memorix 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +119 -236
- package/README.zh-CN.md +119 -238
- package/dist/cli/index.js +13308 -8815
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/app.js +554 -43
- package/dist/dashboard/static/index.html +39 -8
- package/dist/dashboard/static/style.css +2403 -2064
- package/dist/index.js +2816 -602
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,12 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
7
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
8
|
+
}) : x)(function(x) {
|
|
9
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
10
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
11
|
+
});
|
|
6
12
|
var __esm = (fn, res) => function __init() {
|
|
7
13
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
14
|
};
|
|
@@ -470,7 +476,8 @@ var init_types = __esm({
|
|
|
470
476
|
"discovery": "\u{1F7E3}",
|
|
471
477
|
"why-it-exists": "\u{1F7E0}",
|
|
472
478
|
"decision": "\u{1F7E4}",
|
|
473
|
-
"trade-off": "\u2696\uFE0F"
|
|
479
|
+
"trade-off": "\u2696\uFE0F",
|
|
480
|
+
"reasoning": "\u{1F9E0}"
|
|
474
481
|
};
|
|
475
482
|
TOPIC_KEY_FAMILIES = {
|
|
476
483
|
"architecture": ["architecture", "design", "adr", "structure", "pattern"],
|
|
@@ -483,6 +490,146 @@ var init_types = __esm({
|
|
|
483
490
|
}
|
|
484
491
|
});
|
|
485
492
|
|
|
493
|
+
// src/config/yaml-loader.ts
|
|
494
|
+
var yaml_loader_exports = {};
|
|
495
|
+
__export(yaml_loader_exports, {
|
|
496
|
+
initProjectRoot: () => initProjectRoot,
|
|
497
|
+
loadYamlConfig: () => loadYamlConfig,
|
|
498
|
+
resetYamlConfigCache: () => resetYamlConfigCache
|
|
499
|
+
});
|
|
500
|
+
import { existsSync, readFileSync } from "fs";
|
|
501
|
+
import { join } from "path";
|
|
502
|
+
import { homedir } from "os";
|
|
503
|
+
function initProjectRoot(root) {
|
|
504
|
+
globalProjectRoot = root;
|
|
505
|
+
cachedYamlConfig = null;
|
|
506
|
+
cachedProjectRoot = null;
|
|
507
|
+
}
|
|
508
|
+
function loadYamlConfig(projectRoot) {
|
|
509
|
+
const resolvedRoot = projectRoot ?? globalProjectRoot ?? null;
|
|
510
|
+
if (cachedYamlConfig !== null && cachedProjectRoot === resolvedRoot) {
|
|
511
|
+
return cachedYamlConfig;
|
|
512
|
+
}
|
|
513
|
+
const userYaml = join(homedir(), ".memorix", "memorix.yml");
|
|
514
|
+
const projectYaml = resolvedRoot ? join(resolvedRoot, "memorix.yml") : null;
|
|
515
|
+
let userConfig = {};
|
|
516
|
+
let projectConfig = {};
|
|
517
|
+
if (existsSync(userYaml)) {
|
|
518
|
+
try {
|
|
519
|
+
userConfig = parseYaml(readFileSync(userYaml, "utf-8"));
|
|
520
|
+
} catch (err) {
|
|
521
|
+
console.error(`[memorix] Warning: Failed to parse ${userYaml}: ${err}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (projectYaml && existsSync(projectYaml)) {
|
|
525
|
+
try {
|
|
526
|
+
projectConfig = parseYaml(readFileSync(projectYaml, "utf-8"));
|
|
527
|
+
} catch (err) {
|
|
528
|
+
console.error(`[memorix] Warning: Failed to parse ${projectYaml}: ${err}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
cachedYamlConfig = {
|
|
532
|
+
...userConfig,
|
|
533
|
+
...projectConfig,
|
|
534
|
+
// Deep merge for nested objects where both exist
|
|
535
|
+
llm: { ...userConfig.llm, ...projectConfig.llm },
|
|
536
|
+
embedding: { ...userConfig.embedding, ...projectConfig.embedding },
|
|
537
|
+
git: { ...userConfig.git, ...projectConfig.git },
|
|
538
|
+
behavior: { ...userConfig.behavior, ...projectConfig.behavior },
|
|
539
|
+
server: { ...userConfig.server, ...projectConfig.server },
|
|
540
|
+
team: { ...userConfig.team, ...projectConfig.team }
|
|
541
|
+
};
|
|
542
|
+
cachedProjectRoot = resolvedRoot;
|
|
543
|
+
return cachedYamlConfig;
|
|
544
|
+
}
|
|
545
|
+
function resetYamlConfigCache() {
|
|
546
|
+
cachedYamlConfig = null;
|
|
547
|
+
cachedProjectRoot = null;
|
|
548
|
+
}
|
|
549
|
+
function parseYaml(content) {
|
|
550
|
+
try {
|
|
551
|
+
const yaml = __require("js-yaml");
|
|
552
|
+
return yaml.load(content) ?? {};
|
|
553
|
+
} catch {
|
|
554
|
+
try {
|
|
555
|
+
const matter9 = __require("gray-matter");
|
|
556
|
+
const parsed = matter9(`---
|
|
557
|
+
${content}
|
|
558
|
+
---`);
|
|
559
|
+
return parsed.data ?? {};
|
|
560
|
+
} catch {
|
|
561
|
+
console.error("[memorix] YAML parse failed \u2014 check memorix.yml syntax");
|
|
562
|
+
return {};
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
var cachedYamlConfig, cachedProjectRoot, globalProjectRoot;
|
|
567
|
+
var init_yaml_loader = __esm({
|
|
568
|
+
"src/config/yaml-loader.ts"() {
|
|
569
|
+
"use strict";
|
|
570
|
+
init_esm_shims();
|
|
571
|
+
cachedYamlConfig = null;
|
|
572
|
+
cachedProjectRoot = null;
|
|
573
|
+
globalProjectRoot = null;
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// src/config/dotenv-loader.ts
|
|
578
|
+
var dotenv_loader_exports = {};
|
|
579
|
+
__export(dotenv_loader_exports, {
|
|
580
|
+
getLoadedEnvFiles: () => getLoadedEnvFiles,
|
|
581
|
+
loadDotenv: () => loadDotenv,
|
|
582
|
+
resetDotenv: () => resetDotenv
|
|
583
|
+
});
|
|
584
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
585
|
+
import { join as join2 } from "path";
|
|
586
|
+
import { homedir as homedir2 } from "os";
|
|
587
|
+
import { parse } from "dotenv";
|
|
588
|
+
function loadEnvFile(filePath) {
|
|
589
|
+
if (!existsSync2(filePath)) return;
|
|
590
|
+
const parsed = parse(readFileSync2(filePath, "utf-8"));
|
|
591
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
592
|
+
if (!(key in process.env)) {
|
|
593
|
+
process.env[key] = value;
|
|
594
|
+
injectedKeys.add(key);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
loadedEnvFiles.push(filePath);
|
|
598
|
+
}
|
|
599
|
+
function loadDotenv(projectRoot) {
|
|
600
|
+
if (dotenvLoaded && dotenvProjectRoot === (projectRoot ?? null)) return;
|
|
601
|
+
loadedEnvFiles.length = 0;
|
|
602
|
+
if (projectRoot) {
|
|
603
|
+
loadEnvFile(join2(projectRoot, ".env"));
|
|
604
|
+
}
|
|
605
|
+
loadEnvFile(join2(homedir2(), ".memorix", ".env"));
|
|
606
|
+
dotenvLoaded = true;
|
|
607
|
+
dotenvProjectRoot = projectRoot ?? null;
|
|
608
|
+
}
|
|
609
|
+
function resetDotenv() {
|
|
610
|
+
for (const key of injectedKeys) {
|
|
611
|
+
delete process.env[key];
|
|
612
|
+
}
|
|
613
|
+
injectedKeys.clear();
|
|
614
|
+
dotenvLoaded = false;
|
|
615
|
+
dotenvProjectRoot = null;
|
|
616
|
+
loadedEnvFiles.length = 0;
|
|
617
|
+
}
|
|
618
|
+
function getLoadedEnvFiles() {
|
|
619
|
+
return loadedEnvFiles;
|
|
620
|
+
}
|
|
621
|
+
var dotenvLoaded, dotenvProjectRoot, loadedEnvFiles, injectedKeys;
|
|
622
|
+
var init_dotenv_loader = __esm({
|
|
623
|
+
"src/config/dotenv-loader.ts"() {
|
|
624
|
+
"use strict";
|
|
625
|
+
init_esm_shims();
|
|
626
|
+
dotenvLoaded = false;
|
|
627
|
+
dotenvProjectRoot = null;
|
|
628
|
+
loadedEnvFiles = [];
|
|
629
|
+
injectedKeys = /* @__PURE__ */ new Set();
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
486
633
|
// src/config.ts
|
|
487
634
|
var config_exports = {};
|
|
488
635
|
__export(config_exports, {
|
|
@@ -491,22 +638,29 @@ __export(config_exports, {
|
|
|
491
638
|
getEmbeddingDimensions: () => getEmbeddingDimensions,
|
|
492
639
|
getEmbeddingMode: () => getEmbeddingMode,
|
|
493
640
|
getEmbeddingModel: () => getEmbeddingModel,
|
|
641
|
+
getGitConfig: () => getGitConfig,
|
|
494
642
|
getLLMApiKey: () => getLLMApiKey,
|
|
495
643
|
getLLMBaseUrl: () => getLLMBaseUrl,
|
|
496
644
|
getLLMModel: () => getLLMModel,
|
|
497
645
|
getLLMProvider: () => getLLMProvider,
|
|
646
|
+
getLoadedEnvFiles: () => getLoadedEnvFiles,
|
|
647
|
+
getServerConfig: () => getServerConfig,
|
|
648
|
+
getTeamConfig: () => getTeamConfig,
|
|
649
|
+
loadDotenv: () => loadDotenv,
|
|
498
650
|
loadFileConfig: () => loadFileConfig,
|
|
499
|
-
|
|
651
|
+
loadYamlConfig: () => loadYamlConfig,
|
|
652
|
+
resetConfigCache: () => resetConfigCache,
|
|
653
|
+
resetDotenv: () => resetDotenv
|
|
500
654
|
});
|
|
501
|
-
import { existsSync, readFileSync } from "fs";
|
|
502
|
-
import { join } from "path";
|
|
503
|
-
import { homedir } from "os";
|
|
655
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
656
|
+
import { join as join3 } from "path";
|
|
657
|
+
import { homedir as homedir3 } from "os";
|
|
504
658
|
function loadFileConfig() {
|
|
505
659
|
if (cachedConfig !== null) return cachedConfig;
|
|
506
|
-
const configPath =
|
|
660
|
+
const configPath = join3(homedir3(), ".memorix", "config.json");
|
|
507
661
|
try {
|
|
508
|
-
if (
|
|
509
|
-
cachedConfig = JSON.parse(
|
|
662
|
+
if (existsSync3(configPath)) {
|
|
663
|
+
cachedConfig = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
510
664
|
return cachedConfig;
|
|
511
665
|
}
|
|
512
666
|
} catch {
|
|
@@ -518,10 +672,14 @@ function resetConfigCache() {
|
|
|
518
672
|
cachedConfig = null;
|
|
519
673
|
}
|
|
520
674
|
function getLLMApiKey() {
|
|
521
|
-
return process.env.MEMORIX_LLM_API_KEY ||
|
|
675
|
+
return process.env.MEMORIX_LLM_API_KEY || // LLM-specific (优先级最高)
|
|
676
|
+
process.env.MEMORIX_API_KEY || // Unified API key (fallback)
|
|
677
|
+
loadYamlConfig().llm?.apiKey || loadFileConfig().llm?.apiKey || process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY || process.env.OPENROUTER_API_KEY || void 0;
|
|
522
678
|
}
|
|
523
679
|
function getLLMProvider() {
|
|
524
680
|
if (process.env.MEMORIX_LLM_PROVIDER) return process.env.MEMORIX_LLM_PROVIDER;
|
|
681
|
+
const yml = loadYamlConfig();
|
|
682
|
+
if (yml.llm?.provider) return yml.llm.provider;
|
|
525
683
|
const cfg = loadFileConfig();
|
|
526
684
|
if (cfg.llm?.provider) return cfg.llm.provider;
|
|
527
685
|
if (process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY) return "anthropic";
|
|
@@ -529,14 +687,17 @@ function getLLMProvider() {
|
|
|
529
687
|
return "openai";
|
|
530
688
|
}
|
|
531
689
|
function getLLMModel(providerDefault) {
|
|
532
|
-
return process.env.MEMORIX_LLM_MODEL || loadFileConfig().llm?.model || providerDefault;
|
|
690
|
+
return process.env.MEMORIX_LLM_MODEL || loadYamlConfig().llm?.model || loadFileConfig().llm?.model || providerDefault;
|
|
533
691
|
}
|
|
534
692
|
function getLLMBaseUrl(providerDefault) {
|
|
535
|
-
return process.env.MEMORIX_LLM_BASE_URL || loadFileConfig().llm?.baseUrl || providerDefault;
|
|
693
|
+
return process.env.MEMORIX_LLM_BASE_URL || loadYamlConfig().llm?.baseUrl || loadFileConfig().llm?.baseUrl || providerDefault;
|
|
536
694
|
}
|
|
537
695
|
function getEmbeddingMode() {
|
|
538
696
|
const env = process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim();
|
|
539
697
|
if (env === "fastembed" || env === "transformers" || env === "api" || env === "auto") return env;
|
|
698
|
+
const yml = loadYamlConfig();
|
|
699
|
+
const ymlEmb = yml.embedding?.provider;
|
|
700
|
+
if (ymlEmb === "fastembed" || ymlEmb === "transformers" || ymlEmb === "api" || ymlEmb === "auto") return ymlEmb;
|
|
540
701
|
const cfg = loadFileConfig();
|
|
541
702
|
if (cfg.embedding === "fastembed" || cfg.embedding === "transformers" || cfg.embedding === "api" || cfg.embedding === "auto") {
|
|
542
703
|
return cfg.embedding;
|
|
@@ -544,26 +705,42 @@ function getEmbeddingMode() {
|
|
|
544
705
|
return "off";
|
|
545
706
|
}
|
|
546
707
|
function getEmbeddingApiKey() {
|
|
547
|
-
return process.env.MEMORIX_EMBEDDING_API_KEY ||
|
|
708
|
+
return process.env.MEMORIX_EMBEDDING_API_KEY || // Embedding-specific (优先级最高)
|
|
709
|
+
process.env.MEMORIX_API_KEY || // Unified API key (fallback)
|
|
710
|
+
process.env.MEMORIX_LLM_API_KEY || loadYamlConfig().embedding?.apiKey || loadFileConfig().embeddingApi?.apiKey || loadYamlConfig().llm?.apiKey || loadFileConfig().llm?.apiKey || process.env.OPENAI_API_KEY || void 0;
|
|
548
711
|
}
|
|
549
712
|
function getEmbeddingBaseUrl() {
|
|
550
|
-
return process.env.MEMORIX_EMBEDDING_BASE_URL || loadFileConfig().embeddingApi?.baseUrl || process.env.MEMORIX_LLM_BASE_URL || loadFileConfig().llm?.baseUrl || "https://api.openai.com/v1";
|
|
713
|
+
return process.env.MEMORIX_EMBEDDING_BASE_URL || loadYamlConfig().embedding?.baseUrl || loadFileConfig().embeddingApi?.baseUrl || process.env.MEMORIX_LLM_BASE_URL || loadYamlConfig().llm?.baseUrl || loadFileConfig().llm?.baseUrl || "https://api.openai.com/v1";
|
|
551
714
|
}
|
|
552
715
|
function getEmbeddingModel() {
|
|
553
|
-
return process.env.MEMORIX_EMBEDDING_MODEL || loadFileConfig().embeddingApi?.model || "text-embedding-3-small";
|
|
716
|
+
return process.env.MEMORIX_EMBEDDING_MODEL || loadYamlConfig().embedding?.model || loadFileConfig().embeddingApi?.model || "text-embedding-3-small";
|
|
554
717
|
}
|
|
555
718
|
function getEmbeddingDimensions() {
|
|
556
719
|
const envDim = process.env.MEMORIX_EMBEDDING_DIMENSIONS;
|
|
557
720
|
if (envDim) return parseInt(envDim, 10);
|
|
721
|
+
const ymlDim = loadYamlConfig().embedding?.dimensions;
|
|
722
|
+
if (ymlDim) return ymlDim;
|
|
558
723
|
const cfgDim = loadFileConfig().embeddingApi?.dimensions;
|
|
559
724
|
if (cfgDim) return cfgDim;
|
|
560
725
|
return null;
|
|
561
726
|
}
|
|
727
|
+
function getGitConfig() {
|
|
728
|
+
return loadYamlConfig().git ?? {};
|
|
729
|
+
}
|
|
730
|
+
function getServerConfig() {
|
|
731
|
+
return loadYamlConfig().server ?? {};
|
|
732
|
+
}
|
|
733
|
+
function getTeamConfig() {
|
|
734
|
+
return loadYamlConfig().team ?? {};
|
|
735
|
+
}
|
|
562
736
|
var cachedConfig;
|
|
563
737
|
var init_config = __esm({
|
|
564
738
|
"src/config.ts"() {
|
|
565
739
|
"use strict";
|
|
566
740
|
init_esm_shims();
|
|
741
|
+
init_yaml_loader();
|
|
742
|
+
init_dotenv_loader();
|
|
743
|
+
init_yaml_loader();
|
|
567
744
|
cachedConfig = null;
|
|
568
745
|
}
|
|
569
746
|
});
|
|
@@ -575,8 +752,8 @@ __export(fastembed_provider_exports, {
|
|
|
575
752
|
});
|
|
576
753
|
import { createHash } from "crypto";
|
|
577
754
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
578
|
-
import { join as
|
|
579
|
-
import { homedir as
|
|
755
|
+
import { join as join4 } from "path";
|
|
756
|
+
import { homedir as homedir4 } from "os";
|
|
580
757
|
function textHash(text) {
|
|
581
758
|
return createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
582
759
|
}
|
|
@@ -604,8 +781,8 @@ var init_fastembed_provider = __esm({
|
|
|
604
781
|
"src/embedding/fastembed-provider.ts"() {
|
|
605
782
|
"use strict";
|
|
606
783
|
init_esm_shims();
|
|
607
|
-
CACHE_DIR = process.env.MEMORIX_DATA_DIR ||
|
|
608
|
-
CACHE_FILE =
|
|
784
|
+
CACHE_DIR = process.env.MEMORIX_DATA_DIR || join4(homedir4(), ".memorix", "data");
|
|
785
|
+
CACHE_FILE = join4(CACHE_DIR, ".embedding-cache.json");
|
|
609
786
|
cache = /* @__PURE__ */ new Map();
|
|
610
787
|
MAX_CACHE_SIZE = 5e3;
|
|
611
788
|
diskCacheDirty = false;
|
|
@@ -631,8 +808,8 @@ var init_fastembed_provider = __esm({
|
|
|
631
808
|
}
|
|
632
809
|
async embed(text) {
|
|
633
810
|
const hash = textHash(text);
|
|
634
|
-
const
|
|
635
|
-
if (
|
|
811
|
+
const cached2 = cache.get(hash);
|
|
812
|
+
if (cached2) return cached2;
|
|
636
813
|
const raw = await this.model.queryEmbed(text);
|
|
637
814
|
const result = Array.from(raw);
|
|
638
815
|
if (result.length !== this.dimensions) {
|
|
@@ -647,9 +824,9 @@ var init_fastembed_provider = __esm({
|
|
|
647
824
|
const uncachedTexts = [];
|
|
648
825
|
for (let i = 0; i < texts.length; i++) {
|
|
649
826
|
const hash = textHash(texts[i]);
|
|
650
|
-
const
|
|
651
|
-
if (
|
|
652
|
-
results[i] =
|
|
827
|
+
const cached2 = cache.get(hash);
|
|
828
|
+
if (cached2) {
|
|
829
|
+
results[i] = cached2;
|
|
653
830
|
} else {
|
|
654
831
|
uncachedIndices.push(i);
|
|
655
832
|
uncachedTexts.push(texts[i]);
|
|
@@ -718,8 +895,8 @@ var init_transformers_provider = __esm({
|
|
|
718
895
|
return new _TransformersProvider(extractor);
|
|
719
896
|
}
|
|
720
897
|
async embed(text) {
|
|
721
|
-
const
|
|
722
|
-
if (
|
|
898
|
+
const cached2 = cache2.get(text);
|
|
899
|
+
if (cached2) return cached2;
|
|
723
900
|
const output = await this.extractor(text, {
|
|
724
901
|
pooling: "mean",
|
|
725
902
|
normalize: true
|
|
@@ -736,9 +913,9 @@ var init_transformers_provider = __esm({
|
|
|
736
913
|
const uncachedIndices = [];
|
|
737
914
|
const uncachedTexts = [];
|
|
738
915
|
for (let i = 0; i < texts.length; i++) {
|
|
739
|
-
const
|
|
740
|
-
if (
|
|
741
|
-
results[i] =
|
|
916
|
+
const cached2 = cache2.get(texts[i]);
|
|
917
|
+
if (cached2) {
|
|
918
|
+
results[i] = cached2;
|
|
742
919
|
} else {
|
|
743
920
|
uncachedIndices.push(i);
|
|
744
921
|
uncachedTexts.push(texts[i]);
|
|
@@ -777,8 +954,8 @@ __export(api_provider_exports, {
|
|
|
777
954
|
});
|
|
778
955
|
import { createHash as createHash2 } from "crypto";
|
|
779
956
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
780
|
-
import { join as
|
|
781
|
-
import { homedir as
|
|
957
|
+
import { join as join5 } from "path";
|
|
958
|
+
import { homedir as homedir5 } from "os";
|
|
782
959
|
function normalizeText(text) {
|
|
783
960
|
return text.replace(/\s+/g, " ").trim().slice(0, MAX_INPUT_CHARS);
|
|
784
961
|
}
|
|
@@ -861,8 +1038,8 @@ var init_api_provider = __esm({
|
|
|
861
1038
|
"src/embedding/api-provider.ts"() {
|
|
862
1039
|
"use strict";
|
|
863
1040
|
init_esm_shims();
|
|
864
|
-
CACHE_DIR2 = process.env.MEMORIX_DATA_DIR ||
|
|
865
|
-
CACHE_FILE2 =
|
|
1041
|
+
CACHE_DIR2 = process.env.MEMORIX_DATA_DIR || join5(homedir5(), ".memorix", "data");
|
|
1042
|
+
CACHE_FILE2 = join5(CACHE_DIR2, ".embedding-api-cache.json");
|
|
866
1043
|
cache3 = /* @__PURE__ */ new Map();
|
|
867
1044
|
MAX_CACHE_SIZE3 = 1e4;
|
|
868
1045
|
diskCacheDirty2 = false;
|
|
@@ -914,7 +1091,8 @@ var init_api_provider = __esm({
|
|
|
914
1091
|
model = cfg.getEmbeddingModel();
|
|
915
1092
|
requestedDimensions = cfg.getEmbeddingDimensions();
|
|
916
1093
|
} catch {
|
|
917
|
-
apiKey = process.env.MEMORIX_EMBEDDING_API_KEY || process.env.
|
|
1094
|
+
apiKey = process.env.MEMORIX_EMBEDDING_API_KEY || process.env.MEMORIX_API_KEY || // Unified API key
|
|
1095
|
+
process.env.MEMORIX_LLM_API_KEY || process.env.OPENAI_API_KEY;
|
|
918
1096
|
baseUrl = process.env.MEMORIX_EMBEDDING_BASE_URL || process.env.MEMORIX_LLM_BASE_URL || "https://api.openai.com/v1";
|
|
919
1097
|
model = process.env.MEMORIX_EMBEDDING_MODEL || "text-embedding-3-small";
|
|
920
1098
|
const dimStr = process.env.MEMORIX_EMBEDDING_DIMENSIONS;
|
|
@@ -952,8 +1130,8 @@ var init_api_provider = __esm({
|
|
|
952
1130
|
async embed(text) {
|
|
953
1131
|
const normalized = normalizeText(text);
|
|
954
1132
|
const hash = textHash2(normalized);
|
|
955
|
-
const
|
|
956
|
-
if (
|
|
1133
|
+
const cached2 = cache3.get(hash);
|
|
1134
|
+
if (cached2) return cached2;
|
|
957
1135
|
const body = {
|
|
958
1136
|
model: this.config.model,
|
|
959
1137
|
input: normalized
|
|
@@ -982,9 +1160,9 @@ var init_api_provider = __esm({
|
|
|
982
1160
|
const uncachedTexts = [];
|
|
983
1161
|
for (let i = 0; i < normalizedTexts.length; i++) {
|
|
984
1162
|
const hash = textHash2(normalizedTexts[i]);
|
|
985
|
-
const
|
|
986
|
-
if (
|
|
987
|
-
results[i] =
|
|
1163
|
+
const cached2 = cache3.get(hash);
|
|
1164
|
+
if (cached2) {
|
|
1165
|
+
results[i] = cached2;
|
|
988
1166
|
} else {
|
|
989
1167
|
uncachedIndices.push(i);
|
|
990
1168
|
uncachedTexts.push(normalizedTexts[i]);
|
|
@@ -1248,6 +1426,7 @@ function detectQueryIntent(query) {
|
|
|
1248
1426
|
confidence,
|
|
1249
1427
|
typeBoosts: INTENT_TYPE_BOOSTS[bestIntent],
|
|
1250
1428
|
fieldBoosts: INTENT_FIELD_BOOSTS[bestIntent],
|
|
1429
|
+
sourceBoosts: INTENT_SOURCE_BOOSTS[bestIntent],
|
|
1251
1430
|
preferChronological: bestIntent === "when"
|
|
1252
1431
|
};
|
|
1253
1432
|
}
|
|
@@ -1257,7 +1436,7 @@ function applyIntentBoost(score, type, intentResult) {
|
|
|
1257
1436
|
const effectiveBoost = 1 + (boost - 1) * intentResult.confidence;
|
|
1258
1437
|
return score * effectiveBoost;
|
|
1259
1438
|
}
|
|
1260
|
-
var INTENT_PATTERNS, INTENT_TYPE_BOOSTS, INTENT_FIELD_BOOSTS;
|
|
1439
|
+
var INTENT_PATTERNS, INTENT_TYPE_BOOSTS, INTENT_SOURCE_BOOSTS, INTENT_FIELD_BOOSTS;
|
|
1261
1440
|
var init_intent_detector = __esm({
|
|
1262
1441
|
"src/search/intent-detector.ts"() {
|
|
1263
1442
|
"use strict";
|
|
@@ -1390,6 +1569,35 @@ var init_intent_detector = __esm({
|
|
|
1390
1569
|
// No special boosting
|
|
1391
1570
|
}
|
|
1392
1571
|
};
|
|
1572
|
+
INTENT_SOURCE_BOOSTS = {
|
|
1573
|
+
what_changed: {
|
|
1574
|
+
git: 2,
|
|
1575
|
+
// "what changed" → prefer commit-derived ground truth
|
|
1576
|
+
agent: 0.8
|
|
1577
|
+
},
|
|
1578
|
+
when: {
|
|
1579
|
+
git: 1.8,
|
|
1580
|
+
// Temporal queries → git has precise timestamps
|
|
1581
|
+
agent: 1
|
|
1582
|
+
},
|
|
1583
|
+
why: {
|
|
1584
|
+
agent: 2,
|
|
1585
|
+
// "why" → prefer reasoning/decision memories
|
|
1586
|
+
git: 0.7
|
|
1587
|
+
// commits rarely explain WHY
|
|
1588
|
+
},
|
|
1589
|
+
how: {
|
|
1590
|
+
agent: 1.5,
|
|
1591
|
+
// "how" → prefer explanations
|
|
1592
|
+
git: 1
|
|
1593
|
+
},
|
|
1594
|
+
problem: {
|
|
1595
|
+
git: 1.5,
|
|
1596
|
+
// Bug fixes often in commit history
|
|
1597
|
+
agent: 1.5
|
|
1598
|
+
// But also in problem-solution memories
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1393
1601
|
INTENT_FIELD_BOOSTS = {
|
|
1394
1602
|
why: {
|
|
1395
1603
|
title: 2,
|
|
@@ -1436,7 +1644,7 @@ function normalizePath(p) {
|
|
|
1436
1644
|
return normalized;
|
|
1437
1645
|
}
|
|
1438
1646
|
function idPriority(id) {
|
|
1439
|
-
if (id.startsWith("
|
|
1647
|
+
if (id.startsWith("untracked/")) return 0;
|
|
1440
1648
|
if (id.startsWith("local/")) return 1;
|
|
1441
1649
|
return 2;
|
|
1442
1650
|
}
|
|
@@ -1626,6 +1834,14 @@ var init_aliases = __esm({
|
|
|
1626
1834
|
});
|
|
1627
1835
|
|
|
1628
1836
|
// src/llm/provider.ts
|
|
1837
|
+
var provider_exports2 = {};
|
|
1838
|
+
__export(provider_exports2, {
|
|
1839
|
+
callLLM: () => callLLM,
|
|
1840
|
+
getLLMConfig: () => getLLMConfig,
|
|
1841
|
+
initLLM: () => initLLM,
|
|
1842
|
+
isLLMEnabled: () => isLLMEnabled,
|
|
1843
|
+
setLLMConfig: () => setLLMConfig
|
|
1844
|
+
});
|
|
1629
1845
|
function initLLM() {
|
|
1630
1846
|
const { getLLMApiKey: getLLMApiKey2, getLLMProvider: getLLMProvider2, getLLMModel: getLLMModel2, getLLMBaseUrl: getLLMBaseUrl2 } = (init_config(), __toCommonJS(config_exports));
|
|
1631
1847
|
const apiKey = getLLMApiKey2();
|
|
@@ -1649,6 +1865,9 @@ function isLLMEnabled() {
|
|
|
1649
1865
|
function getLLMConfig() {
|
|
1650
1866
|
return currentConfig;
|
|
1651
1867
|
}
|
|
1868
|
+
function setLLMConfig(config) {
|
|
1869
|
+
currentConfig = config;
|
|
1870
|
+
}
|
|
1652
1871
|
async function callLLM(systemPrompt, userMessage) {
|
|
1653
1872
|
if (!currentConfig) {
|
|
1654
1873
|
throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
|
|
@@ -1748,7 +1967,7 @@ __export(quality_exports, {
|
|
|
1748
1967
|
});
|
|
1749
1968
|
async function compressNarrative(narrative, facts, type) {
|
|
1750
1969
|
const originalTokens = estimateTokens(narrative);
|
|
1751
|
-
if (!isLLMEnabled() || narrative.length <=
|
|
1970
|
+
if (!isLLMEnabled() || narrative.length <= 150) {
|
|
1752
1971
|
return { compressed: narrative, saved: 0, usedLLM: false };
|
|
1753
1972
|
}
|
|
1754
1973
|
if (shouldSkipCompression(narrative, type)) {
|
|
@@ -1758,7 +1977,9 @@ async function compressNarrative(narrative, facts, type) {
|
|
|
1758
1977
|
const factsContext = facts && facts.length > 0 ? `
|
|
1759
1978
|
|
|
1760
1979
|
Separate facts (already stored, don't repeat): ${facts.join("; ")}` : "";
|
|
1761
|
-
const
|
|
1980
|
+
const HIGH_VALUE_TYPES = /* @__PURE__ */ new Set(["decision", "trade-off", "why-it-exists", "how-it-works"]);
|
|
1981
|
+
const prompt = type && HIGH_VALUE_TYPES.has(type) ? COMPRESS_PROMPT_GENTLE : COMPRESS_PROMPT;
|
|
1982
|
+
const response = await callLLM(prompt, narrative + factsContext);
|
|
1762
1983
|
const compressed = response.content.trim();
|
|
1763
1984
|
if (!compressed || compressed.length >= narrative.length) {
|
|
1764
1985
|
return { compressed: narrative, saved: 0, usedLLM: true };
|
|
@@ -1830,7 +2051,7 @@ function estimateTokens(text) {
|
|
|
1830
2051
|
const otherChars = text.length - cjkChars;
|
|
1831
2052
|
return Math.ceil(cjkChars / 1.5 + otherChars / 4);
|
|
1832
2053
|
}
|
|
1833
|
-
var COMPRESS_PROMPT, RERANK_PROMPT, SKIP_PATTERNS, LOW_COMPRESSION_TYPES;
|
|
2054
|
+
var COMPRESS_PROMPT, COMPRESS_PROMPT_GENTLE, RERANK_PROMPT, SKIP_PATTERNS, LOW_COMPRESSION_TYPES;
|
|
1834
2055
|
var init_quality = __esm({
|
|
1835
2056
|
"src/llm/quality.ts"() {
|
|
1836
2057
|
"use strict";
|
|
@@ -1838,13 +2059,12 @@ var init_quality = __esm({
|
|
|
1838
2059
|
init_provider2();
|
|
1839
2060
|
COMPRESS_PROMPT = `You are a memory compression engine for a coding assistant.
|
|
1840
2061
|
|
|
1841
|
-
Compress the given narrative
|
|
2062
|
+
Compress the given narrative while preserving ALL technical facts and reasoning.
|
|
1842
2063
|
|
|
1843
2064
|
Rules:
|
|
1844
|
-
-
|
|
1845
|
-
-
|
|
1846
|
-
-
|
|
1847
|
-
- Merge related points into single dense sentences
|
|
2065
|
+
- Remove: filler words, debugging journey, repeated info already in facts
|
|
2066
|
+
- Keep: specific values, file paths, error messages, version numbers, config keys, causal relationships, design reasoning
|
|
2067
|
+
- Merge related points into dense sentences
|
|
1848
2068
|
- If facts are provided separately, do NOT repeat them in the compressed narrative
|
|
1849
2069
|
- Output the compressed text ONLY, no explanation or wrapper
|
|
1850
2070
|
|
|
@@ -1854,6 +2074,16 @@ Output: "JWT refresh\u65E0\u81EA\u52A8\u7EED\u7B7E\u219224h\u540E\u9759\u9ED8\u8
|
|
|
1854
2074
|
|
|
1855
2075
|
Input: "Final deployment model for shadcn-blog is stable: GitHub Actions build locally, SCP artifacts to VPS, systemd manages the process. Docker was considered but rejected due to complexity overhead for a simple blog. The whole pipeline takes about 2 minutes from push to live."
|
|
1856
2076
|
Output: "shadcn-blog\u90E8\u7F72: GH Actions\u6784\u5EFA\u2192SCP\u5230VPS\u2192systemd\u7BA1\u7406, \u5F03Docker(\u590D\u6742\u5EA6\u8FC7\u9AD8), push\u5230\u4E0A\u7EBF~2min"`;
|
|
2077
|
+
COMPRESS_PROMPT_GENTLE = `You are a memory compression engine for a coding assistant.
|
|
2078
|
+
|
|
2079
|
+
Lightly compress the given narrative \u2014 preserve reasoning, trade-offs, and "why" context.
|
|
2080
|
+
|
|
2081
|
+
Rules:
|
|
2082
|
+
- Only remove: obvious filler, debugging detours, info already in the separate facts list
|
|
2083
|
+
- PRESERVE: design reasoning, rejected alternatives, trade-off analysis, causal chains
|
|
2084
|
+
- Aim for ~70-80% of original length, NOT aggressive compression
|
|
2085
|
+
- If facts are provided separately, do NOT repeat them in the compressed narrative
|
|
2086
|
+
- Output the compressed text ONLY, no explanation or wrapper`;
|
|
1857
2087
|
RERANK_PROMPT = `You are a memory relevance ranker for a coding assistant.
|
|
1858
2088
|
|
|
1859
2089
|
Given a QUERY (what the user/agent is looking for) and a list of CANDIDATE memories,
|
|
@@ -1867,7 +2097,7 @@ Rules:
|
|
|
1867
2097
|
- Output ONLY a JSON array of IDs in order of relevance (most relevant first)
|
|
1868
2098
|
- Include ALL candidate IDs, just reorder them
|
|
1869
2099
|
|
|
1870
|
-
Example output: [
|
|
2100
|
+
Example output: ["r1", "r3", "r2"]`;
|
|
1871
2101
|
SKIP_PATTERNS = [
|
|
1872
2102
|
/^(?:Command|Run|Execute):\s/i,
|
|
1873
2103
|
// Shell commands
|
|
@@ -1899,11 +2129,18 @@ __export(orama_store_exports, {
|
|
|
1899
2129
|
getTimeline: () => getTimeline,
|
|
1900
2130
|
insertObservation: () => insertObservation,
|
|
1901
2131
|
isEmbeddingEnabled: () => isEmbeddingEnabled,
|
|
2132
|
+
makeOramaObservationId: () => makeOramaObservationId,
|
|
1902
2133
|
removeObservation: () => removeObservation,
|
|
1903
2134
|
resetDb: () => resetDb,
|
|
1904
2135
|
searchObservations: () => searchObservations
|
|
1905
2136
|
});
|
|
1906
2137
|
import { create, insert, search, remove, update, count } from "@orama/orama";
|
|
2138
|
+
function makeOramaObservationId(projectId, observationId) {
|
|
2139
|
+
return `obs-${encodeURIComponent(projectId)}-${observationId}`;
|
|
2140
|
+
}
|
|
2141
|
+
function makeEntryKey(projectId, observationId) {
|
|
2142
|
+
return `${projectId ?? ""}::${observationId}`;
|
|
2143
|
+
}
|
|
1907
2144
|
async function getDb() {
|
|
1908
2145
|
if (db) return db;
|
|
1909
2146
|
const provider2 = await getEmbeddingProvider();
|
|
@@ -1923,7 +2160,8 @@ async function getDb() {
|
|
|
1923
2160
|
projectId: "string",
|
|
1924
2161
|
accessCount: "number",
|
|
1925
2162
|
lastAccessedAt: "string",
|
|
1926
|
-
status: "string"
|
|
2163
|
+
status: "string",
|
|
2164
|
+
source: "string"
|
|
1927
2165
|
};
|
|
1928
2166
|
const dims = provider2?.dimensions ?? 384;
|
|
1929
2167
|
const schema = embeddingEnabled ? { ...baseSchema, embedding: `vector[${dims}]` } : baseSchema;
|
|
@@ -1978,6 +2216,9 @@ async function searchObservations(options) {
|
|
|
1978
2216
|
if (options.type) {
|
|
1979
2217
|
filters["type"] = options.type;
|
|
1980
2218
|
}
|
|
2219
|
+
if (options.source) {
|
|
2220
|
+
filters["source"] = options.source;
|
|
2221
|
+
}
|
|
1981
2222
|
const hasQuery = options.query && options.query.trim().length > 0;
|
|
1982
2223
|
const intentResult = hasQuery ? detectQueryIntent(options.query) : null;
|
|
1983
2224
|
const requestLimit = projectIds && projectIds.length > 1 ? (options.limit ?? 20) * 3 : options.limit ?? 20;
|
|
@@ -2006,7 +2247,12 @@ async function searchObservations(options) {
|
|
|
2006
2247
|
try {
|
|
2007
2248
|
const provider2 = await getEmbeddingProvider();
|
|
2008
2249
|
if (provider2) {
|
|
2009
|
-
|
|
2250
|
+
const EMBEDDING_TIMEOUT_MS = 15e3;
|
|
2251
|
+
const embedPromise = provider2.embed(options.query);
|
|
2252
|
+
const timeoutPromise = new Promise(
|
|
2253
|
+
(_, reject) => setTimeout(() => reject(new Error(`Embedding timeout after ${EMBEDDING_TIMEOUT_MS}ms`)), EMBEDDING_TIMEOUT_MS)
|
|
2254
|
+
);
|
|
2255
|
+
queryVector = await Promise.race([embedPromise, timeoutPromise]);
|
|
2010
2256
|
const cjkRatio = (options.query.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / options.query.length;
|
|
2011
2257
|
const isCJKHeavy = cjkRatio > 0.3;
|
|
2012
2258
|
searchParams = {
|
|
@@ -2020,7 +2266,8 @@ async function searchObservations(options) {
|
|
|
2020
2266
|
hybridWeights: isCJKHeavy ? { text: 0.2, vector: 0.8 } : { text: 0.6, vector: 0.4 }
|
|
2021
2267
|
};
|
|
2022
2268
|
}
|
|
2023
|
-
} catch {
|
|
2269
|
+
} catch (error) {
|
|
2270
|
+
console.error("[memorix] Embedding failed or timed out, falling back to fulltext search");
|
|
2024
2271
|
}
|
|
2025
2272
|
}
|
|
2026
2273
|
let results = await search(database, searchParams);
|
|
@@ -2068,7 +2315,9 @@ async function searchObservations(options) {
|
|
|
2068
2315
|
icon: OBSERVATION_ICONS[obsType] ?? "\u2753",
|
|
2069
2316
|
title: doc.title,
|
|
2070
2317
|
tokens: doc.tokens,
|
|
2071
|
-
score: (hit.score ?? 1) * recencyBoost
|
|
2318
|
+
score: (hit.score ?? 1) * recencyBoost,
|
|
2319
|
+
projectId: doc.projectId,
|
|
2320
|
+
source: doc.source || "agent"
|
|
2072
2321
|
};
|
|
2073
2322
|
});
|
|
2074
2323
|
if (intentResult && intentResult.confidence > 0.3) {
|
|
@@ -2077,6 +2326,14 @@ async function searchObservations(options) {
|
|
|
2077
2326
|
score: applyIntentBoost(entry.score, entry.type, intentResult)
|
|
2078
2327
|
}));
|
|
2079
2328
|
}
|
|
2329
|
+
if (intentResult && intentResult.confidence > 0.3 && intentResult.sourceBoosts) {
|
|
2330
|
+
const srcBoosts = intentResult.sourceBoosts;
|
|
2331
|
+
intermediate = intermediate.map((entry) => {
|
|
2332
|
+
const boost = srcBoosts[entry.source] ?? 1;
|
|
2333
|
+
const effectiveBoost = 1 + (boost - 1) * intentResult.confidence;
|
|
2334
|
+
return { ...entry, score: entry.score * effectiveBoost };
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2080
2337
|
if (intentResult?.preferChronological) {
|
|
2081
2338
|
intermediate.sort((a, b) => new Date(b.rawTime).getTime() - new Date(a.rawTime).getTime());
|
|
2082
2339
|
} else {
|
|
@@ -2130,34 +2387,40 @@ async function searchObservations(options) {
|
|
|
2130
2387
|
const narrativeMap = /* @__PURE__ */ new Map();
|
|
2131
2388
|
for (const hit of results.hits) {
|
|
2132
2389
|
const doc = hit.document;
|
|
2133
|
-
narrativeMap.set(doc.observationId, doc.narrative);
|
|
2390
|
+
narrativeMap.set(makeEntryKey(doc.projectId, doc.observationId), doc.narrative);
|
|
2134
2391
|
}
|
|
2135
|
-
const candidates = intermediate.map((e) => ({
|
|
2136
|
-
id:
|
|
2392
|
+
const candidates = intermediate.map((e, index) => ({
|
|
2393
|
+
id: `r${index + 1}`,
|
|
2137
2394
|
title: e.title,
|
|
2138
2395
|
type: e.type,
|
|
2139
2396
|
score: e.score,
|
|
2140
|
-
narrative: narrativeMap.get(e.id)
|
|
2397
|
+
narrative: narrativeMap.get(makeEntryKey(e.projectId, e.id))
|
|
2141
2398
|
}));
|
|
2142
|
-
const
|
|
2399
|
+
const RERANK_TIMEOUT_MS = 1e4;
|
|
2400
|
+
const rerankPromise = rerankResults2(options.query, candidates);
|
|
2401
|
+
const timeoutPromise = new Promise(
|
|
2402
|
+
(_, reject) => setTimeout(() => reject(new Error(`LLM rerank timeout after ${RERANK_TIMEOUT_MS}ms`)), RERANK_TIMEOUT_MS)
|
|
2403
|
+
);
|
|
2404
|
+
const { reranked, usedLLM } = await Promise.race([rerankPromise, timeoutPromise]);
|
|
2143
2405
|
if (usedLLM) {
|
|
2144
|
-
const
|
|
2145
|
-
const rerankedIntermediate = reranked.map((r) =>
|
|
2406
|
+
const candidateMap = new Map(candidates.map((candidate, index) => [candidate.id, intermediate[index]]));
|
|
2407
|
+
const rerankedIntermediate = reranked.map((r) => candidateMap.get(r.id)).filter((e) => e != null);
|
|
2146
2408
|
if (rerankedIntermediate.length > 0) {
|
|
2147
2409
|
intermediate = rerankedIntermediate;
|
|
2148
2410
|
}
|
|
2149
2411
|
}
|
|
2150
|
-
} catch {
|
|
2412
|
+
} catch (error) {
|
|
2413
|
+
console.error("[memorix] LLM rerank failed or timed out, using original order");
|
|
2151
2414
|
}
|
|
2152
2415
|
}
|
|
2153
2416
|
let entries = intermediate.map(({ rawTime: _, ...rest }) => rest);
|
|
2154
2417
|
if (hasQuery && options.query) {
|
|
2155
2418
|
const queryLower = options.query.toLowerCase();
|
|
2156
2419
|
const queryTokens = queryLower.split(/\s+/).filter((t) => t.length > 1);
|
|
2157
|
-
const entryMap = new Map(entries.map((e) => [e.id, e]));
|
|
2420
|
+
const entryMap = new Map(entries.map((e) => [makeEntryKey(e.projectId, e.id), e]));
|
|
2158
2421
|
for (const hit of results.hits) {
|
|
2159
2422
|
const doc = hit.document;
|
|
2160
|
-
const entry = entryMap.get(doc.observationId);
|
|
2423
|
+
const entry = entryMap.get(makeEntryKey(doc.projectId, doc.observationId));
|
|
2161
2424
|
if (!entry) continue;
|
|
2162
2425
|
const reasons = [];
|
|
2163
2426
|
const fields = [
|
|
@@ -2173,7 +2436,7 @@ async function searchObservations(options) {
|
|
|
2173
2436
|
if (queryTokens.some((t) => valueLower.includes(t))) reasons.push(name);
|
|
2174
2437
|
}
|
|
2175
2438
|
if (reasons.length === 0) reasons.push("fuzzy");
|
|
2176
|
-
entry
|
|
2439
|
+
entry.matchedFields = reasons;
|
|
2177
2440
|
}
|
|
2178
2441
|
}
|
|
2179
2442
|
if (options.maxTokens && options.maxTokens > 0) {
|
|
@@ -2327,7 +2590,19 @@ function extractEntities(content) {
|
|
|
2327
2590
|
seen.add(key);
|
|
2328
2591
|
switch (kind) {
|
|
2329
2592
|
case "file":
|
|
2330
|
-
|
|
2593
|
+
const isLikelyFilePath = (() => {
|
|
2594
|
+
if (entity.includes("/") || entity.includes("\\")) return true;
|
|
2595
|
+
if (entity.startsWith("./") || entity.startsWith("../")) return true;
|
|
2596
|
+
if (/^(?:process\.env|config\.|options\.|params\.|props\.|this\.|self\.|module\.|exports\.|require\.|import\.)/i.test(entity)) return false;
|
|
2597
|
+
const dotCount = (entity.match(/\./g) || []).length;
|
|
2598
|
+
if (dotCount >= 2) return false;
|
|
2599
|
+
const ext = entity.split(".").pop()?.toLowerCase() ?? "";
|
|
2600
|
+
const FILE_EXTENSIONS = /* @__PURE__ */ new Set(["ts", "js", "tsx", "jsx", "mts", "mjs", "cjs", "json", "yaml", "yml", "toml", "xml", "csv", "py", "rb", "go", "rs", "java", "kt", "swift", "c", "cpp", "h", "html", "css", "scss", "less", "vue", "svelte", "sh", "bash", "zsh", "ps1", "cmd", "bat", "md", "txt", "rst", "log", "sql", "graphql", "prisma", "env", "gitignore", "dockerignore", "dockerfile", "lock", "config"]);
|
|
2601
|
+
return FILE_EXTENSIONS.has(ext);
|
|
2602
|
+
})();
|
|
2603
|
+
if (isLikelyFilePath) {
|
|
2604
|
+
result.files.push(entity);
|
|
2605
|
+
}
|
|
2331
2606
|
break;
|
|
2332
2607
|
case "module":
|
|
2333
2608
|
result.modules.push(entity);
|
|
@@ -2462,11 +2737,15 @@ async function storeObservation(input) {
|
|
|
2462
2737
|
revisionCount: 1,
|
|
2463
2738
|
sessionId: input.sessionId,
|
|
2464
2739
|
status: "active",
|
|
2465
|
-
progress: input.progress
|
|
2740
|
+
progress: input.progress,
|
|
2741
|
+
source: input.source,
|
|
2742
|
+
commitHash: input.commitHash,
|
|
2743
|
+
relatedCommits: input.relatedCommits,
|
|
2744
|
+
relatedEntities: input.relatedEntities
|
|
2466
2745
|
};
|
|
2467
2746
|
observations.push(observation);
|
|
2468
2747
|
const doc = {
|
|
2469
|
-
id:
|
|
2748
|
+
id: makeOramaObservationId(input.projectId, id),
|
|
2470
2749
|
observationId: id,
|
|
2471
2750
|
entityName: input.entityName,
|
|
2472
2751
|
type: input.type,
|
|
@@ -2480,7 +2759,8 @@ async function storeObservation(input) {
|
|
|
2480
2759
|
projectId: input.projectId,
|
|
2481
2760
|
accessCount: 0,
|
|
2482
2761
|
lastAccessedAt: "",
|
|
2483
|
-
status: "active"
|
|
2762
|
+
status: "active",
|
|
2763
|
+
source: input.source ?? "agent"
|
|
2484
2764
|
};
|
|
2485
2765
|
await insertObservation(doc);
|
|
2486
2766
|
if (projectDir) {
|
|
@@ -2503,7 +2783,7 @@ async function storeObservation(input) {
|
|
|
2503
2783
|
if (embedding) {
|
|
2504
2784
|
try {
|
|
2505
2785
|
const { removeObservation: removeObs } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
|
|
2506
|
-
await removeObs(
|
|
2786
|
+
await removeObs(makeOramaObservationId(input.projectId, id));
|
|
2507
2787
|
await insertObservation(Object.assign({}, doc, { embedding }));
|
|
2508
2788
|
} catch {
|
|
2509
2789
|
}
|
|
@@ -2539,7 +2819,7 @@ async function upsertObservation(existing, input, now) {
|
|
|
2539
2819
|
if (input.sessionId) existing.sessionId = input.sessionId;
|
|
2540
2820
|
if (input.progress) existing.progress = input.progress;
|
|
2541
2821
|
const doc = {
|
|
2542
|
-
id:
|
|
2822
|
+
id: makeOramaObservationId(existing.projectId, existing.id),
|
|
2543
2823
|
observationId: existing.id,
|
|
2544
2824
|
entityName: existing.entityName,
|
|
2545
2825
|
type: existing.type,
|
|
@@ -2553,11 +2833,12 @@ async function upsertObservation(existing, input, now) {
|
|
|
2553
2833
|
projectId: existing.projectId,
|
|
2554
2834
|
accessCount: 0,
|
|
2555
2835
|
lastAccessedAt: "",
|
|
2556
|
-
status: "active"
|
|
2836
|
+
status: "active",
|
|
2837
|
+
source: existing.source ?? "agent"
|
|
2557
2838
|
};
|
|
2558
2839
|
try {
|
|
2559
2840
|
const { removeObservation: removeObservation3 } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
|
|
2560
|
-
await removeObservation3(
|
|
2841
|
+
await removeObservation3(makeOramaObservationId(existing.projectId, existing.id));
|
|
2561
2842
|
} catch {
|
|
2562
2843
|
}
|
|
2563
2844
|
await insertObservation(doc);
|
|
@@ -2580,7 +2861,7 @@ async function upsertObservation(existing, input, now) {
|
|
|
2580
2861
|
if (embedding) {
|
|
2581
2862
|
try {
|
|
2582
2863
|
const { removeObservation: removeObs } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
|
|
2583
|
-
await removeObs(
|
|
2864
|
+
await removeObs(makeOramaObservationId(existing.projectId, obsId));
|
|
2584
2865
|
await insertObservation(Object.assign({}, doc, { embedding }));
|
|
2585
2866
|
} catch {
|
|
2586
2867
|
}
|
|
@@ -2611,9 +2892,9 @@ async function resolveObservations(ids, status = "resolved") {
|
|
|
2611
2892
|
resolved.push(id);
|
|
2612
2893
|
try {
|
|
2613
2894
|
const { removeObservation: removeObs } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
|
|
2614
|
-
await removeObs(
|
|
2895
|
+
await removeObs(makeOramaObservationId(obs.projectId, id));
|
|
2615
2896
|
const doc = {
|
|
2616
|
-
id:
|
|
2897
|
+
id: makeOramaObservationId(obs.projectId, obs.id),
|
|
2617
2898
|
observationId: obs.id,
|
|
2618
2899
|
entityName: obs.entityName,
|
|
2619
2900
|
type: obs.type,
|
|
@@ -2627,7 +2908,8 @@ async function resolveObservations(ids, status = "resolved") {
|
|
|
2627
2908
|
projectId: obs.projectId,
|
|
2628
2909
|
accessCount: 0,
|
|
2629
2910
|
lastAccessedAt: "",
|
|
2630
|
-
status
|
|
2911
|
+
status,
|
|
2912
|
+
source: obs.source ?? "agent"
|
|
2631
2913
|
};
|
|
2632
2914
|
await insertObservation(doc);
|
|
2633
2915
|
const obsId = obs.id;
|
|
@@ -2712,7 +2994,7 @@ async function reindexObservations() {
|
|
|
2712
2994
|
const obs = observations[i];
|
|
2713
2995
|
try {
|
|
2714
2996
|
const embedding = embeddings[i] ?? null;
|
|
2715
|
-
const docId =
|
|
2997
|
+
const docId = makeOramaObservationId(obs.projectId, obs.id);
|
|
2716
2998
|
const doc = {
|
|
2717
2999
|
id: docId,
|
|
2718
3000
|
observationId: obs.id,
|
|
@@ -2729,6 +3011,7 @@ async function reindexObservations() {
|
|
|
2729
3011
|
accessCount: 0,
|
|
2730
3012
|
lastAccessedAt: "",
|
|
2731
3013
|
status: obs.status ?? "active",
|
|
3014
|
+
source: obs.source ?? "agent",
|
|
2732
3015
|
...embedding ? { embedding } : {}
|
|
2733
3016
|
};
|
|
2734
3017
|
await insertObservation(doc);
|
|
@@ -2756,6 +3039,126 @@ var init_observations = __esm({
|
|
|
2756
3039
|
}
|
|
2757
3040
|
});
|
|
2758
3041
|
|
|
3042
|
+
// src/project/detector.ts
|
|
3043
|
+
var detector_exports = {};
|
|
3044
|
+
__export(detector_exports, {
|
|
3045
|
+
detectProject: () => detectProject,
|
|
3046
|
+
findGitInSubdirs: () => findGitInSubdirs,
|
|
3047
|
+
isSystemDirectory: () => isSystemDirectory
|
|
3048
|
+
});
|
|
3049
|
+
import { execSync } from "child_process";
|
|
3050
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync, statSync } from "fs";
|
|
3051
|
+
import path5 from "path";
|
|
3052
|
+
function detectProject(cwd) {
|
|
3053
|
+
const basePath = cwd ?? process.cwd();
|
|
3054
|
+
const gitRoot = getGitRoot(basePath);
|
|
3055
|
+
if (!gitRoot) {
|
|
3056
|
+
return null;
|
|
3057
|
+
}
|
|
3058
|
+
const gitRemote = getGitRemote(gitRoot);
|
|
3059
|
+
if (gitRemote) {
|
|
3060
|
+
const id2 = normalizeGitRemote(gitRemote);
|
|
3061
|
+
const name2 = id2.split("/").pop() ?? path5.basename(gitRoot);
|
|
3062
|
+
return { id: id2, name: name2, gitRemote, rootPath: gitRoot };
|
|
3063
|
+
}
|
|
3064
|
+
const name = path5.basename(gitRoot);
|
|
3065
|
+
const id = `local/${name}`;
|
|
3066
|
+
return { id, name, rootPath: gitRoot };
|
|
3067
|
+
}
|
|
3068
|
+
function getGitRoot(cwd) {
|
|
3069
|
+
let dir = path5.resolve(cwd);
|
|
3070
|
+
const fsRoot = path5.parse(dir).root;
|
|
3071
|
+
while (dir !== fsRoot) {
|
|
3072
|
+
if (existsSync4(path5.join(dir, ".git"))) return dir;
|
|
3073
|
+
dir = path5.dirname(dir);
|
|
3074
|
+
}
|
|
3075
|
+
try {
|
|
3076
|
+
const root = execSync("git -c safe.directory=* rev-parse --show-toplevel", {
|
|
3077
|
+
cwd,
|
|
3078
|
+
encoding: "utf-8",
|
|
3079
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3080
|
+
timeout: 5e3
|
|
3081
|
+
}).trim();
|
|
3082
|
+
return root || null;
|
|
3083
|
+
} catch {
|
|
3084
|
+
return null;
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
function getGitRemote(cwd) {
|
|
3088
|
+
const fsRemote = readGitConfigRemote(cwd);
|
|
3089
|
+
if (fsRemote) return fsRemote;
|
|
3090
|
+
try {
|
|
3091
|
+
const remote = execSync("git -c safe.directory=* remote get-url origin", {
|
|
3092
|
+
cwd,
|
|
3093
|
+
encoding: "utf-8",
|
|
3094
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3095
|
+
timeout: 5e3
|
|
3096
|
+
}).trim();
|
|
3097
|
+
return remote || null;
|
|
3098
|
+
} catch {
|
|
3099
|
+
return null;
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
function readGitConfigRemote(cwd) {
|
|
3103
|
+
try {
|
|
3104
|
+
const configPath = path5.join(cwd, ".git", "config");
|
|
3105
|
+
if (!existsSync4(configPath)) return null;
|
|
3106
|
+
const content = readFileSync4(configPath, "utf-8");
|
|
3107
|
+
const remoteMatch = content.match(/\[remote\s+"origin"\]([\s\S]*?)(?=\n\[|$)/);
|
|
3108
|
+
if (!remoteMatch) return null;
|
|
3109
|
+
const urlMatch = remoteMatch[1].match(/^\s*url\s*=\s*(.+)$/m);
|
|
3110
|
+
return urlMatch ? urlMatch[1].trim() : null;
|
|
3111
|
+
} catch {
|
|
3112
|
+
return null;
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
function isSystemDirectory(dir) {
|
|
3116
|
+
const lower = dir.toLowerCase().replace(/\\/g, "/");
|
|
3117
|
+
return lower.includes("/windows/") || lower.endsWith("/windows") || lower.includes("/program files") || lower.includes("/appdata/") || // IDE installation directories
|
|
3118
|
+
/\/(windsurf|cursor|code|vscode)\/\1/i.test(lower) || /\/windsurf\b/i.test(lower) && !lower.includes(".windsurf") || // Node / npm internal paths
|
|
3119
|
+
lower.includes("/node_modules/") || lower.includes("/nvm") || // System root
|
|
3120
|
+
/^[a-z]:\/$/i.test(lower);
|
|
3121
|
+
}
|
|
3122
|
+
function findGitInSubdirs(dir) {
|
|
3123
|
+
try {
|
|
3124
|
+
const resolved = path5.resolve(dir);
|
|
3125
|
+
const entries = readdirSync(resolved);
|
|
3126
|
+
for (const entry of entries) {
|
|
3127
|
+
if (entry.startsWith(".")) continue;
|
|
3128
|
+
const fullPath = path5.join(resolved, entry);
|
|
3129
|
+
try {
|
|
3130
|
+
if (statSync(fullPath).isDirectory() && existsSync4(path5.join(fullPath, ".git"))) {
|
|
3131
|
+
return fullPath;
|
|
3132
|
+
}
|
|
3133
|
+
} catch {
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
} catch {
|
|
3137
|
+
}
|
|
3138
|
+
return null;
|
|
3139
|
+
}
|
|
3140
|
+
function normalizeGitRemote(remote) {
|
|
3141
|
+
let normalized = remote;
|
|
3142
|
+
normalized = normalized.replace(/\.git$/, "");
|
|
3143
|
+
const sshMatch = normalized.match(/^[\w-]+@[\w.-]+:(.+)$/);
|
|
3144
|
+
if (sshMatch) {
|
|
3145
|
+
return sshMatch[1];
|
|
3146
|
+
}
|
|
3147
|
+
try {
|
|
3148
|
+
const url = new URL(normalized);
|
|
3149
|
+
return url.pathname.replace(/^\//, "");
|
|
3150
|
+
} catch {
|
|
3151
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
3152
|
+
return segments.slice(-2).join("/");
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
var init_detector = __esm({
|
|
3156
|
+
"src/project/detector.ts"() {
|
|
3157
|
+
"use strict";
|
|
3158
|
+
init_esm_shims();
|
|
3159
|
+
}
|
|
3160
|
+
});
|
|
3161
|
+
|
|
2759
3162
|
// src/llm/memory-manager.ts
|
|
2760
3163
|
var memory_manager_exports = {};
|
|
2761
3164
|
__export(memory_manager_exports, {
|
|
@@ -2883,59 +3286,994 @@ function mergeFacts(oldFactsStr, newFactsStr) {
|
|
|
2883
3286
|
merged.push(f);
|
|
2884
3287
|
}
|
|
2885
3288
|
}
|
|
2886
|
-
return merged;
|
|
3289
|
+
return merged;
|
|
3290
|
+
}
|
|
3291
|
+
async function deduplicateMemory(newMemory, existingMemories) {
|
|
3292
|
+
if (!isLLMEnabled()) return null;
|
|
3293
|
+
if (existingMemories.length === 0) return { action: "ADD", reason: "No existing memories", usedLLM: false };
|
|
3294
|
+
const asExisting = existingMemories.map((m) => ({
|
|
3295
|
+
...m,
|
|
3296
|
+
score: 0.8
|
|
3297
|
+
// Batch dedup assumes high similarity (pre-filtered)
|
|
3298
|
+
}));
|
|
3299
|
+
return compactOnWrite(newMemory, asExisting);
|
|
3300
|
+
}
|
|
3301
|
+
var COMPACT_ON_WRITE_PROMPT, SIMILARITY_HIGH, SIMILARITY_MEDIUM;
|
|
3302
|
+
var init_memory_manager = __esm({
|
|
3303
|
+
"src/llm/memory-manager.ts"() {
|
|
3304
|
+
"use strict";
|
|
3305
|
+
init_esm_shims();
|
|
3306
|
+
init_provider2();
|
|
3307
|
+
COMPACT_ON_WRITE_PROMPT = `You are a smart coding memory manager. You control the memory of a cross-IDE coding assistant.
|
|
3308
|
+
|
|
3309
|
+
You receive a NEW MEMORY to store and a list of EXISTING similar memories. Your job:
|
|
3310
|
+
1. Extract the key facts from the new memory
|
|
3311
|
+
2. Compare with existing memories
|
|
3312
|
+
3. Decide the best action
|
|
3313
|
+
|
|
3314
|
+
Actions:
|
|
3315
|
+
- ADD: New memory contains unique information. Store it as-is.
|
|
3316
|
+
- UPDATE: New memory supersedes or improves an existing one. Merge them into a single, comprehensive memory.
|
|
3317
|
+
- DELETE: An existing memory is outdated/contradicted by the new one. Remove it.
|
|
3318
|
+
- NONE: New memory is redundant. Existing memories already cover this. Skip storing.
|
|
3319
|
+
|
|
3320
|
+
Decision rules:
|
|
3321
|
+
- Same topic updated (e.g., "MySQL \u2192 PostgreSQL"): UPDATE the old memory with merged content
|
|
3322
|
+
- Bug fixed that was reported as open: UPDATE the bug report to include the fix
|
|
3323
|
+
- Task completed that was tracked as in-progress: UPDATE to mark completed
|
|
3324
|
+
- Minor variation of existing memory: NONE (skip)
|
|
3325
|
+
- Completely new topic: ADD
|
|
3326
|
+
- Old info directly contradicted: DELETE the old one
|
|
3327
|
+
- Prefer UPDATE over ADD \u2014 keep memory count low, merge information
|
|
3328
|
+
|
|
3329
|
+
For UPDATE: write a merged narrative that combines the best of both old and new, preserving all important details. Also merge the facts lists, removing duplicates.
|
|
3330
|
+
|
|
3331
|
+
Respond in JSON only:
|
|
3332
|
+
{
|
|
3333
|
+
"action": "ADD" | "UPDATE" | "DELETE" | "NONE",
|
|
3334
|
+
"targetId": null or existing_memory_id_number,
|
|
3335
|
+
"reason": "brief explanation of decision",
|
|
3336
|
+
"mergedNarrative": "merged narrative text (required for UPDATE, null otherwise)",
|
|
3337
|
+
"mergedFacts": ["merged fact 1", "merged fact 2"] or null,
|
|
3338
|
+
"extractedFacts": ["fact extracted from new content 1", "fact 2"]
|
|
3339
|
+
}`;
|
|
3340
|
+
SIMILARITY_HIGH = 0.75;
|
|
3341
|
+
SIMILARITY_MEDIUM = 0.5;
|
|
3342
|
+
}
|
|
3343
|
+
});
|
|
3344
|
+
|
|
3345
|
+
// src/memory/formation/extract.ts
|
|
3346
|
+
function extractFacts(narrative, existingFacts) {
|
|
3347
|
+
const existingLower = new Set(existingFacts.map((f) => f.toLowerCase().trim()));
|
|
3348
|
+
const extracted = [];
|
|
3349
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3350
|
+
for (const { pattern, format } of FACT_PATTERNS) {
|
|
3351
|
+
pattern.lastIndex = 0;
|
|
3352
|
+
let match;
|
|
3353
|
+
while ((match = pattern.exec(narrative)) !== null) {
|
|
3354
|
+
const fact = format(match);
|
|
3355
|
+
const normalized = fact.toLowerCase().trim();
|
|
3356
|
+
if (existingLower.has(normalized) || seen.has(normalized)) continue;
|
|
3357
|
+
if (fact.length < 5 || fact.length > 120) continue;
|
|
3358
|
+
seen.add(normalized);
|
|
3359
|
+
extracted.push(fact);
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
return extracted.slice(0, 10);
|
|
3363
|
+
}
|
|
3364
|
+
function improveTitle(title, narrative) {
|
|
3365
|
+
const isGeneric = GENERIC_TITLE_PATTERNS.some((p) => p.test(title));
|
|
3366
|
+
if (!isGeneric) return { title, improved: false };
|
|
3367
|
+
const sentences = narrative.replace(/```[\s\S]*?```/g, "").split(/[.。!!?\n]/).map((s) => s.trim()).filter((s) => s.length >= 15);
|
|
3368
|
+
if (sentences.length > 0) {
|
|
3369
|
+
return { title: sentences[0].slice(0, 60), improved: true };
|
|
3370
|
+
}
|
|
3371
|
+
return { title, improved: false };
|
|
3372
|
+
}
|
|
3373
|
+
function resolveEntity(entityName, existingEntities) {
|
|
3374
|
+
if (existingEntities.length === 0) return { entityName, resolved: false };
|
|
3375
|
+
const lower = entityName.toLowerCase().replace(/[-_]/g, "");
|
|
3376
|
+
for (const existing of existingEntities) {
|
|
3377
|
+
const existingLower = existing.toLowerCase().replace(/[-_]/g, "");
|
|
3378
|
+
if (lower === existingLower) {
|
|
3379
|
+
return { entityName: existing, resolved: existing !== entityName };
|
|
3380
|
+
}
|
|
3381
|
+
if (lower.length >= 3 && existingLower.length >= 3) {
|
|
3382
|
+
if (existingLower.includes(lower) || lower.includes(existingLower)) {
|
|
3383
|
+
const canonical = existing.length >= entityName.length ? existing : entityName;
|
|
3384
|
+
return { entityName: canonical, resolved: canonical !== entityName };
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
return { entityName, resolved: false };
|
|
3389
|
+
}
|
|
3390
|
+
function verifyType(declaredType, narrative, title) {
|
|
3391
|
+
const content = `${title} ${narrative}`;
|
|
3392
|
+
const scores = [];
|
|
3393
|
+
for (const { type, patterns } of TYPE_SIGNALS) {
|
|
3394
|
+
let score = 0;
|
|
3395
|
+
for (const p of patterns) {
|
|
3396
|
+
const regex = new RegExp(p.source, p.flags.includes("g") ? p.flags : p.flags + "g");
|
|
3397
|
+
const matches = [...content.matchAll(regex)];
|
|
3398
|
+
score += matches.length;
|
|
3399
|
+
}
|
|
3400
|
+
if (score > 0) scores.push({ type, score });
|
|
3401
|
+
}
|
|
3402
|
+
if (scores.length === 0) return { type: declaredType, corrected: false };
|
|
3403
|
+
scores.sort((a, b) => b.score - a.score);
|
|
3404
|
+
const best = scores[0];
|
|
3405
|
+
if (best.type !== declaredType && best.score >= 2) {
|
|
3406
|
+
const declaredScore = scores.find((s) => s.type === declaredType)?.score ?? 0;
|
|
3407
|
+
if (declaredScore === 0) {
|
|
3408
|
+
return { type: best.type, corrected: true };
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
return { type: declaredType, corrected: false };
|
|
3412
|
+
}
|
|
3413
|
+
async function extractFactsWithLLM(narrative, title, existingFacts) {
|
|
3414
|
+
try {
|
|
3415
|
+
const { callLLM: callLLM2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
|
|
3416
|
+
const input = `Title: ${title}
|
|
3417
|
+
Content: ${narrative}${existingFacts.length > 0 ? `
|
|
3418
|
+
Already known facts (don't repeat): ${existingFacts.join("; ")}` : ""}`;
|
|
3419
|
+
const response = await callLLM2(LLM_EXTRACT_PROMPT, input);
|
|
3420
|
+
const text = response.content.trim();
|
|
3421
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
3422
|
+
if (!jsonMatch) return [];
|
|
3423
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
3424
|
+
const facts = parsed.facts;
|
|
3425
|
+
if (!Array.isArray(facts)) return [];
|
|
3426
|
+
const existingLower = new Set(existingFacts.map((f) => f.toLowerCase().trim()));
|
|
3427
|
+
return facts.filter((f) => typeof f === "string" && f.length >= 5).filter((f) => !existingLower.has(f.toLowerCase().trim())).slice(0, 10);
|
|
3428
|
+
} catch {
|
|
3429
|
+
return [];
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
async function runExtract(input, existingEntities, useLLM = false) {
|
|
3433
|
+
const callerFacts = input.facts ?? [];
|
|
3434
|
+
let extractedFacts;
|
|
3435
|
+
if (useLLM) {
|
|
3436
|
+
extractedFacts = await extractFactsWithLLM(input.narrative, input.title, callerFacts);
|
|
3437
|
+
if (extractedFacts.length === 0) {
|
|
3438
|
+
extractedFacts = extractFacts(input.narrative, callerFacts);
|
|
3439
|
+
}
|
|
3440
|
+
} else {
|
|
3441
|
+
extractedFacts = extractFacts(input.narrative, callerFacts);
|
|
3442
|
+
}
|
|
3443
|
+
const allFacts = [...callerFacts, ...extractedFacts];
|
|
3444
|
+
const { title, improved: titleImproved } = improveTitle(input.title, input.narrative);
|
|
3445
|
+
const { entityName, resolved: entityResolved } = resolveEntity(
|
|
3446
|
+
input.entityName,
|
|
3447
|
+
existingEntities
|
|
3448
|
+
);
|
|
3449
|
+
const { type, corrected: typeCorrected } = verifyType(
|
|
3450
|
+
input.type,
|
|
3451
|
+
input.narrative,
|
|
3452
|
+
input.title
|
|
3453
|
+
);
|
|
3454
|
+
return {
|
|
3455
|
+
title,
|
|
3456
|
+
titleImproved,
|
|
3457
|
+
narrative: input.narrative,
|
|
3458
|
+
facts: allFacts,
|
|
3459
|
+
extractedFacts,
|
|
3460
|
+
entityName,
|
|
3461
|
+
entityResolved,
|
|
3462
|
+
type,
|
|
3463
|
+
typeCorrected
|
|
3464
|
+
};
|
|
3465
|
+
}
|
|
3466
|
+
var FACT_PATTERNS, GENERIC_TITLE_PATTERNS, TYPE_SIGNALS, LLM_EXTRACT_PROMPT;
|
|
3467
|
+
var init_extract = __esm({
|
|
3468
|
+
"src/memory/formation/extract.ts"() {
|
|
3469
|
+
"use strict";
|
|
3470
|
+
init_esm_shims();
|
|
3471
|
+
FACT_PATTERNS = [
|
|
3472
|
+
// Key: Value pairs (e.g., "Port: 3000", "Timeout = 60s")
|
|
3473
|
+
{
|
|
3474
|
+
pattern: /\b([A-Z][a-zA-Z_-]{2,30})\s*[:=]\s*([^\n,;]{2,60})/g,
|
|
3475
|
+
format: (m) => `${m[1]}: ${m[2].trim()}`
|
|
3476
|
+
},
|
|
3477
|
+
// Arrow notation (e.g., "MySQL → PostgreSQL", "v1.0 → v2.0")
|
|
3478
|
+
{
|
|
3479
|
+
pattern: /\b(\S{2,30})\s*[→➜\->]+\s*(\S{2,30})/g,
|
|
3480
|
+
format: (m) => `${m[1]} \u2192 ${m[2]}`
|
|
3481
|
+
},
|
|
3482
|
+
// Version numbers (e.g., "v1.2.3", "version 2.0")
|
|
3483
|
+
{
|
|
3484
|
+
pattern: /\b(?:v(?:ersion)?\s*)(\d+\.\d+(?:\.\d+)?(?:-[\w.]+)?)\b/gi,
|
|
3485
|
+
format: (m) => `Version: ${m[1]}`
|
|
3486
|
+
},
|
|
3487
|
+
// Error messages (e.g., "Error: ...", "ERR_...")
|
|
3488
|
+
{
|
|
3489
|
+
pattern: /\b(?:Error|ERR|ENOENT|ECONNREFUSED|TypeError|RangeError|SyntaxError|ReferenceError)[:\s]+([^\n]{5,80})/gi,
|
|
3490
|
+
format: (m) => `Error: ${m[1].trim()}`
|
|
3491
|
+
},
|
|
3492
|
+
// Port numbers in context
|
|
3493
|
+
{
|
|
3494
|
+
pattern: /\b(?:port|PORT)\s*[:=]?\s*(\d{2,5})\b/gi,
|
|
3495
|
+
format: (m) => `Port: ${m[1]}`
|
|
3496
|
+
},
|
|
3497
|
+
// Environment variables
|
|
3498
|
+
{
|
|
3499
|
+
pattern: /\b([A-Z][A-Z0-9_]{3,30})\s*=\s*(\S{1,60})/g,
|
|
3500
|
+
format: (m) => `${m[1]}=${m[2]}`
|
|
3501
|
+
},
|
|
3502
|
+
// npm/package versions (e.g., "react@18.2.0")
|
|
3503
|
+
{
|
|
3504
|
+
pattern: /\b([@a-z][\w./-]+)@(\d+\.\d+\.\d+(?:-[\w.]+)?)\b/g,
|
|
3505
|
+
format: (m) => `${m[1]}@${m[2]}`
|
|
3506
|
+
}
|
|
3507
|
+
];
|
|
3508
|
+
GENERIC_TITLE_PATTERNS = [
|
|
3509
|
+
/^Updated \S+\.\w+$/i,
|
|
3510
|
+
/^Created \S+\.\w+$/i,
|
|
3511
|
+
/^Deleted \S+\.\w+$/i,
|
|
3512
|
+
/^Modified \S+\.\w+$/i,
|
|
3513
|
+
/^Changed \S+\.\w+$/i,
|
|
3514
|
+
/^Session activity/i,
|
|
3515
|
+
/^Activity \(/i,
|
|
3516
|
+
/^Used \w+$/i,
|
|
3517
|
+
/^Ran: /i
|
|
3518
|
+
];
|
|
3519
|
+
TYPE_SIGNALS = [
|
|
3520
|
+
{
|
|
3521
|
+
type: "problem-solution",
|
|
3522
|
+
patterns: [
|
|
3523
|
+
/\b(fix|fixed|bug|error|issue|crash|broken|resolved|workaround|patch)\b/i,
|
|
3524
|
+
/\b(修复|修正|解决|报错|崩溃|异常)\b/
|
|
3525
|
+
]
|
|
3526
|
+
},
|
|
3527
|
+
{
|
|
3528
|
+
type: "gotcha",
|
|
3529
|
+
patterns: [
|
|
3530
|
+
/\b(gotcha|pitfall|trap|careful|warning|caveat|footgun|unexpected|beware)\b/i,
|
|
3531
|
+
/\b(坑|陷阱|注意|小心|踩坑)\b/
|
|
3532
|
+
]
|
|
3533
|
+
},
|
|
3534
|
+
{
|
|
3535
|
+
type: "decision",
|
|
3536
|
+
patterns: [
|
|
3537
|
+
/\b(decided|chose|chosen|selected|adopted|rejected|evaluated|compared)\b/i,
|
|
3538
|
+
/\b(决定|选择|采用|弃用|对比|评估)\b/
|
|
3539
|
+
]
|
|
3540
|
+
},
|
|
3541
|
+
{
|
|
3542
|
+
type: "what-changed",
|
|
3543
|
+
patterns: [
|
|
3544
|
+
/\b(changed|migrated|upgraded|refactored|replaced|renamed|moved|removed|added)\b/i,
|
|
3545
|
+
/\b(改|迁移|升级|重构|替换|重命名|删除|新增)\b/
|
|
3546
|
+
]
|
|
3547
|
+
},
|
|
3548
|
+
{
|
|
3549
|
+
type: "how-it-works",
|
|
3550
|
+
patterns: [
|
|
3551
|
+
/\b(works by|architecture|mechanism|pipeline|flow|under the hood|internally)\b/i,
|
|
3552
|
+
/\b(原理|机制|流程|架构|内部)\b/
|
|
3553
|
+
]
|
|
3554
|
+
},
|
|
3555
|
+
{
|
|
3556
|
+
type: "trade-off",
|
|
3557
|
+
patterns: [
|
|
3558
|
+
/\b(trade.?off|compromise|downside|cost|benefit|pro|con|versus|vs)\b/i,
|
|
3559
|
+
/\b(权衡|折中|代价|收益|优缺点)\b/
|
|
3560
|
+
]
|
|
3561
|
+
}
|
|
3562
|
+
];
|
|
3563
|
+
LLM_EXTRACT_PROMPT = `You are a Software Engineering Knowledge Extractor.
|
|
3564
|
+
Extract structured facts from the given development context.
|
|
3565
|
+
|
|
3566
|
+
Focus on:
|
|
3567
|
+
1. Technical decisions and their reasoning
|
|
3568
|
+
2. Bug root causes and fixes
|
|
3569
|
+
3. Configuration values (ports, versions, env vars)
|
|
3570
|
+
4. Architecture patterns and constraints
|
|
3571
|
+
5. Gotchas, pitfalls, and workarounds
|
|
3572
|
+
6. File paths and their roles
|
|
3573
|
+
|
|
3574
|
+
Rules:
|
|
3575
|
+
- Return ONLY a JSON object with a "facts" key containing an array of strings
|
|
3576
|
+
- Each fact should be a concise, self-contained statement
|
|
3577
|
+
- Include specific values (versions, ports, paths) when present
|
|
3578
|
+
- Detect the language of the input and record facts in the same language
|
|
3579
|
+
- If no meaningful facts exist, return {"facts": []}
|
|
3580
|
+
- Do NOT include trivial information (file read, directory listing)
|
|
3581
|
+
- Maximum 10 facts
|
|
3582
|
+
|
|
3583
|
+
Example:
|
|
3584
|
+
Input: "Fixed Redis connection leak. The pool wasn't being closed on shutdown. Added defer pool.Close() in main.go. Port 6379."
|
|
3585
|
+
Output: {"facts": ["Redis connection leak caused by pool not closed on shutdown", "Fix: added defer pool.Close() in main.go", "Redis port: 6379"]}`;
|
|
3586
|
+
}
|
|
3587
|
+
});
|
|
3588
|
+
|
|
3589
|
+
// src/memory/formation/resolve.ts
|
|
3590
|
+
function wordOverlap(a, b) {
|
|
3591
|
+
const wordsA = new Set(a.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
3592
|
+
const wordsB = new Set(b.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
3593
|
+
if (wordsA.size === 0 || wordsB.size === 0) return 0;
|
|
3594
|
+
let intersection = 0;
|
|
3595
|
+
for (const w of wordsA) {
|
|
3596
|
+
if (wordsB.has(w)) intersection++;
|
|
3597
|
+
}
|
|
3598
|
+
return intersection / Math.max(wordsA.size, wordsB.size);
|
|
3599
|
+
}
|
|
3600
|
+
function entitiesMatch(a, b) {
|
|
3601
|
+
const na = a.toLowerCase().replace(/[-_]/g, "");
|
|
3602
|
+
const nb = b.toLowerCase().replace(/[-_]/g, "");
|
|
3603
|
+
if (na === nb) return true;
|
|
3604
|
+
if (na.length >= 3 && nb.length >= 3) {
|
|
3605
|
+
if (na.includes(nb) || nb.includes(na)) return true;
|
|
3606
|
+
}
|
|
3607
|
+
return false;
|
|
3608
|
+
}
|
|
3609
|
+
function hasContradiction(oldText, newText) {
|
|
3610
|
+
const negationPatterns = [
|
|
3611
|
+
/\bnot\s+(\w+)/gi,
|
|
3612
|
+
/\bno longer\b/i,
|
|
3613
|
+
/\binstead of\b/i,
|
|
3614
|
+
/\breplaced\b.*\bwith\b/i,
|
|
3615
|
+
/\bremoved\b/i,
|
|
3616
|
+
/\bdeprecated\b/i,
|
|
3617
|
+
/\bobsolete\b/i,
|
|
3618
|
+
/不再/,
|
|
3619
|
+
/已弃用/,
|
|
3620
|
+
/替换为/,
|
|
3621
|
+
/改为/
|
|
3622
|
+
];
|
|
3623
|
+
return negationPatterns.some((p) => p.test(newText));
|
|
3624
|
+
}
|
|
3625
|
+
function mergeNarratives(oldNarrative, newNarrative) {
|
|
3626
|
+
if (newNarrative.length > oldNarrative.length * 1.5) return newNarrative;
|
|
3627
|
+
if (oldNarrative.length > newNarrative.length * 1.5) return oldNarrative;
|
|
3628
|
+
return `${newNarrative}
|
|
3629
|
+
|
|
3630
|
+
[Previous context]: ${oldNarrative}`;
|
|
3631
|
+
}
|
|
3632
|
+
function mergeFacts2(oldFacts, newFacts) {
|
|
3633
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3634
|
+
const merged = [];
|
|
3635
|
+
for (const f of newFacts) {
|
|
3636
|
+
const norm = f.toLowerCase().trim();
|
|
3637
|
+
if (!seen.has(norm) && f.trim().length > 0) {
|
|
3638
|
+
seen.add(norm);
|
|
3639
|
+
merged.push(f);
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
for (const f of oldFacts) {
|
|
3643
|
+
const norm = f.toLowerCase().trim();
|
|
3644
|
+
if (!seen.has(norm) && f.trim().length > 0) {
|
|
3645
|
+
seen.add(norm);
|
|
3646
|
+
merged.push(f);
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
return merged;
|
|
3650
|
+
}
|
|
3651
|
+
async function resolveWithLLM(extracted, hits, getObservation2) {
|
|
3652
|
+
try {
|
|
3653
|
+
const { callLLM: callLLM2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
|
|
3654
|
+
const existingMemories = hits.slice(0, 5).map((h, i) => ({
|
|
3655
|
+
id: h.observationId,
|
|
3656
|
+
index: i,
|
|
3657
|
+
title: h.title,
|
|
3658
|
+
content: h.narrative.substring(0, 300),
|
|
3659
|
+
facts: h.facts.substring(0, 200)
|
|
3660
|
+
}));
|
|
3661
|
+
const input = `NEW MEMORY:
|
|
3662
|
+
Title: ${extracted.title}
|
|
3663
|
+
Content: ${extracted.narrative.substring(0, 500)}
|
|
3664
|
+
Facts: ${extracted.facts.join("; ")}
|
|
3665
|
+
|
|
3666
|
+
EXISTING MEMORIES:
|
|
3667
|
+
${existingMemories.map((m) => `[ID:${m.id}] ${m.title} | ${m.content} | Facts: ${m.facts}`).join("\n")}`;
|
|
3668
|
+
const response = await callLLM2(LLM_RESOLVE_PROMPT, input);
|
|
3669
|
+
const text = response.content.trim();
|
|
3670
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
3671
|
+
if (!jsonMatch) return null;
|
|
3672
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
3673
|
+
const action = parsed.action?.toUpperCase();
|
|
3674
|
+
const targetId = parsed.targetId ? Number(parsed.targetId) : void 0;
|
|
3675
|
+
const reason = parsed.reason || "LLM decision";
|
|
3676
|
+
if (action === "NOOP") {
|
|
3677
|
+
return { action: "discard", targetId, reason: `LLM: ${reason}` };
|
|
3678
|
+
}
|
|
3679
|
+
if (action === "ADD") {
|
|
3680
|
+
return { action: "new", reason: `LLM: ${reason}` };
|
|
3681
|
+
}
|
|
3682
|
+
if (action === "UPDATE" && targetId) {
|
|
3683
|
+
const existing = getObservation2(targetId);
|
|
3684
|
+
const oldFacts = existing?.facts ?? [];
|
|
3685
|
+
return {
|
|
3686
|
+
action: "merge",
|
|
3687
|
+
targetId,
|
|
3688
|
+
reason: `LLM: ${reason}`,
|
|
3689
|
+
mergedNarrative: parsed.mergedText || mergeNarratives(existing?.narrative ?? "", extracted.narrative),
|
|
3690
|
+
mergedFacts: mergeFacts2(oldFacts, extracted.facts)
|
|
3691
|
+
};
|
|
3692
|
+
}
|
|
3693
|
+
if (action === "DELETE" && targetId) {
|
|
3694
|
+
const existing = getObservation2(targetId);
|
|
3695
|
+
const oldFacts = existing?.facts ?? [];
|
|
3696
|
+
return {
|
|
3697
|
+
action: "evolve",
|
|
3698
|
+
targetId,
|
|
3699
|
+
reason: `LLM: ${reason}`,
|
|
3700
|
+
mergedNarrative: extracted.narrative,
|
|
3701
|
+
mergedFacts: mergeFacts2(oldFacts, extracted.facts)
|
|
3702
|
+
};
|
|
3703
|
+
}
|
|
3704
|
+
return null;
|
|
3705
|
+
} catch {
|
|
3706
|
+
return null;
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
function scoreCandidate(extracted, candidate) {
|
|
3710
|
+
const entityMatch = entitiesMatch(extracted.entityName, candidate.entityName);
|
|
3711
|
+
const contentOverlap = wordOverlap(
|
|
3712
|
+
`${extracted.title} ${extracted.narrative}`,
|
|
3713
|
+
`${candidate.title} ${candidate.narrative}`
|
|
3714
|
+
);
|
|
3715
|
+
const score = candidate.score * 0.6 + (entityMatch ? 0.2 : 0) + contentOverlap * 0.2;
|
|
3716
|
+
const newLength = extracted.narrative.length + extracted.facts.join(" ").length;
|
|
3717
|
+
const oldLength = candidate.narrative.length + candidate.facts.length;
|
|
3718
|
+
const richer = newLength > oldLength * 1.15;
|
|
3719
|
+
const contradiction = hasContradiction(candidate.narrative, extracted.narrative);
|
|
3720
|
+
return { score, entityMatch, richer, contradiction };
|
|
3721
|
+
}
|
|
3722
|
+
async function runResolve(extracted, projectId, searchMemories, getObservation2, useLLM = false) {
|
|
3723
|
+
const query = `${extracted.title} ${extracted.narrative.substring(0, 200)}`;
|
|
3724
|
+
let hits;
|
|
3725
|
+
try {
|
|
3726
|
+
hits = await searchMemories(query, 5, projectId);
|
|
3727
|
+
} catch {
|
|
3728
|
+
return { action: "new", reason: "Search unavailable, defaulting to new" };
|
|
3729
|
+
}
|
|
3730
|
+
if (hits.length === 0) {
|
|
3731
|
+
return { action: "new", reason: "No similar existing memories found" };
|
|
3732
|
+
}
|
|
3733
|
+
if (useLLM) {
|
|
3734
|
+
const llmResult = await resolveWithLLM(extracted, hits, getObservation2);
|
|
3735
|
+
if (llmResult) return llmResult;
|
|
3736
|
+
}
|
|
3737
|
+
const scored = hits.map((hit) => ({
|
|
3738
|
+
hit,
|
|
3739
|
+
...scoreCandidate(extracted, hit)
|
|
3740
|
+
}));
|
|
3741
|
+
scored.sort((a, b) => b.score - a.score);
|
|
3742
|
+
const best = scored[0];
|
|
3743
|
+
if (best.hit.score >= SIMILARITY_DUPLICATE) {
|
|
3744
|
+
if (best.richer) {
|
|
3745
|
+
const existing = getObservation2(best.hit.observationId);
|
|
3746
|
+
const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
|
|
3747
|
+
return {
|
|
3748
|
+
action: "evolve",
|
|
3749
|
+
targetId: best.hit.observationId,
|
|
3750
|
+
reason: `Near-duplicate of #${best.hit.observationId} but richer content (score: ${best.score.toFixed(2)})`,
|
|
3751
|
+
mergedNarrative: mergeNarratives(best.hit.narrative, extracted.narrative),
|
|
3752
|
+
mergedFacts: mergeFacts2(oldFacts, extracted.facts)
|
|
3753
|
+
};
|
|
3754
|
+
}
|
|
3755
|
+
return {
|
|
3756
|
+
action: "discard",
|
|
3757
|
+
targetId: best.hit.observationId,
|
|
3758
|
+
reason: `Duplicate of #${best.hit.observationId} (score: ${best.score.toFixed(2)})`
|
|
3759
|
+
};
|
|
3760
|
+
}
|
|
3761
|
+
if (best.score >= SIMILARITY_HIGH2) {
|
|
3762
|
+
if (best.contradiction) {
|
|
3763
|
+
const existing = getObservation2(best.hit.observationId);
|
|
3764
|
+
const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
|
|
3765
|
+
return {
|
|
3766
|
+
action: "evolve",
|
|
3767
|
+
targetId: best.hit.observationId,
|
|
3768
|
+
reason: `Supersedes #${best.hit.observationId}: contradiction detected (score: ${best.score.toFixed(2)})`,
|
|
3769
|
+
mergedNarrative: extracted.narrative,
|
|
3770
|
+
mergedFacts: mergeFacts2(oldFacts, extracted.facts)
|
|
3771
|
+
};
|
|
3772
|
+
}
|
|
3773
|
+
if (best.richer) {
|
|
3774
|
+
const existing = getObservation2(best.hit.observationId);
|
|
3775
|
+
const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
|
|
3776
|
+
return {
|
|
3777
|
+
action: "merge",
|
|
3778
|
+
targetId: best.hit.observationId,
|
|
3779
|
+
reason: `Merging with #${best.hit.observationId}: same topic, new content is richer (score: ${best.score.toFixed(2)})`,
|
|
3780
|
+
mergedNarrative: mergeNarratives(best.hit.narrative, extracted.narrative),
|
|
3781
|
+
mergedFacts: mergeFacts2(oldFacts, extracted.facts)
|
|
3782
|
+
};
|
|
3783
|
+
}
|
|
3784
|
+
return {
|
|
3785
|
+
action: "discard",
|
|
3786
|
+
targetId: best.hit.observationId,
|
|
3787
|
+
reason: `Already covered by #${best.hit.observationId} (score: ${best.score.toFixed(2)})`
|
|
3788
|
+
};
|
|
3789
|
+
}
|
|
3790
|
+
if (best.score >= SIMILARITY_MEDIUM2 && best.entityMatch) {
|
|
3791
|
+
const existing = getObservation2(best.hit.observationId);
|
|
3792
|
+
const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
|
|
3793
|
+
const newFactCount = extracted.facts.length;
|
|
3794
|
+
const oldFactCount = oldFacts.length;
|
|
3795
|
+
if (newFactCount > oldFactCount) {
|
|
3796
|
+
return {
|
|
3797
|
+
action: "merge",
|
|
3798
|
+
targetId: best.hit.observationId,
|
|
3799
|
+
reason: `Same entity "${extracted.entityName}", new memory has more facts (${newFactCount} > ${oldFactCount})`,
|
|
3800
|
+
mergedNarrative: mergeNarratives(best.hit.narrative, extracted.narrative),
|
|
3801
|
+
mergedFacts: mergeFacts2(oldFacts, extracted.facts)
|
|
3802
|
+
};
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
return { action: "new", reason: `Different from existing memories (best score: ${best.score.toFixed(2)})` };
|
|
3806
|
+
}
|
|
3807
|
+
var SIMILARITY_HIGH2, SIMILARITY_MEDIUM2, SIMILARITY_DUPLICATE, LLM_RESOLVE_PROMPT;
|
|
3808
|
+
var init_resolve = __esm({
|
|
3809
|
+
"src/memory/formation/resolve.ts"() {
|
|
3810
|
+
"use strict";
|
|
3811
|
+
init_esm_shims();
|
|
3812
|
+
SIMILARITY_HIGH2 = 0.75;
|
|
3813
|
+
SIMILARITY_MEDIUM2 = 0.5;
|
|
3814
|
+
SIMILARITY_DUPLICATE = 0.9;
|
|
3815
|
+
LLM_RESOLVE_PROMPT = `You are a Memory Consolidation Manager for a software engineering knowledge base.
|
|
3816
|
+
|
|
3817
|
+
You must decide what to do with a NEW memory given EXISTING memories that are similar.
|
|
3818
|
+
|
|
3819
|
+
Operations:
|
|
3820
|
+
- ADD: The new memory contains genuinely new information not present in existing memories.
|
|
3821
|
+
- UPDATE: The new memory adds to or refines an existing memory. Specify which existing memory ID to update.
|
|
3822
|
+
- DELETE: The new memory contradicts an existing memory. Specify which existing memory ID to delete.
|
|
3823
|
+
- NOOP: The new memory is redundant (already covered by existing memories). Skip storage.
|
|
3824
|
+
|
|
3825
|
+
Rules:
|
|
3826
|
+
- Return ONLY a JSON object
|
|
3827
|
+
- If UPDATE: merge the best information from both old and new
|
|
3828
|
+
- If DELETE: the new memory supersedes the old (contradiction detected)
|
|
3829
|
+
- Prefer UPDATE over ADD when the topic is the same but information differs
|
|
3830
|
+
- Prefer NOOP over ADD when the information is essentially the same
|
|
3831
|
+
|
|
3832
|
+
Response format:
|
|
3833
|
+
{"action": "ADD|UPDATE|DELETE|NOOP", "targetId": <number or null>, "reason": "<brief explanation>", "mergedText": "<merged content for UPDATE, or null>"}`;
|
|
3834
|
+
}
|
|
3835
|
+
});
|
|
3836
|
+
|
|
3837
|
+
// src/memory/formation/evaluate.ts
|
|
3838
|
+
function factDensity(facts, narrativeLength) {
|
|
3839
|
+
if (narrativeLength === 0) return 0;
|
|
3840
|
+
const structuredChars = facts.reduce((sum, f) => sum + f.length, 0);
|
|
3841
|
+
return Math.min(1, structuredChars / Math.max(narrativeLength, 100));
|
|
3842
|
+
}
|
|
3843
|
+
function specificityScore(content) {
|
|
3844
|
+
let count2 = 0;
|
|
3845
|
+
for (const p of SPECIFICITY_PATTERNS) {
|
|
3846
|
+
p.lastIndex = 0;
|
|
3847
|
+
if (p.test(content)) count2++;
|
|
3848
|
+
}
|
|
3849
|
+
return Math.min(1, count2 / 3);
|
|
3850
|
+
}
|
|
3851
|
+
function causalScore(content) {
|
|
3852
|
+
let count2 = 0;
|
|
3853
|
+
for (const p of CAUSAL_PATTERNS) {
|
|
3854
|
+
p.lastIndex = 0;
|
|
3855
|
+
if (p.test(content)) count2++;
|
|
3856
|
+
}
|
|
3857
|
+
return Math.min(1, count2 / 2);
|
|
3858
|
+
}
|
|
3859
|
+
function noiseScore(title, narrative) {
|
|
3860
|
+
let noisiness = 0;
|
|
3861
|
+
for (const p of NOISE_PATTERNS) {
|
|
3862
|
+
if (p.test(title)) {
|
|
3863
|
+
noisiness += 0.3;
|
|
3864
|
+
break;
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
const lines = narrative.split("\n").filter((l) => l.trim().length > 0);
|
|
3868
|
+
let toolOutputLines = 0;
|
|
3869
|
+
for (const line of lines) {
|
|
3870
|
+
for (const p of TOOL_OUTPUT_PATTERNS) {
|
|
3871
|
+
if (p.test(line)) {
|
|
3872
|
+
toolOutputLines++;
|
|
3873
|
+
break;
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
if (lines.length > 0) {
|
|
3878
|
+
noisiness += toolOutputLines / lines.length * 0.5;
|
|
3879
|
+
}
|
|
3880
|
+
if (narrative.length < 50) noisiness += 0.2;
|
|
3881
|
+
return Math.min(1, noisiness);
|
|
3882
|
+
}
|
|
3883
|
+
function categorize(score) {
|
|
3884
|
+
if (score >= 0.6) return "core";
|
|
3885
|
+
if (score >= 0.35) return "contextual";
|
|
3886
|
+
return "ephemeral";
|
|
3887
|
+
}
|
|
3888
|
+
function buildReason(typeWeight, factDens, specificity, causal, noise, category) {
|
|
3889
|
+
const parts = [];
|
|
3890
|
+
if (typeWeight >= 0.7) parts.push("high-value type");
|
|
3891
|
+
else if (typeWeight <= 0.45) parts.push("low-value type");
|
|
3892
|
+
if (factDens > 0.3) parts.push("fact-dense");
|
|
3893
|
+
if (specificity > 0.3) parts.push("specific (versions/codes/paths)");
|
|
3894
|
+
if (causal > 0.3) parts.push("causal reasoning");
|
|
3895
|
+
if (noise > 0.3) parts.push("noisy content");
|
|
3896
|
+
const detail = parts.length > 0 ? parts.join(", ") : "average content";
|
|
3897
|
+
return `${category}: ${detail}`;
|
|
3898
|
+
}
|
|
3899
|
+
function runEvaluate(extracted) {
|
|
3900
|
+
const content = `${extracted.title} ${extracted.narrative} ${extracted.facts.join(" ")}`;
|
|
3901
|
+
const typeWeight = TYPE_WEIGHTS[extracted.type] ?? 0.5;
|
|
3902
|
+
const factDens = factDensity(extracted.facts, extracted.narrative.length);
|
|
3903
|
+
const specificity = specificityScore(content);
|
|
3904
|
+
const causal = causalScore(content);
|
|
3905
|
+
const noise = noiseScore(extracted.title, extracted.narrative);
|
|
3906
|
+
const rawScore = typeWeight * 0.5 + factDens * 0.12 + specificity * 0.12 + causal * 0.12 - noise * 0.14;
|
|
3907
|
+
const extractionBonus = extracted.extractedFacts.length > 0 ? 0.05 : 0;
|
|
3908
|
+
const titlePenalty = extracted.titleImproved ? -0.03 : 0;
|
|
3909
|
+
const correctionBonus = extracted.typeCorrected ? 0.03 : 0;
|
|
3910
|
+
const score = Math.max(0, Math.min(1, rawScore + extractionBonus + titlePenalty + correctionBonus));
|
|
3911
|
+
const category = categorize(score);
|
|
3912
|
+
const reason = buildReason(typeWeight, factDens, specificity, causal, noise, category);
|
|
3913
|
+
return { score, category, reason };
|
|
3914
|
+
}
|
|
3915
|
+
var TYPE_WEIGHTS, SPECIFICITY_PATTERNS, CAUSAL_PATTERNS, NOISE_PATTERNS, TOOL_OUTPUT_PATTERNS;
|
|
3916
|
+
var init_evaluate = __esm({
|
|
3917
|
+
"src/memory/formation/evaluate.ts"() {
|
|
3918
|
+
"use strict";
|
|
3919
|
+
init_esm_shims();
|
|
3920
|
+
TYPE_WEIGHTS = {
|
|
3921
|
+
"gotcha": 0.85,
|
|
3922
|
+
"decision": 0.8,
|
|
3923
|
+
"problem-solution": 0.75,
|
|
3924
|
+
"trade-off": 0.7,
|
|
3925
|
+
"reasoning": 0.7,
|
|
3926
|
+
"why-it-exists": 0.65,
|
|
3927
|
+
"how-it-works": 0.6,
|
|
3928
|
+
"discovery": 0.55,
|
|
3929
|
+
"what-changed": 0.45,
|
|
3930
|
+
"session-request": 0.4
|
|
3931
|
+
};
|
|
3932
|
+
SPECIFICITY_PATTERNS = [
|
|
3933
|
+
/\b\d+\.\d+\.\d+\b/,
|
|
3934
|
+
// Semantic version numbers
|
|
3935
|
+
/\b(ERR_|ENOENT|ECONNREFUSED|E[A-Z]{3,})\b/,
|
|
3936
|
+
// Error codes
|
|
3937
|
+
/\b(port|PORT)\s*[:=]?\s*\d{2,5}\b/i,
|
|
3938
|
+
// Port numbers
|
|
3939
|
+
/\bhttps?:\/\/\S+/,
|
|
3940
|
+
// URLs
|
|
3941
|
+
/`[^`]{3,60}`/,
|
|
3942
|
+
// Inline code references
|
|
3943
|
+
/\b[A-Z][A-Z0-9_]{3,}\b/,
|
|
3944
|
+
// Constants (e.g., MAX_RETRIES)
|
|
3945
|
+
/\b\d+\s*(ms|s|sec|min|MB|GB|KB)\b/i
|
|
3946
|
+
// Measurements with units
|
|
3947
|
+
];
|
|
3948
|
+
CAUSAL_PATTERNS = [
|
|
3949
|
+
/\b(because|therefore|due to|caused by|as a result|fixed by|resolved by)\b/i,
|
|
3950
|
+
/\b(so that|in order to|leads to|results in|prevents)\b/i,
|
|
3951
|
+
/(?:因为|所以|由于|导致|造成|因此|为了|解决)/
|
|
3952
|
+
];
|
|
3953
|
+
NOISE_PATTERNS = [
|
|
3954
|
+
/^Session activity/i,
|
|
3955
|
+
/^Updated \S+\.\w+$/i,
|
|
3956
|
+
/^Created \S+\.\w+$/i,
|
|
3957
|
+
/^Deleted \S+\.\w+$/i,
|
|
3958
|
+
/^File written successfully/i,
|
|
3959
|
+
/^Command executed/i,
|
|
3960
|
+
/^Tool: (read_file|list_dir|find_by_name)/i,
|
|
3961
|
+
/^\s*$/
|
|
3962
|
+
];
|
|
3963
|
+
TOOL_OUTPUT_PATTERNS = [
|
|
3964
|
+
/^(file|directory|folder)\s+(created|deleted|moved|copied)/i,
|
|
3965
|
+
/^Successfully\s+(installed|updated|removed)/i,
|
|
3966
|
+
/^\d+ files? changed/i,
|
|
3967
|
+
/^npm (WARN|notice)/i,
|
|
3968
|
+
/^\s*at\s+\S+\s+\(/
|
|
3969
|
+
// Stack trace lines
|
|
3970
|
+
];
|
|
3971
|
+
}
|
|
3972
|
+
});
|
|
3973
|
+
|
|
3974
|
+
// src/memory/formation/index.ts
|
|
3975
|
+
var formation_exports = {};
|
|
3976
|
+
__export(formation_exports, {
|
|
3977
|
+
clearFormationMetrics: () => clearFormationMetrics,
|
|
3978
|
+
getBeforeAfterMetrics: () => getBeforeAfterMetrics,
|
|
3979
|
+
getFormationMetrics: () => getFormationMetrics,
|
|
3980
|
+
getMetricsSummary: () => getMetricsSummary,
|
|
3981
|
+
recordBeforeAfterMetrics: () => recordBeforeAfterMetrics,
|
|
3982
|
+
runFormation: () => runFormation
|
|
3983
|
+
});
|
|
3984
|
+
function getFormationMetrics() {
|
|
3985
|
+
return metricsBuffer;
|
|
3986
|
+
}
|
|
3987
|
+
function clearFormationMetrics() {
|
|
3988
|
+
metricsBuffer.length = 0;
|
|
3989
|
+
}
|
|
3990
|
+
function recordBeforeAfterMetrics(data) {
|
|
3991
|
+
if (beforeAfterBuffer.length >= MAX_BEFORE_AFTER_BUFFER) {
|
|
3992
|
+
beforeAfterBuffer.shift();
|
|
3993
|
+
}
|
|
3994
|
+
beforeAfterBuffer.push(data);
|
|
3995
|
+
}
|
|
3996
|
+
function getBeforeAfterMetrics() {
|
|
3997
|
+
const totalProcessed = beforeAfterBuffer.length;
|
|
3998
|
+
if (totalProcessed === 0) {
|
|
3999
|
+
return {
|
|
4000
|
+
totalProcessed: 0,
|
|
4001
|
+
agreements: 0,
|
|
4002
|
+
disagreements: 0,
|
|
4003
|
+
disagreementBreakdown: {
|
|
4004
|
+
formationDiscardedCompactAdded: 0,
|
|
4005
|
+
formationMergedCompactAdded: 0,
|
|
4006
|
+
formationAddedCompactDiscarded: 0,
|
|
4007
|
+
formationAddedCompactMerged: 0,
|
|
4008
|
+
formationEvolvedCompactAdded: 0,
|
|
4009
|
+
other: 0
|
|
4010
|
+
},
|
|
4011
|
+
quality: {
|
|
4012
|
+
formationDiscardedLowValue: 0,
|
|
4013
|
+
formationMergedDuplicates: 0,
|
|
4014
|
+
formationEvolvedOutdated: 0,
|
|
4015
|
+
compactMissedDuplicates: 0,
|
|
4016
|
+
compactKeptLowValue: 0
|
|
4017
|
+
},
|
|
4018
|
+
duration: {
|
|
4019
|
+
formationAvgMs: 0,
|
|
4020
|
+
compactAvgMs: 0,
|
|
4021
|
+
diffMs: 0
|
|
4022
|
+
}
|
|
4023
|
+
};
|
|
4024
|
+
}
|
|
4025
|
+
let agreements = 0;
|
|
4026
|
+
let disagreements = 0;
|
|
4027
|
+
const disagreementBreakdown = {
|
|
4028
|
+
formationDiscardedCompactAdded: 0,
|
|
4029
|
+
formationMergedCompactAdded: 0,
|
|
4030
|
+
formationAddedCompactDiscarded: 0,
|
|
4031
|
+
formationAddedCompactMerged: 0,
|
|
4032
|
+
formationEvolvedCompactAdded: 0,
|
|
4033
|
+
other: 0
|
|
4034
|
+
};
|
|
4035
|
+
const quality = {
|
|
4036
|
+
formationDiscardedLowValue: 0,
|
|
4037
|
+
formationMergedDuplicates: 0,
|
|
4038
|
+
formationEvolvedOutdated: 0,
|
|
4039
|
+
compactMissedDuplicates: 0,
|
|
4040
|
+
compactKeptLowValue: 0
|
|
4041
|
+
};
|
|
4042
|
+
let formationTotalDuration = 0;
|
|
4043
|
+
let compactTotalDuration = 0;
|
|
4044
|
+
let compactDurationCount = 0;
|
|
4045
|
+
for (const data of beforeAfterBuffer) {
|
|
4046
|
+
formationTotalDuration += data.formationDurationMs;
|
|
4047
|
+
if (data.compactDurationMs !== void 0) {
|
|
4048
|
+
compactTotalDuration += data.compactDurationMs;
|
|
4049
|
+
compactDurationCount++;
|
|
4050
|
+
}
|
|
4051
|
+
const formationAction = data.formationAction;
|
|
4052
|
+
const oldCompactAction = data.oldCompactAction;
|
|
4053
|
+
let formationMapped = "ADD";
|
|
4054
|
+
if (formationAction === "merge" || formationAction === "evolve") {
|
|
4055
|
+
formationMapped = "UPDATE";
|
|
4056
|
+
} else if (formationAction === "discard") {
|
|
4057
|
+
formationMapped = "NONE";
|
|
4058
|
+
}
|
|
4059
|
+
if (formationMapped === oldCompactAction) {
|
|
4060
|
+
agreements++;
|
|
4061
|
+
} else {
|
|
4062
|
+
disagreements++;
|
|
4063
|
+
if (formationAction === "discard" && oldCompactAction === "ADD") {
|
|
4064
|
+
disagreementBreakdown.formationDiscardedCompactAdded++;
|
|
4065
|
+
if (data.formationValueCategory === "ephemeral") {
|
|
4066
|
+
quality.formationDiscardedLowValue++;
|
|
4067
|
+
}
|
|
4068
|
+
} else if (formationAction === "merge" && oldCompactAction === "ADD") {
|
|
4069
|
+
disagreementBreakdown.formationMergedCompactAdded++;
|
|
4070
|
+
quality.formationMergedDuplicates++;
|
|
4071
|
+
} else if (formationAction === "new" && oldCompactAction === "NONE") {
|
|
4072
|
+
disagreementBreakdown.formationAddedCompactDiscarded++;
|
|
4073
|
+
quality.compactMissedDuplicates++;
|
|
4074
|
+
} else if (formationAction === "new" && oldCompactAction === "UPDATE") {
|
|
4075
|
+
disagreementBreakdown.formationAddedCompactMerged++;
|
|
4076
|
+
} else if (formationAction === "evolve" && oldCompactAction === "ADD") {
|
|
4077
|
+
disagreementBreakdown.formationEvolvedCompactAdded++;
|
|
4078
|
+
quality.formationEvolvedOutdated++;
|
|
4079
|
+
} else if (formationAction === "new" && oldCompactAction === "ADD") {
|
|
4080
|
+
if (data.formationValueCategory === "ephemeral") {
|
|
4081
|
+
quality.compactKeptLowValue++;
|
|
4082
|
+
}
|
|
4083
|
+
} else {
|
|
4084
|
+
disagreementBreakdown.other++;
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
const compactAvgMs = compactDurationCount > 0 ? compactTotalDuration / compactDurationCount : 0;
|
|
4089
|
+
const formationAvgMs = totalProcessed > 0 ? formationTotalDuration / totalProcessed : 0;
|
|
4090
|
+
return {
|
|
4091
|
+
totalProcessed,
|
|
4092
|
+
agreements,
|
|
4093
|
+
disagreements,
|
|
4094
|
+
disagreementBreakdown,
|
|
4095
|
+
quality,
|
|
4096
|
+
duration: {
|
|
4097
|
+
formationAvgMs,
|
|
4098
|
+
compactAvgMs,
|
|
4099
|
+
diffMs: compactAvgMs - formationAvgMs
|
|
4100
|
+
}
|
|
4101
|
+
};
|
|
4102
|
+
}
|
|
4103
|
+
function getMetricsSummary() {
|
|
4104
|
+
const total = metricsBuffer.length;
|
|
4105
|
+
if (total === 0) {
|
|
4106
|
+
return {
|
|
4107
|
+
total: 0,
|
|
4108
|
+
avgValueScore: 0,
|
|
4109
|
+
avgExtractedFacts: 0,
|
|
4110
|
+
titleImprovedRate: 0,
|
|
4111
|
+
entityResolvedRate: 0,
|
|
4112
|
+
typeCorectedRate: 0,
|
|
4113
|
+
resolutionBreakdown: {},
|
|
4114
|
+
categoryBreakdown: {},
|
|
4115
|
+
avgDurationMs: 0
|
|
4116
|
+
};
|
|
4117
|
+
}
|
|
4118
|
+
const sum = (fn) => metricsBuffer.reduce((s, m) => s + fn(m), 0);
|
|
4119
|
+
const resolutionBreakdown = {};
|
|
4120
|
+
const categoryBreakdown = {};
|
|
4121
|
+
for (const m of metricsBuffer) {
|
|
4122
|
+
resolutionBreakdown[m.resolutionAction] = (resolutionBreakdown[m.resolutionAction] ?? 0) + 1;
|
|
4123
|
+
categoryBreakdown[m.valueCategory] = (categoryBreakdown[m.valueCategory] ?? 0) + 1;
|
|
4124
|
+
}
|
|
4125
|
+
return {
|
|
4126
|
+
total,
|
|
4127
|
+
avgValueScore: sum((m) => m.valueScore) / total,
|
|
4128
|
+
avgExtractedFacts: sum((m) => m.systemExtractedFacts) / total,
|
|
4129
|
+
titleImprovedRate: sum((m) => m.titleImproved ? 1 : 0) / total,
|
|
4130
|
+
entityResolvedRate: sum((m) => m.entityResolved ? 1 : 0) / total,
|
|
4131
|
+
typeCorectedRate: sum((m) => m.typeCorrected ? 1 : 0) / total,
|
|
4132
|
+
resolutionBreakdown,
|
|
4133
|
+
categoryBreakdown,
|
|
4134
|
+
avgDurationMs: sum((m) => m.durationMs) / total
|
|
4135
|
+
};
|
|
4136
|
+
}
|
|
4137
|
+
async function runFormation(input, config) {
|
|
4138
|
+
const startTime = Date.now();
|
|
4139
|
+
let stagesCompleted = 0;
|
|
4140
|
+
const existingEntities = config.getEntityNames();
|
|
4141
|
+
const extraction = await runExtract(input, existingEntities, config.useLLM);
|
|
4142
|
+
stagesCompleted = 1;
|
|
4143
|
+
let resolution;
|
|
4144
|
+
if (input.topicKey) {
|
|
4145
|
+
resolution = {
|
|
4146
|
+
action: "new",
|
|
4147
|
+
reason: "TopicKey upsert \u2014 bypasses resolve stage"
|
|
4148
|
+
};
|
|
4149
|
+
} else {
|
|
4150
|
+
resolution = await runResolve(
|
|
4151
|
+
extraction,
|
|
4152
|
+
input.projectId,
|
|
4153
|
+
config.searchMemories,
|
|
4154
|
+
config.getObservation,
|
|
4155
|
+
config.useLLM
|
|
4156
|
+
);
|
|
4157
|
+
}
|
|
4158
|
+
stagesCompleted = 2;
|
|
4159
|
+
const evaluation = runEvaluate(extraction);
|
|
4160
|
+
stagesCompleted = 3;
|
|
4161
|
+
const durationMs = Date.now() - startTime;
|
|
4162
|
+
const formed = {
|
|
4163
|
+
// Final enriched data
|
|
4164
|
+
entityName: extraction.entityName,
|
|
4165
|
+
type: extraction.type,
|
|
4166
|
+
title: extraction.title,
|
|
4167
|
+
narrative: resolution.mergedNarrative ?? extraction.narrative,
|
|
4168
|
+
facts: resolution.mergedFacts ?? extraction.facts,
|
|
4169
|
+
// Stage results
|
|
4170
|
+
extraction,
|
|
4171
|
+
resolution,
|
|
4172
|
+
evaluation,
|
|
4173
|
+
// Pipeline metadata
|
|
4174
|
+
pipeline: {
|
|
4175
|
+
mode: config.useLLM ? "llm" : "rules",
|
|
4176
|
+
durationMs,
|
|
4177
|
+
stagesCompleted,
|
|
4178
|
+
shadow: config.mode === "shadow"
|
|
4179
|
+
},
|
|
4180
|
+
// Governance fields
|
|
4181
|
+
governance: {
|
|
4182
|
+
provenance: {
|
|
4183
|
+
creator: input.source === "explicit" ? "user" : "system",
|
|
4184
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4185
|
+
source: input.source
|
|
4186
|
+
},
|
|
4187
|
+
confidence: {
|
|
4188
|
+
score: evaluation.score,
|
|
4189
|
+
breakdown: {
|
|
4190
|
+
extractionConfidence: extraction.extractedFacts.length > 0 ? 0.8 : 0.5,
|
|
4191
|
+
resolutionConfidence: resolution.action === "new" ? 0.7 : 0.9,
|
|
4192
|
+
evaluationConfidence: evaluation.score
|
|
4193
|
+
},
|
|
4194
|
+
reason: `Value score ${evaluation.score.toFixed(2)} in ${evaluation.category} category`
|
|
4195
|
+
},
|
|
4196
|
+
supersession: (resolution.action === "merge" || resolution.action === "evolve") && resolution.targetId ? {
|
|
4197
|
+
replacedIds: [resolution.targetId],
|
|
4198
|
+
reason: resolution.reason,
|
|
4199
|
+
replacementType: resolution.action === "evolve" ? "hard" : "soft"
|
|
4200
|
+
} : void 0
|
|
4201
|
+
}
|
|
4202
|
+
};
|
|
4203
|
+
const metrics = {
|
|
4204
|
+
systemExtractedFacts: extraction.extractedFacts.length,
|
|
4205
|
+
titleImproved: extraction.titleImproved,
|
|
4206
|
+
entityResolved: extraction.entityResolved,
|
|
4207
|
+
typeCorrected: extraction.typeCorrected,
|
|
4208
|
+
resolutionAction: resolution.action,
|
|
4209
|
+
valueScore: evaluation.score,
|
|
4210
|
+
valueCategory: evaluation.category,
|
|
4211
|
+
durationMs,
|
|
4212
|
+
mode: "rules"
|
|
4213
|
+
};
|
|
4214
|
+
if (metricsBuffer.length >= MAX_METRICS_BUFFER) {
|
|
4215
|
+
metricsBuffer.shift();
|
|
4216
|
+
}
|
|
4217
|
+
metricsBuffer.push(metrics);
|
|
4218
|
+
return formed;
|
|
4219
|
+
}
|
|
4220
|
+
var metricsBuffer, MAX_METRICS_BUFFER, beforeAfterBuffer, MAX_BEFORE_AFTER_BUFFER;
|
|
4221
|
+
var init_formation = __esm({
|
|
4222
|
+
"src/memory/formation/index.ts"() {
|
|
4223
|
+
"use strict";
|
|
4224
|
+
init_esm_shims();
|
|
4225
|
+
init_extract();
|
|
4226
|
+
init_resolve();
|
|
4227
|
+
init_evaluate();
|
|
4228
|
+
metricsBuffer = [];
|
|
4229
|
+
MAX_METRICS_BUFFER = 500;
|
|
4230
|
+
beforeAfterBuffer = [];
|
|
4231
|
+
MAX_BEFORE_AFTER_BUFFER = 500;
|
|
4232
|
+
}
|
|
4233
|
+
});
|
|
4234
|
+
|
|
4235
|
+
// src/config/behavior.ts
|
|
4236
|
+
var behavior_exports = {};
|
|
4237
|
+
__export(behavior_exports, {
|
|
4238
|
+
getBehaviorConfig: () => getBehaviorConfig,
|
|
4239
|
+
resetBehaviorConfigCache: () => resetBehaviorConfigCache
|
|
4240
|
+
});
|
|
4241
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
4242
|
+
import { join as join17 } from "path";
|
|
4243
|
+
import { homedir as homedir16 } from "os";
|
|
4244
|
+
function getBehaviorConfig() {
|
|
4245
|
+
if (cached) return cached;
|
|
4246
|
+
try {
|
|
4247
|
+
const configPath = join17(homedir16(), ".memorix", "config.json");
|
|
4248
|
+
const raw = readFileSync6(configPath, "utf-8");
|
|
4249
|
+
const config = JSON.parse(raw);
|
|
4250
|
+
const behavior = config.behavior ?? {};
|
|
4251
|
+
cached = {
|
|
4252
|
+
sessionInject: behavior.sessionInject ?? DEFAULTS.sessionInject,
|
|
4253
|
+
syncAdvisory: behavior.syncAdvisory ?? DEFAULTS.syncAdvisory,
|
|
4254
|
+
autoCleanup: behavior.autoCleanup ?? DEFAULTS.autoCleanup,
|
|
4255
|
+
formationMode: behavior.formationMode ?? DEFAULTS.formationMode
|
|
4256
|
+
};
|
|
4257
|
+
} catch {
|
|
4258
|
+
cached = { ...DEFAULTS };
|
|
4259
|
+
}
|
|
4260
|
+
return cached;
|
|
2887
4261
|
}
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
if (existingMemories.length === 0) return { action: "ADD", reason: "No existing memories", usedLLM: false };
|
|
2891
|
-
const asExisting = existingMemories.map((m) => ({
|
|
2892
|
-
...m,
|
|
2893
|
-
score: 0.8
|
|
2894
|
-
// Batch dedup assumes high similarity (pre-filtered)
|
|
2895
|
-
}));
|
|
2896
|
-
return compactOnWrite(newMemory, asExisting);
|
|
4262
|
+
function resetBehaviorConfigCache() {
|
|
4263
|
+
cached = null;
|
|
2897
4264
|
}
|
|
2898
|
-
var
|
|
2899
|
-
var
|
|
2900
|
-
"src/
|
|
4265
|
+
var DEFAULTS, cached;
|
|
4266
|
+
var init_behavior = __esm({
|
|
4267
|
+
"src/config/behavior.ts"() {
|
|
2901
4268
|
"use strict";
|
|
2902
4269
|
init_esm_shims();
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
Actions:
|
|
2912
|
-
- ADD: New memory contains unique information. Store it as-is.
|
|
2913
|
-
- UPDATE: New memory supersedes or improves an existing one. Merge them into a single, comprehensive memory.
|
|
2914
|
-
- DELETE: An existing memory is outdated/contradicted by the new one. Remove it.
|
|
2915
|
-
- NONE: New memory is redundant. Existing memories already cover this. Skip storing.
|
|
2916
|
-
|
|
2917
|
-
Decision rules:
|
|
2918
|
-
- Same topic updated (e.g., "MySQL \u2192 PostgreSQL"): UPDATE the old memory with merged content
|
|
2919
|
-
- Bug fixed that was reported as open: UPDATE the bug report to include the fix
|
|
2920
|
-
- Task completed that was tracked as in-progress: UPDATE to mark completed
|
|
2921
|
-
- Minor variation of existing memory: NONE (skip)
|
|
2922
|
-
- Completely new topic: ADD
|
|
2923
|
-
- Old info directly contradicted: DELETE the old one
|
|
2924
|
-
- Prefer UPDATE over ADD \u2014 keep memory count low, merge information
|
|
2925
|
-
|
|
2926
|
-
For UPDATE: write a merged narrative that combines the best of both old and new, preserving all important details. Also merge the facts lists, removing duplicates.
|
|
2927
|
-
|
|
2928
|
-
Respond in JSON only:
|
|
2929
|
-
{
|
|
2930
|
-
"action": "ADD" | "UPDATE" | "DELETE" | "NONE",
|
|
2931
|
-
"targetId": null or existing_memory_id_number,
|
|
2932
|
-
"reason": "brief explanation of decision",
|
|
2933
|
-
"mergedNarrative": "merged narrative text (required for UPDATE, null otherwise)",
|
|
2934
|
-
"mergedFacts": ["merged fact 1", "merged fact 2"] or null,
|
|
2935
|
-
"extractedFacts": ["fact extracted from new content 1", "fact 2"]
|
|
2936
|
-
}`;
|
|
2937
|
-
SIMILARITY_HIGH = 0.75;
|
|
2938
|
-
SIMILARITY_MEDIUM = 0.5;
|
|
4270
|
+
DEFAULTS = {
|
|
4271
|
+
sessionInject: "minimal",
|
|
4272
|
+
syncAdvisory: true,
|
|
4273
|
+
autoCleanup: true,
|
|
4274
|
+
formationMode: "active"
|
|
4275
|
+
};
|
|
4276
|
+
cached = null;
|
|
2939
4277
|
}
|
|
2940
4278
|
});
|
|
2941
4279
|
|
|
@@ -3187,7 +4525,8 @@ async function archiveExpired(projectDir2, referenceTime, accessMap) {
|
|
|
3187
4525
|
projectId: obs.projectId,
|
|
3188
4526
|
accessCount: access2?.accessCount ?? 0,
|
|
3189
4527
|
lastAccessedAt: access2?.lastAccessedAt ?? "",
|
|
3190
|
-
status: obs.status ?? "active"
|
|
4528
|
+
status: obs.status ?? "active",
|
|
4529
|
+
source: obs.source ?? "agent"
|
|
3191
4530
|
};
|
|
3192
4531
|
};
|
|
3193
4532
|
const toArchive = [];
|
|
@@ -3232,6 +4571,7 @@ var init_retention = __esm({
|
|
|
3232
4571
|
gotcha: "high",
|
|
3233
4572
|
decision: "high",
|
|
3234
4573
|
"trade-off": "high",
|
|
4574
|
+
reasoning: "high",
|
|
3235
4575
|
"problem-solution": "medium",
|
|
3236
4576
|
"how-it-works": "medium",
|
|
3237
4577
|
"what-changed": "low",
|
|
@@ -3249,10 +4589,10 @@ var engine_exports = {};
|
|
|
3249
4589
|
__export(engine_exports, {
|
|
3250
4590
|
SkillsEngine: () => SkillsEngine
|
|
3251
4591
|
});
|
|
3252
|
-
import { existsSync as
|
|
3253
|
-
import { join as
|
|
3254
|
-
import { homedir as
|
|
3255
|
-
var SKILLS_DIRS, SKILL_WORTHY_TYPES, MIN_OBS_FOR_SKILL, MIN_SCORE_FOR_SKILL, SkillsEngine;
|
|
4592
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync3 } from "fs";
|
|
4593
|
+
import { join as join18 } from "path";
|
|
4594
|
+
import { homedir as homedir17 } from "os";
|
|
4595
|
+
var SKILLS_DIRS, SKILL_WORTHY_TYPES, MIN_OBS_FOR_SKILL, MIN_SCORE_FOR_SKILL, LOW_SIGNAL_TITLE_PATTERNS, LOW_SIGNAL_ENTITY_PATTERNS, COMMAND_TRACE_PATTERNS, SkillsEngine;
|
|
3256
4596
|
var init_engine = __esm({
|
|
3257
4597
|
"src/skills/engine.ts"() {
|
|
3258
4598
|
"use strict";
|
|
@@ -3277,6 +4617,31 @@ var init_engine = __esm({
|
|
|
3277
4617
|
]);
|
|
3278
4618
|
MIN_OBS_FOR_SKILL = 3;
|
|
3279
4619
|
MIN_SCORE_FOR_SKILL = 5;
|
|
4620
|
+
LOW_SIGNAL_TITLE_PATTERNS = [
|
|
4621
|
+
/^ran:/i,
|
|
4622
|
+
/^command:/i
|
|
4623
|
+
];
|
|
4624
|
+
LOW_SIGNAL_ENTITY_PATTERNS = [
|
|
4625
|
+
/^(?:bash|sh|cmd|powershell|pwsh|node|npm|npx|pnpm|yarn|gh|git)$/i,
|
|
4626
|
+
/^mcp[_-]/i
|
|
4627
|
+
];
|
|
4628
|
+
COMMAND_TRACE_PATTERNS = [
|
|
4629
|
+
/\bcommand:\b/i,
|
|
4630
|
+
/\b2>&1\b/i,
|
|
4631
|
+
/\bselect-string\b/i,
|
|
4632
|
+
/\bget-content\b/i,
|
|
4633
|
+
/\bget-command\b/i,
|
|
4634
|
+
/\bpowershell\b/i,
|
|
4635
|
+
/\bcmd(?:\.exe)?\b/i,
|
|
4636
|
+
/\bnpm\b/i,
|
|
4637
|
+
/\bnpx\b/i,
|
|
4638
|
+
/\bpnpm\b/i,
|
|
4639
|
+
/\byarn\b/i,
|
|
4640
|
+
/\bgit\b/i,
|
|
4641
|
+
/\bgh\b/i,
|
|
4642
|
+
/\|/,
|
|
4643
|
+
/&&/
|
|
4644
|
+
];
|
|
3280
4645
|
SkillsEngine = class {
|
|
3281
4646
|
constructor(projectRoot, options) {
|
|
3282
4647
|
this.projectRoot = projectRoot;
|
|
@@ -3292,30 +4657,30 @@ var init_engine = __esm({
|
|
|
3292
4657
|
listSkills() {
|
|
3293
4658
|
const skills = [];
|
|
3294
4659
|
const seen = /* @__PURE__ */ new Set();
|
|
3295
|
-
const home =
|
|
4660
|
+
const home = homedir17();
|
|
3296
4661
|
for (const [agent, dirs] of Object.entries(SKILLS_DIRS)) {
|
|
3297
4662
|
for (const dir of dirs) {
|
|
3298
|
-
const paths = [
|
|
4663
|
+
const paths = [join18(this.projectRoot, dir)];
|
|
3299
4664
|
if (!this.skipGlobal) {
|
|
3300
|
-
paths.push(
|
|
4665
|
+
paths.push(join18(home, dir));
|
|
3301
4666
|
}
|
|
3302
4667
|
for (const skillsRoot of paths) {
|
|
3303
|
-
if (!
|
|
4668
|
+
if (!existsSync7(skillsRoot)) continue;
|
|
3304
4669
|
try {
|
|
3305
|
-
const entries =
|
|
4670
|
+
const entries = readdirSync3(skillsRoot, { withFileTypes: true });
|
|
3306
4671
|
for (const entry of entries) {
|
|
3307
4672
|
if (!entry.isDirectory()) continue;
|
|
3308
4673
|
const name = entry.name;
|
|
3309
4674
|
if (seen.has(name)) continue;
|
|
3310
|
-
const skillMd =
|
|
3311
|
-
if (!
|
|
4675
|
+
const skillMd = join18(skillsRoot, name, "SKILL.md");
|
|
4676
|
+
if (!existsSync7(skillMd)) continue;
|
|
3312
4677
|
try {
|
|
3313
|
-
const content =
|
|
4678
|
+
const content = readFileSync7(skillMd, "utf-8");
|
|
3314
4679
|
const description = this.parseDescription(content);
|
|
3315
4680
|
skills.push({
|
|
3316
4681
|
name,
|
|
3317
4682
|
description,
|
|
3318
|
-
sourcePath:
|
|
4683
|
+
sourcePath: join18(skillsRoot, name),
|
|
3319
4684
|
sourceAgent: agent,
|
|
3320
4685
|
content,
|
|
3321
4686
|
generated: false
|
|
@@ -3339,7 +4704,8 @@ var init_engine = __esm({
|
|
|
3339
4704
|
* rich knowledge accumulation.
|
|
3340
4705
|
*/
|
|
3341
4706
|
generateFromObservations(observations2) {
|
|
3342
|
-
const
|
|
4707
|
+
const candidates = observations2.filter((obs) => !this.isLowSignalObservation(obs));
|
|
4708
|
+
const clusters = this.clusterByEntity(candidates);
|
|
3343
4709
|
for (const cluster of clusters.values()) {
|
|
3344
4710
|
cluster.score = this.scoreCluster(cluster);
|
|
3345
4711
|
}
|
|
@@ -3357,11 +4723,11 @@ var init_engine = __esm({
|
|
|
3357
4723
|
writeSkill(skill, target) {
|
|
3358
4724
|
const dirs = SKILLS_DIRS[target];
|
|
3359
4725
|
if (!dirs || dirs.length === 0) return null;
|
|
3360
|
-
const targetDir =
|
|
4726
|
+
const targetDir = join18(this.projectRoot, dirs[0], skill.name);
|
|
3361
4727
|
try {
|
|
3362
4728
|
mkdirSync3(targetDir, { recursive: true });
|
|
3363
|
-
writeFileSync2(
|
|
3364
|
-
return
|
|
4729
|
+
writeFileSync2(join18(targetDir, "SKILL.md"), skill.content, "utf-8");
|
|
4730
|
+
return join18(dirs[0], skill.name, "SKILL.md");
|
|
3365
4731
|
} catch {
|
|
3366
4732
|
return null;
|
|
3367
4733
|
}
|
|
@@ -3383,6 +4749,24 @@ var init_engine = __esm({
|
|
|
3383
4749
|
const match = content.match(/^---[\s\S]*?description:\s*["']?(.+?)["']?\s*$/m);
|
|
3384
4750
|
return match ? match[1] : "";
|
|
3385
4751
|
}
|
|
4752
|
+
isLowSignalObservation(obs) {
|
|
4753
|
+
if (obs.status === "archived") return true;
|
|
4754
|
+
const title = obs.title.trim();
|
|
4755
|
+
const narrative = obs.narrative.trim();
|
|
4756
|
+
const entity = (obs.entityName || "").trim();
|
|
4757
|
+
const haystack = `${title}
|
|
4758
|
+
${narrative}`;
|
|
4759
|
+
if (LOW_SIGNAL_TITLE_PATTERNS.some((pattern) => pattern.test(title))) {
|
|
4760
|
+
return true;
|
|
4761
|
+
}
|
|
4762
|
+
if (LOW_SIGNAL_ENTITY_PATTERNS.some((pattern) => pattern.test(entity))) {
|
|
4763
|
+
return true;
|
|
4764
|
+
}
|
|
4765
|
+
if (obs.source !== "git" && COMMAND_TRACE_PATTERNS.filter((pattern) => pattern.test(haystack)).length >= 2) {
|
|
4766
|
+
return true;
|
|
4767
|
+
}
|
|
4768
|
+
return false;
|
|
4769
|
+
}
|
|
3386
4770
|
clusterByEntity(observations2) {
|
|
3387
4771
|
const clusters = /* @__PURE__ */ new Map();
|
|
3388
4772
|
for (const obs of observations2) {
|
|
@@ -3577,7 +4961,7 @@ async function promoteToMiniSkill(projectDir2, projectId, observations2, options
|
|
|
3577
4961
|
const title = generateTitle(observations2);
|
|
3578
4962
|
const instruction = options?.instruction || generateInstruction(observations2);
|
|
3579
4963
|
const trigger = options?.trigger || generateTrigger(observations2);
|
|
3580
|
-
const facts =
|
|
4964
|
+
const facts = extractFacts2(observations2);
|
|
3581
4965
|
const tags = [
|
|
3582
4966
|
...options?.tags || [],
|
|
3583
4967
|
...extractTags(observations2)
|
|
@@ -3685,7 +5069,7 @@ function generateTrigger(observations2) {
|
|
|
3685
5069
|
}
|
|
3686
5070
|
return parts.length > 0 ? parts.join("; ") : `Related to ${obs.title}`;
|
|
3687
5071
|
}
|
|
3688
|
-
function
|
|
5072
|
+
function extractFacts2(observations2) {
|
|
3689
5073
|
const facts = /* @__PURE__ */ new Set();
|
|
3690
5074
|
for (const obs of observations2) {
|
|
3691
5075
|
for (const f of obs.facts) {
|
|
@@ -4113,6 +5497,45 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir) {
|
|
|
4113
5497
|
const t = obs.type || "unknown";
|
|
4114
5498
|
typeCounts[t] = (typeCounts[t] || 0) + 1;
|
|
4115
5499
|
}
|
|
5500
|
+
const sourceCounts = { git: 0, agent: 0, manual: 0 };
|
|
5501
|
+
const gitMemories = [];
|
|
5502
|
+
const now = Date.now();
|
|
5503
|
+
const sevenDaysAgo = now - 7 * 24 * 60 * 60 * 1e3;
|
|
5504
|
+
let recentGitCount = 0;
|
|
5505
|
+
for (const obs of observations2) {
|
|
5506
|
+
const src = obs.source || "agent";
|
|
5507
|
+
sourceCounts[src] = (sourceCounts[src] || 0) + 1;
|
|
5508
|
+
if (src === "git") {
|
|
5509
|
+
gitMemories.push(obs);
|
|
5510
|
+
if (obs.createdAt && new Date(obs.createdAt).getTime() > sevenDaysAgo) {
|
|
5511
|
+
recentGitCount++;
|
|
5512
|
+
}
|
|
5513
|
+
}
|
|
5514
|
+
}
|
|
5515
|
+
const gitSorted = [...gitMemories].sort((a, b) => (b.id || 0) - (a.id || 0));
|
|
5516
|
+
const recentGitMemories = gitSorted.slice(0, 8).map((o) => ({
|
|
5517
|
+
id: o.id,
|
|
5518
|
+
title: o.title,
|
|
5519
|
+
type: o.type,
|
|
5520
|
+
commitHash: o.commitHash,
|
|
5521
|
+
entityName: o.entityName,
|
|
5522
|
+
createdAt: o.createdAt,
|
|
5523
|
+
filesModified: o.filesModified
|
|
5524
|
+
}));
|
|
5525
|
+
let retentionSummary = { active: 0, stale: 0, archive: 0, immune: 0 };
|
|
5526
|
+
for (const obs of observations2) {
|
|
5527
|
+
const age = now - new Date(obs.createdAt || now).getTime();
|
|
5528
|
+
const ageHours = age / (1e3 * 60 * 60);
|
|
5529
|
+
const importance = obs.importance ?? 5;
|
|
5530
|
+
const accessCount = obs.accessCount ?? 0;
|
|
5531
|
+
const lambda = 0.01;
|
|
5532
|
+
const score = Math.min(importance * Math.exp(-lambda * ageHours) + Math.min(accessCount * 0.5, 3), 10);
|
|
5533
|
+
const isImmune2 = importance >= 8 || obs.type === "gotcha" || obs.type === "decision";
|
|
5534
|
+
if (isImmune2) retentionSummary.immune++;
|
|
5535
|
+
if (score >= 3) retentionSummary.active++;
|
|
5536
|
+
else if (score >= 1) retentionSummary.stale++;
|
|
5537
|
+
else retentionSummary.archive++;
|
|
5538
|
+
}
|
|
4116
5539
|
const sorted = [...observations2].sort((a, b) => (b.id || 0) - (a.id || 0)).slice(0, 10);
|
|
4117
5540
|
let embeddingStatus = { enabled: false, provider: "", dimensions: 0 };
|
|
4118
5541
|
try {
|
|
@@ -4131,8 +5554,15 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir) {
|
|
|
4131
5554
|
observations: observations2.length,
|
|
4132
5555
|
nextId: nextId2,
|
|
4133
5556
|
typeCounts,
|
|
5557
|
+
sourceCounts,
|
|
4134
5558
|
recentObservations: sorted,
|
|
4135
|
-
embedding: embeddingStatus
|
|
5559
|
+
embedding: embeddingStatus,
|
|
5560
|
+
gitSummary: {
|
|
5561
|
+
total: gitMemories.length,
|
|
5562
|
+
recentWeek: recentGitCount,
|
|
5563
|
+
recentMemories: recentGitMemories
|
|
5564
|
+
},
|
|
5565
|
+
retentionSummary
|
|
4136
5566
|
});
|
|
4137
5567
|
break;
|
|
4138
5568
|
}
|
|
@@ -4172,6 +5602,109 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir) {
|
|
|
4172
5602
|
});
|
|
4173
5603
|
break;
|
|
4174
5604
|
}
|
|
5605
|
+
case "/config": {
|
|
5606
|
+
const os4 = await import("os");
|
|
5607
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
5608
|
+
const { join: join21 } = await import("path");
|
|
5609
|
+
let yml = {};
|
|
5610
|
+
try {
|
|
5611
|
+
const { loadYamlConfig: loadYamlConfig2 } = await Promise.resolve().then(() => (init_yaml_loader(), yaml_loader_exports));
|
|
5612
|
+
yml = loadYamlConfig2(effectiveProjectId.includes("/") ? void 0 : void 0);
|
|
5613
|
+
} catch {
|
|
5614
|
+
}
|
|
5615
|
+
const projectRoot = process.cwd();
|
|
5616
|
+
const files = {
|
|
5617
|
+
"project memorix.yml": { exists: false, path: "" },
|
|
5618
|
+
"user memorix.yml": { exists: false, path: "" },
|
|
5619
|
+
"project .env": { exists: false, path: "" },
|
|
5620
|
+
"user .env": { exists: false, path: "" },
|
|
5621
|
+
"legacy config.json": { exists: false, path: "" }
|
|
5622
|
+
};
|
|
5623
|
+
try {
|
|
5624
|
+
const home = os4.homedir();
|
|
5625
|
+
const paths = {
|
|
5626
|
+
"project memorix.yml": join21(projectRoot, "memorix.yml"),
|
|
5627
|
+
"user memorix.yml": join21(home, ".memorix", "memorix.yml"),
|
|
5628
|
+
"project .env": join21(projectRoot, ".env"),
|
|
5629
|
+
"user .env": join21(home, ".memorix", ".env"),
|
|
5630
|
+
"legacy config.json": join21(home, ".memorix", "config.json")
|
|
5631
|
+
};
|
|
5632
|
+
for (const [key, fpath] of Object.entries(paths)) {
|
|
5633
|
+
files[key] = { exists: existsSync10(fpath), path: fpath };
|
|
5634
|
+
}
|
|
5635
|
+
} catch {
|
|
5636
|
+
}
|
|
5637
|
+
const values = [];
|
|
5638
|
+
const llmProvider = process.env.MEMORIX_LLM_PROVIDER || yml.llm?.provider;
|
|
5639
|
+
if (llmProvider) values.push({ key: "llm.provider", value: llmProvider, source: process.env.MEMORIX_LLM_PROVIDER ? "env" : "memorix.yml" });
|
|
5640
|
+
const llmModel = process.env.MEMORIX_LLM_MODEL || yml.llm?.model;
|
|
5641
|
+
if (llmModel) values.push({ key: "llm.model", value: llmModel, source: process.env.MEMORIX_LLM_MODEL ? "env" : "memorix.yml" });
|
|
5642
|
+
const llmKey = process.env.MEMORIX_LLM_API_KEY || process.env.MEMORIX_API_KEY || yml.llm?.apiKey || process.env.OPENAI_API_KEY;
|
|
5643
|
+
if (llmKey) {
|
|
5644
|
+
let src = "unknown";
|
|
5645
|
+
if (process.env.MEMORIX_LLM_API_KEY) src = "env:MEMORIX_LLM_API_KEY";
|
|
5646
|
+
else if (process.env.MEMORIX_API_KEY) src = "env:MEMORIX_API_KEY";
|
|
5647
|
+
else if (yml.llm?.apiKey) src = "memorix.yml (move to .env!)";
|
|
5648
|
+
else if (process.env.OPENAI_API_KEY) src = "env:OPENAI_API_KEY";
|
|
5649
|
+
values.push({ key: "llm.apiKey", value: "****" + llmKey.slice(-4), source: src, sensitive: true });
|
|
5650
|
+
} else {
|
|
5651
|
+
values.push({ key: "llm.apiKey", value: "not set", source: "none" });
|
|
5652
|
+
}
|
|
5653
|
+
const embProvider = process.env.MEMORIX_EMBEDDING || yml.embedding?.provider || "off";
|
|
5654
|
+
values.push({ key: "embedding.provider", value: embProvider, source: process.env.MEMORIX_EMBEDDING ? "env" : yml.embedding?.provider ? "memorix.yml" : "default" });
|
|
5655
|
+
values.push({ key: "git.autoHook", value: String(yml.git?.autoHook ?? false), source: yml.git?.autoHook !== void 0 ? "memorix.yml" : "default" });
|
|
5656
|
+
values.push({ key: "git.skipMergeCommits", value: String(yml.git?.skipMergeCommits ?? true), source: yml.git?.skipMergeCommits !== void 0 ? "memorix.yml" : "default" });
|
|
5657
|
+
if (yml.behavior?.formationMode) values.push({ key: "behavior.formationMode", value: yml.behavior.formationMode, source: "memorix.yml" });
|
|
5658
|
+
if (yml.behavior?.sessionInject) values.push({ key: "behavior.sessionInject", value: yml.behavior.sessionInject, source: "memorix.yml" });
|
|
5659
|
+
values.push({ key: "server.transport", value: yml.server?.transport || "stdio", source: yml.server?.transport ? "memorix.yml" : "default" });
|
|
5660
|
+
values.push({ key: "server.dashboard", value: String(yml.server?.dashboard ?? true), source: yml.server?.dashboard !== void 0 ? "memorix.yml" : "default" });
|
|
5661
|
+
sendJson(res, { files, values });
|
|
5662
|
+
break;
|
|
5663
|
+
}
|
|
5664
|
+
case "/identity": {
|
|
5665
|
+
const allObs = await loadObservationsJson(baseDir);
|
|
5666
|
+
const allProjectIds = [...new Set(allObs.map((o) => o.projectId).filter(Boolean))];
|
|
5667
|
+
const dirtyPatterns = [
|
|
5668
|
+
/^placeholder\//,
|
|
5669
|
+
/System32/i,
|
|
5670
|
+
/Microsoft VS Code/i,
|
|
5671
|
+
/node_modules/i,
|
|
5672
|
+
/\.vscode/i,
|
|
5673
|
+
/^local\/[A-Z]:\\/
|
|
5674
|
+
];
|
|
5675
|
+
const dirtyIds = allProjectIds.filter((id) => dirtyPatterns.some((p) => p.test(id)));
|
|
5676
|
+
let aliasGroups = [];
|
|
5677
|
+
let canonicalId = effectiveProjectId;
|
|
5678
|
+
try {
|
|
5679
|
+
const aliasModule = await Promise.resolve().then(() => (init_aliases(), aliases_exports));
|
|
5680
|
+
canonicalId = await aliasModule.getCanonicalId(effectiveProjectId);
|
|
5681
|
+
const { promises: fsP } = await import("fs");
|
|
5682
|
+
const registryPath = path7.join(baseDir, ".project-aliases.json");
|
|
5683
|
+
const raw = await fsP.readFile(registryPath, "utf-8");
|
|
5684
|
+
const registry = JSON.parse(raw);
|
|
5685
|
+
aliasGroups = registry.groups || [];
|
|
5686
|
+
} catch {
|
|
5687
|
+
}
|
|
5688
|
+
const currentGroup = aliasGroups.find((g) => g.aliases?.includes(effectiveProjectId) || g.canonical === effectiveProjectId);
|
|
5689
|
+
const aliases = currentGroup?.aliases || [effectiveProjectId];
|
|
5690
|
+
const hasDirtyIds = dirtyIds.length > 0;
|
|
5691
|
+
const hasMultipleUnmerged = allProjectIds.length > aliasGroups.length + 1;
|
|
5692
|
+
const isHealthy = !hasDirtyIds && !hasMultipleUnmerged;
|
|
5693
|
+
sendJson(res, {
|
|
5694
|
+
currentProjectId: effectiveProjectId,
|
|
5695
|
+
canonicalId,
|
|
5696
|
+
aliases,
|
|
5697
|
+
allProjectIds,
|
|
5698
|
+
dirtyIds,
|
|
5699
|
+
aliasGroups: aliasGroups.length,
|
|
5700
|
+
isHealthy,
|
|
5701
|
+
healthIssues: [
|
|
5702
|
+
...hasDirtyIds ? [`${dirtyIds.length} dirty project ID(s) detected`] : [],
|
|
5703
|
+
...hasMultipleUnmerged ? ["Possible unmerged project identity splits"] : []
|
|
5704
|
+
]
|
|
5705
|
+
});
|
|
5706
|
+
break;
|
|
5707
|
+
}
|
|
4175
5708
|
default: {
|
|
4176
5709
|
const deleteMatch = apiPath.match(/^\/observations\/(\d+)$/);
|
|
4177
5710
|
if (deleteMatch && req.method === "DELETE") {
|
|
@@ -4294,7 +5827,11 @@ async function startDashboard(dataDir, port, staticDir, projectId, projectName,
|
|
|
4294
5827
|
}
|
|
4295
5828
|
return;
|
|
4296
5829
|
}
|
|
4297
|
-
if (url.startsWith("/api/team")
|
|
5830
|
+
if (url.startsWith("/api/team")) {
|
|
5831
|
+
if (!teamInstances) {
|
|
5832
|
+
sendJson(res, { unavailable: true, reason: "http-transport-required" });
|
|
5833
|
+
return;
|
|
5834
|
+
}
|
|
4298
5835
|
try {
|
|
4299
5836
|
teamInstances.fileLocks.cleanExpired();
|
|
4300
5837
|
const agents = teamInstances.registry.listAgents();
|
|
@@ -4945,7 +6482,7 @@ __export(persistence_exports2, {
|
|
|
4945
6482
|
TeamPersistence: () => TeamPersistence
|
|
4946
6483
|
});
|
|
4947
6484
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
4948
|
-
import { statSync, renameSync as renameSync2, existsSync as
|
|
6485
|
+
import { statSync as statSync2, renameSync as renameSync2, existsSync as existsSync8 } from "fs";
|
|
4949
6486
|
import { dirname as dirname2 } from "path";
|
|
4950
6487
|
var TeamPersistence;
|
|
4951
6488
|
var init_persistence2 = __esm({
|
|
@@ -4964,8 +6501,8 @@ var init_persistence2 = __esm({
|
|
|
4964
6501
|
/** Reload state from disk if the file changed since last read */
|
|
4965
6502
|
async sync() {
|
|
4966
6503
|
try {
|
|
4967
|
-
if (!
|
|
4968
|
-
const st =
|
|
6504
|
+
if (!existsSync8(this.filePath)) return;
|
|
6505
|
+
const st = statSync2(this.filePath);
|
|
4969
6506
|
if (st.mtimeMs <= this.lastMtimeMs) return;
|
|
4970
6507
|
} catch {
|
|
4971
6508
|
return;
|
|
@@ -4979,7 +6516,7 @@ var init_persistence2 = __esm({
|
|
|
4979
6516
|
this.taskManager.hydrate(snap.tasks);
|
|
4980
6517
|
this.fileLocks.hydrate(snap.locks);
|
|
4981
6518
|
try {
|
|
4982
|
-
this.lastMtimeMs =
|
|
6519
|
+
this.lastMtimeMs = statSync2(this.filePath).mtimeMs;
|
|
4983
6520
|
} catch {
|
|
4984
6521
|
}
|
|
4985
6522
|
} catch {
|
|
@@ -5009,7 +6546,7 @@ var init_persistence2 = __esm({
|
|
|
5009
6546
|
}
|
|
5010
6547
|
}
|
|
5011
6548
|
try {
|
|
5012
|
-
this.lastMtimeMs =
|
|
6549
|
+
this.lastMtimeMs = statSync2(this.filePath).mtimeMs;
|
|
5013
6550
|
} catch {
|
|
5014
6551
|
}
|
|
5015
6552
|
}
|
|
@@ -5017,17 +6554,118 @@ var init_persistence2 = __esm({
|
|
|
5017
6554
|
}
|
|
5018
6555
|
});
|
|
5019
6556
|
|
|
6557
|
+
// src/audit/index.ts
|
|
6558
|
+
var audit_exports = {};
|
|
6559
|
+
__export(audit_exports, {
|
|
6560
|
+
getAllAuditEntries: () => getAllAuditEntries,
|
|
6561
|
+
getProjectFiles: () => getProjectFiles,
|
|
6562
|
+
getProjectId: () => getProjectId,
|
|
6563
|
+
loadAudit: () => loadAudit,
|
|
6564
|
+
recordFile: () => recordFile,
|
|
6565
|
+
removeFile: () => removeFile,
|
|
6566
|
+
saveAudit: () => saveAudit
|
|
6567
|
+
});
|
|
6568
|
+
import * as fs6 from "fs/promises";
|
|
6569
|
+
import * as path8 from "path";
|
|
6570
|
+
import { homedir as homedir18 } from "os";
|
|
6571
|
+
async function loadAudit() {
|
|
6572
|
+
try {
|
|
6573
|
+
const content = await fs6.readFile(AUDIT_FILE, "utf-8");
|
|
6574
|
+
return JSON.parse(content);
|
|
6575
|
+
} catch {
|
|
6576
|
+
return {
|
|
6577
|
+
version: "1.0.0",
|
|
6578
|
+
projects: {}
|
|
6579
|
+
};
|
|
6580
|
+
}
|
|
6581
|
+
}
|
|
6582
|
+
async function saveAudit(data) {
|
|
6583
|
+
const dir = path8.dirname(AUDIT_FILE);
|
|
6584
|
+
await fs6.mkdir(dir, { recursive: true });
|
|
6585
|
+
await fs6.writeFile(AUDIT_FILE, JSON.stringify(data, null, 2), "utf-8");
|
|
6586
|
+
}
|
|
6587
|
+
function getProjectId(projectRoot) {
|
|
6588
|
+
try {
|
|
6589
|
+
const { detectProject: detectProject2 } = (init_detector(), __toCommonJS(detector_exports));
|
|
6590
|
+
const project = detectProject2(projectRoot);
|
|
6591
|
+
if (project) return project.id;
|
|
6592
|
+
} catch {
|
|
6593
|
+
}
|
|
6594
|
+
const normalized = projectRoot.replace(/\\/g, "/");
|
|
6595
|
+
return `untracked/${normalized}`;
|
|
6596
|
+
}
|
|
6597
|
+
async function recordFile(projectRoot, type, filePath, agent) {
|
|
6598
|
+
const data = await loadAudit();
|
|
6599
|
+
const projectId = getProjectId(projectRoot);
|
|
6600
|
+
if (!data.projects[projectId]) {
|
|
6601
|
+
data.projects[projectId] = {
|
|
6602
|
+
projectRoot,
|
|
6603
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6604
|
+
entries: []
|
|
6605
|
+
};
|
|
6606
|
+
}
|
|
6607
|
+
const existingIndex = data.projects[projectId].entries.findIndex(
|
|
6608
|
+
(e) => e.path === filePath
|
|
6609
|
+
);
|
|
6610
|
+
if (existingIndex === -1) {
|
|
6611
|
+
data.projects[projectId].entries.push({
|
|
6612
|
+
type,
|
|
6613
|
+
agent,
|
|
6614
|
+
path: filePath,
|
|
6615
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6616
|
+
});
|
|
6617
|
+
}
|
|
6618
|
+
await saveAudit(data);
|
|
6619
|
+
}
|
|
6620
|
+
async function getProjectFiles(projectRoot) {
|
|
6621
|
+
const data = await loadAudit();
|
|
6622
|
+
const projectId = getProjectId(projectRoot);
|
|
6623
|
+
return data.projects[projectId]?.entries || [];
|
|
6624
|
+
}
|
|
6625
|
+
async function removeFile(projectRoot, filePath) {
|
|
6626
|
+
const data = await loadAudit();
|
|
6627
|
+
const projectId = getProjectId(projectRoot);
|
|
6628
|
+
if (!data.projects[projectId]) return;
|
|
6629
|
+
data.projects[projectId].entries = data.projects[projectId].entries.filter(
|
|
6630
|
+
(e) => e.path !== filePath
|
|
6631
|
+
);
|
|
6632
|
+
if (data.projects[projectId].entries.length === 0) {
|
|
6633
|
+
delete data.projects[projectId];
|
|
6634
|
+
}
|
|
6635
|
+
await saveAudit(data);
|
|
6636
|
+
}
|
|
6637
|
+
async function getAllAuditEntries() {
|
|
6638
|
+
const data = await loadAudit();
|
|
6639
|
+
const entries = [];
|
|
6640
|
+
for (const [projectId, project] of Object.entries(data.projects)) {
|
|
6641
|
+
for (const entry of project.entries) {
|
|
6642
|
+
entries.push({ projectId, entry });
|
|
6643
|
+
}
|
|
6644
|
+
}
|
|
6645
|
+
return entries;
|
|
6646
|
+
}
|
|
6647
|
+
var AUDIT_FILE;
|
|
6648
|
+
var init_audit = __esm({
|
|
6649
|
+
"src/audit/index.ts"() {
|
|
6650
|
+
"use strict";
|
|
6651
|
+
init_esm_shims();
|
|
6652
|
+
AUDIT_FILE = path8.join(homedir18(), ".memorix", "audit.json");
|
|
6653
|
+
}
|
|
6654
|
+
});
|
|
6655
|
+
|
|
5020
6656
|
// src/hooks/installers/index.ts
|
|
5021
6657
|
var installers_exports = {};
|
|
5022
6658
|
__export(installers_exports, {
|
|
5023
6659
|
detectInstalledAgents: () => detectInstalledAgents,
|
|
6660
|
+
getGlobalConfigPath: () => getGlobalConfigPath,
|
|
5024
6661
|
getHookStatus: () => getHookStatus,
|
|
6662
|
+
getProjectConfigPath: () => getProjectConfigPath,
|
|
5025
6663
|
installHooks: () => installHooks,
|
|
5026
6664
|
uninstallHooks: () => uninstallHooks
|
|
5027
6665
|
});
|
|
5028
|
-
import * as
|
|
5029
|
-
import * as
|
|
5030
|
-
import * as
|
|
6666
|
+
import * as fs7 from "fs/promises";
|
|
6667
|
+
import * as path9 from "path";
|
|
6668
|
+
import * as os3 from "os";
|
|
5031
6669
|
function resolveHookCommand() {
|
|
5032
6670
|
if (process.platform === "win32") {
|
|
5033
6671
|
return "memorix.cmd";
|
|
@@ -5261,101 +6899,101 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
|
|
|
5261
6899
|
function getProjectConfigPath(agent, projectRoot) {
|
|
5262
6900
|
switch (agent) {
|
|
5263
6901
|
case "claude":
|
|
5264
|
-
return
|
|
6902
|
+
return path9.join(projectRoot, ".claude", "settings.local.json");
|
|
5265
6903
|
case "copilot":
|
|
5266
|
-
return
|
|
6904
|
+
return path9.join(projectRoot, ".github", "hooks", "memorix.json");
|
|
5267
6905
|
case "windsurf":
|
|
5268
|
-
return
|
|
6906
|
+
return path9.join(projectRoot, ".windsurf", "hooks.json");
|
|
5269
6907
|
case "cursor":
|
|
5270
|
-
return
|
|
6908
|
+
return path9.join(projectRoot, ".cursor", "hooks.json");
|
|
5271
6909
|
case "kiro":
|
|
5272
|
-
return
|
|
6910
|
+
return path9.join(projectRoot, ".kiro", "hooks", "memorix-agent-stop.kiro.hook");
|
|
5273
6911
|
case "codex":
|
|
5274
|
-
return
|
|
6912
|
+
return path9.join(projectRoot, "AGENTS.md");
|
|
5275
6913
|
case "trae":
|
|
5276
|
-
return
|
|
6914
|
+
return path9.join(projectRoot, ".trae", "rules", "project_rules.md");
|
|
5277
6915
|
case "opencode":
|
|
5278
|
-
return
|
|
6916
|
+
return path9.join(projectRoot, ".opencode", "plugins", "memorix.js");
|
|
5279
6917
|
case "antigravity":
|
|
5280
|
-
return
|
|
6918
|
+
return path9.join(projectRoot, ".gemini", "settings.json");
|
|
5281
6919
|
default:
|
|
5282
|
-
return
|
|
6920
|
+
return path9.join(projectRoot, ".memorix", "hooks.json");
|
|
5283
6921
|
}
|
|
5284
6922
|
}
|
|
5285
6923
|
function getGlobalConfigPath(agent) {
|
|
5286
|
-
const home =
|
|
6924
|
+
const home = os3.homedir();
|
|
5287
6925
|
switch (agent) {
|
|
5288
6926
|
case "claude":
|
|
5289
6927
|
case "copilot":
|
|
5290
|
-
return
|
|
6928
|
+
return path9.join(home, ".claude", "settings.json");
|
|
5291
6929
|
case "windsurf":
|
|
5292
|
-
return
|
|
6930
|
+
return path9.join(home, ".codeium", "windsurf", "hooks.json");
|
|
5293
6931
|
case "cursor":
|
|
5294
|
-
return
|
|
6932
|
+
return path9.join(home, ".cursor", "hooks.json");
|
|
5295
6933
|
case "antigravity":
|
|
5296
|
-
return
|
|
6934
|
+
return path9.join(home, ".gemini", "settings.json");
|
|
5297
6935
|
case "opencode":
|
|
5298
|
-
return
|
|
6936
|
+
return path9.join(home, ".config", "opencode", "plugins", "memorix.js");
|
|
5299
6937
|
case "trae":
|
|
5300
|
-
return
|
|
6938
|
+
return path9.join(home, ".trae", "rules", "project_rules.md");
|
|
5301
6939
|
default:
|
|
5302
|
-
return
|
|
6940
|
+
return path9.join(home, ".memorix", "hooks.json");
|
|
5303
6941
|
}
|
|
5304
6942
|
}
|
|
5305
6943
|
async function detectInstalledAgents() {
|
|
5306
6944
|
const agents = [];
|
|
5307
|
-
const home =
|
|
5308
|
-
const claudeDir =
|
|
6945
|
+
const home = os3.homedir();
|
|
6946
|
+
const claudeDir = path9.join(home, ".claude");
|
|
5309
6947
|
try {
|
|
5310
|
-
await
|
|
6948
|
+
await fs7.access(claudeDir);
|
|
5311
6949
|
agents.push("claude");
|
|
5312
6950
|
} catch {
|
|
5313
6951
|
}
|
|
5314
|
-
const windsurfDir =
|
|
6952
|
+
const windsurfDir = path9.join(home, ".codeium", "windsurf");
|
|
5315
6953
|
try {
|
|
5316
|
-
await
|
|
6954
|
+
await fs7.access(windsurfDir);
|
|
5317
6955
|
agents.push("windsurf");
|
|
5318
6956
|
} catch {
|
|
5319
6957
|
}
|
|
5320
|
-
const cursorDir =
|
|
6958
|
+
const cursorDir = path9.join(home, ".cursor");
|
|
5321
6959
|
try {
|
|
5322
|
-
await
|
|
6960
|
+
await fs7.access(cursorDir);
|
|
5323
6961
|
agents.push("cursor");
|
|
5324
6962
|
} catch {
|
|
5325
6963
|
}
|
|
5326
|
-
const vscodeDir =
|
|
6964
|
+
const vscodeDir = path9.join(home, ".vscode");
|
|
5327
6965
|
try {
|
|
5328
|
-
await
|
|
6966
|
+
await fs7.access(vscodeDir);
|
|
5329
6967
|
agents.push("copilot");
|
|
5330
6968
|
} catch {
|
|
5331
6969
|
}
|
|
5332
|
-
const kiroConfig =
|
|
6970
|
+
const kiroConfig = path9.join(home, ".kiro");
|
|
5333
6971
|
try {
|
|
5334
|
-
await
|
|
6972
|
+
await fs7.access(kiroConfig);
|
|
5335
6973
|
agents.push("kiro");
|
|
5336
6974
|
} catch {
|
|
5337
6975
|
}
|
|
5338
|
-
const codexDir =
|
|
6976
|
+
const codexDir = path9.join(home, ".codex");
|
|
5339
6977
|
try {
|
|
5340
|
-
await
|
|
6978
|
+
await fs7.access(codexDir);
|
|
5341
6979
|
agents.push("codex");
|
|
5342
6980
|
} catch {
|
|
5343
6981
|
}
|
|
5344
|
-
const geminiDir =
|
|
6982
|
+
const geminiDir = path9.join(home, ".gemini");
|
|
5345
6983
|
try {
|
|
5346
|
-
await
|
|
6984
|
+
await fs7.access(geminiDir);
|
|
5347
6985
|
agents.push("antigravity");
|
|
5348
6986
|
} catch {
|
|
5349
6987
|
}
|
|
5350
|
-
const opencodeDir =
|
|
6988
|
+
const opencodeDir = path9.join(home, ".config", "opencode");
|
|
5351
6989
|
try {
|
|
5352
|
-
await
|
|
6990
|
+
await fs7.access(opencodeDir);
|
|
5353
6991
|
agents.push("opencode");
|
|
5354
6992
|
} catch {
|
|
5355
6993
|
}
|
|
5356
|
-
const traeDir =
|
|
6994
|
+
const traeDir = path9.join(home, ".trae");
|
|
5357
6995
|
try {
|
|
5358
|
-
await
|
|
6996
|
+
await fs7.access(traeDir);
|
|
5359
6997
|
agents.push("trae");
|
|
5360
6998
|
} catch {
|
|
5361
6999
|
}
|
|
@@ -5402,8 +7040,13 @@ async function installHooks(agent, projectRoot, global = false) {
|
|
|
5402
7040
|
case "opencode": {
|
|
5403
7041
|
const pluginContent = generateOpenCodePlugin();
|
|
5404
7042
|
const pluginPath = global ? getGlobalConfigPath(agent) : getProjectConfigPath(agent, projectRoot);
|
|
5405
|
-
await
|
|
5406
|
-
await
|
|
7043
|
+
await fs7.mkdir(path9.dirname(pluginPath), { recursive: true });
|
|
7044
|
+
await fs7.writeFile(pluginPath, pluginContent, "utf-8");
|
|
7045
|
+
try {
|
|
7046
|
+
const { recordFile: recordFile2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
7047
|
+
await recordFile2(projectRoot, "hook", pluginPath, agent);
|
|
7048
|
+
} catch {
|
|
7049
|
+
}
|
|
5407
7050
|
await installAgentRules(agent, projectRoot);
|
|
5408
7051
|
return {
|
|
5409
7052
|
agent,
|
|
@@ -5415,18 +7058,24 @@ async function installHooks(agent, projectRoot, global = false) {
|
|
|
5415
7058
|
default:
|
|
5416
7059
|
generated = generateClaudeConfig();
|
|
5417
7060
|
}
|
|
5418
|
-
await
|
|
7061
|
+
await fs7.mkdir(path9.dirname(configPath), { recursive: true });
|
|
5419
7062
|
if (agent === "kiro") {
|
|
5420
7063
|
const hookFiles = generateKiroHookFiles();
|
|
5421
|
-
const hooksDir =
|
|
5422
|
-
await
|
|
7064
|
+
const hooksDir = path9.join(path9.dirname(configPath));
|
|
7065
|
+
await fs7.mkdir(hooksDir, { recursive: true });
|
|
5423
7066
|
for (const hf of hookFiles) {
|
|
5424
|
-
|
|
7067
|
+
const hookPath = path9.join(hooksDir, hf.filename);
|
|
7068
|
+
await fs7.writeFile(hookPath, hf.content, "utf-8");
|
|
7069
|
+
try {
|
|
7070
|
+
const { recordFile: recordFile2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
7071
|
+
await recordFile2(projectRoot, "hook", hookPath, agent);
|
|
7072
|
+
} catch {
|
|
7073
|
+
}
|
|
5425
7074
|
}
|
|
5426
7075
|
} else {
|
|
5427
7076
|
let existing = {};
|
|
5428
7077
|
try {
|
|
5429
|
-
const content = await
|
|
7078
|
+
const content = await fs7.readFile(configPath, "utf-8");
|
|
5430
7079
|
existing = JSON.parse(content);
|
|
5431
7080
|
} catch {
|
|
5432
7081
|
}
|
|
@@ -5458,7 +7107,12 @@ async function installHooks(agent, projectRoot, global = false) {
|
|
|
5458
7107
|
const h = merged.hooks;
|
|
5459
7108
|
if (h) delete h.preToolUse;
|
|
5460
7109
|
}
|
|
5461
|
-
await
|
|
7110
|
+
await fs7.writeFile(configPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
7111
|
+
try {
|
|
7112
|
+
const { recordFile: recordFile2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
7113
|
+
await recordFile2(projectRoot, "hook", configPath, agent);
|
|
7114
|
+
} catch {
|
|
7115
|
+
}
|
|
5462
7116
|
}
|
|
5463
7117
|
const events = [];
|
|
5464
7118
|
switch (agent) {
|
|
@@ -5494,51 +7148,61 @@ async function installAgentRules(agent, projectRoot) {
|
|
|
5494
7148
|
let rulesPath;
|
|
5495
7149
|
switch (agent) {
|
|
5496
7150
|
case "windsurf":
|
|
5497
|
-
rulesPath =
|
|
7151
|
+
rulesPath = path9.join(projectRoot, ".windsurf", "rules", "memorix.md");
|
|
5498
7152
|
break;
|
|
5499
7153
|
case "cursor":
|
|
5500
|
-
rulesPath =
|
|
7154
|
+
rulesPath = path9.join(projectRoot, ".cursor", "rules", "memorix.mdc");
|
|
5501
7155
|
break;
|
|
5502
7156
|
case "claude":
|
|
5503
7157
|
case "copilot":
|
|
5504
|
-
rulesPath =
|
|
7158
|
+
rulesPath = path9.join(projectRoot, ".github", "copilot-instructions.md");
|
|
5505
7159
|
break;
|
|
5506
7160
|
case "codex":
|
|
5507
|
-
rulesPath =
|
|
7161
|
+
rulesPath = path9.join(projectRoot, "AGENTS.md");
|
|
5508
7162
|
break;
|
|
5509
7163
|
case "kiro":
|
|
5510
|
-
rulesPath =
|
|
7164
|
+
rulesPath = path9.join(projectRoot, ".kiro", "steering", "memorix.md");
|
|
5511
7165
|
break;
|
|
5512
7166
|
case "opencode":
|
|
5513
|
-
rulesPath =
|
|
7167
|
+
rulesPath = path9.join(projectRoot, "AGENTS.md");
|
|
5514
7168
|
break;
|
|
5515
7169
|
case "antigravity":
|
|
5516
|
-
rulesPath =
|
|
7170
|
+
rulesPath = path9.join(projectRoot, "GEMINI.md");
|
|
5517
7171
|
break;
|
|
5518
7172
|
case "trae":
|
|
5519
|
-
rulesPath =
|
|
7173
|
+
rulesPath = path9.join(projectRoot, ".trae", "rules", "project_rules.md");
|
|
5520
7174
|
break;
|
|
5521
7175
|
default:
|
|
5522
|
-
rulesPath =
|
|
7176
|
+
rulesPath = path9.join(projectRoot, ".agent", "rules", "memorix.md");
|
|
5523
7177
|
break;
|
|
5524
7178
|
}
|
|
5525
7179
|
try {
|
|
5526
|
-
await
|
|
7180
|
+
await fs7.mkdir(path9.dirname(rulesPath), { recursive: true });
|
|
5527
7181
|
if (agent === "codex" || agent === "opencode" || agent === "antigravity") {
|
|
5528
7182
|
try {
|
|
5529
|
-
const existing = await
|
|
7183
|
+
const existing = await fs7.readFile(rulesPath, "utf-8");
|
|
5530
7184
|
if (existing.includes("Memorix")) {
|
|
5531
7185
|
return;
|
|
5532
7186
|
}
|
|
5533
|
-
await
|
|
7187
|
+
await fs7.writeFile(rulesPath, existing + "\n\n" + rulesContent, "utf-8");
|
|
7188
|
+
try {
|
|
7189
|
+
const { recordFile: recordFile2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
7190
|
+
await recordFile2(projectRoot, "rule", rulesPath, agent);
|
|
7191
|
+
} catch {
|
|
7192
|
+
}
|
|
5534
7193
|
} catch {
|
|
5535
|
-
await
|
|
7194
|
+
await fs7.writeFile(rulesPath, rulesContent, "utf-8");
|
|
5536
7195
|
}
|
|
5537
7196
|
} else {
|
|
5538
7197
|
try {
|
|
5539
|
-
await
|
|
7198
|
+
await fs7.access(rulesPath);
|
|
5540
7199
|
} catch {
|
|
5541
|
-
await
|
|
7200
|
+
await fs7.writeFile(rulesPath, rulesContent, "utf-8");
|
|
7201
|
+
try {
|
|
7202
|
+
const { recordFile: recordFile2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
7203
|
+
await recordFile2(projectRoot, "rule", rulesPath, agent);
|
|
7204
|
+
} catch {
|
|
7205
|
+
}
|
|
5542
7206
|
}
|
|
5543
7207
|
}
|
|
5544
7208
|
} catch {
|
|
@@ -5670,15 +7334,15 @@ async function uninstallHooks(agent, projectRoot, global = false) {
|
|
|
5670
7334
|
const configPath = global ? getGlobalConfigPath(agent) : getProjectConfigPath(agent, projectRoot);
|
|
5671
7335
|
try {
|
|
5672
7336
|
if (agent === "kiro") {
|
|
5673
|
-
await
|
|
7337
|
+
await fs7.unlink(configPath);
|
|
5674
7338
|
} else {
|
|
5675
|
-
const content = await
|
|
7339
|
+
const content = await fs7.readFile(configPath, "utf-8");
|
|
5676
7340
|
const config = JSON.parse(content);
|
|
5677
7341
|
delete config.hooks;
|
|
5678
7342
|
if (Object.keys(config).length === 0) {
|
|
5679
|
-
await
|
|
7343
|
+
await fs7.unlink(configPath);
|
|
5680
7344
|
} else {
|
|
5681
|
-
await
|
|
7345
|
+
await fs7.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
5682
7346
|
}
|
|
5683
7347
|
}
|
|
5684
7348
|
return true;
|
|
@@ -5695,11 +7359,11 @@ async function getHookStatus(projectRoot) {
|
|
|
5695
7359
|
let installed = false;
|
|
5696
7360
|
let usedPath = projectPath;
|
|
5697
7361
|
try {
|
|
5698
|
-
await
|
|
7362
|
+
await fs7.access(projectPath);
|
|
5699
7363
|
installed = true;
|
|
5700
7364
|
} catch {
|
|
5701
7365
|
try {
|
|
5702
|
-
await
|
|
7366
|
+
await fs7.access(globalPath);
|
|
5703
7367
|
installed = true;
|
|
5704
7368
|
usedPath = globalPath;
|
|
5705
7369
|
} catch {
|
|
@@ -5716,6 +7380,60 @@ var init_installers = __esm({
|
|
|
5716
7380
|
}
|
|
5717
7381
|
});
|
|
5718
7382
|
|
|
7383
|
+
// src/git/hooks-path.ts
|
|
7384
|
+
var hooks_path_exports = {};
|
|
7385
|
+
__export(hooks_path_exports, {
|
|
7386
|
+
ensureHooksDir: () => ensureHooksDir,
|
|
7387
|
+
resolveGitDir: () => resolveGitDir,
|
|
7388
|
+
resolveHooksDir: () => resolveHooksDir
|
|
7389
|
+
});
|
|
7390
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, statSync as statSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
7391
|
+
import path10 from "path";
|
|
7392
|
+
function resolveGitDir(projectRoot) {
|
|
7393
|
+
const dotGit = path10.join(projectRoot, ".git");
|
|
7394
|
+
if (!existsSync9(dotGit)) return null;
|
|
7395
|
+
const stat = statSync3(dotGit);
|
|
7396
|
+
if (stat.isDirectory()) {
|
|
7397
|
+
return dotGit;
|
|
7398
|
+
}
|
|
7399
|
+
if (stat.isFile()) {
|
|
7400
|
+
try {
|
|
7401
|
+
const content = readFileSync8(dotGit, "utf-8").trim();
|
|
7402
|
+
const match = content.match(/^gitdir:\s*(.+)$/);
|
|
7403
|
+
if (match) {
|
|
7404
|
+
const gitdir = match[1].trim();
|
|
7405
|
+
const resolved = path10.isAbsolute(gitdir) ? gitdir : path10.resolve(projectRoot, gitdir);
|
|
7406
|
+
if (existsSync9(resolved)) {
|
|
7407
|
+
return resolved;
|
|
7408
|
+
}
|
|
7409
|
+
}
|
|
7410
|
+
} catch {
|
|
7411
|
+
}
|
|
7412
|
+
}
|
|
7413
|
+
return null;
|
|
7414
|
+
}
|
|
7415
|
+
function resolveHooksDir(projectRoot) {
|
|
7416
|
+
const gitDir = resolveGitDir(projectRoot);
|
|
7417
|
+
if (!gitDir) return null;
|
|
7418
|
+
const hooksDir = path10.join(gitDir, "hooks");
|
|
7419
|
+
return {
|
|
7420
|
+
hooksDir,
|
|
7421
|
+
hookPath: path10.join(hooksDir, "post-commit")
|
|
7422
|
+
};
|
|
7423
|
+
}
|
|
7424
|
+
function ensureHooksDir(projectRoot) {
|
|
7425
|
+
const resolved = resolveHooksDir(projectRoot);
|
|
7426
|
+
if (!resolved) return null;
|
|
7427
|
+
mkdirSync4(resolved.hooksDir, { recursive: true });
|
|
7428
|
+
return resolved;
|
|
7429
|
+
}
|
|
7430
|
+
var init_hooks_path = __esm({
|
|
7431
|
+
"src/git/hooks-path.ts"() {
|
|
7432
|
+
"use strict";
|
|
7433
|
+
init_esm_shims();
|
|
7434
|
+
}
|
|
7435
|
+
});
|
|
7436
|
+
|
|
5719
7437
|
// src/index.ts
|
|
5720
7438
|
init_esm_shims();
|
|
5721
7439
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -5841,6 +7559,10 @@ var KnowledgeGraphManager = class {
|
|
|
5841
7559
|
);
|
|
5842
7560
|
await this.save();
|
|
5843
7561
|
}
|
|
7562
|
+
/** Get all entity names (for Formation Pipeline entity resolution) */
|
|
7563
|
+
getEntityNames() {
|
|
7564
|
+
return this.entities.map((e) => e.name);
|
|
7565
|
+
}
|
|
5844
7566
|
/** Read the entire graph */
|
|
5845
7567
|
async readGraph() {
|
|
5846
7568
|
await this.init();
|
|
@@ -5946,38 +7668,38 @@ init_observations();
|
|
|
5946
7668
|
|
|
5947
7669
|
// src/compact/index-format.ts
|
|
5948
7670
|
init_esm_shims();
|
|
5949
|
-
function formatIndexTable(entries, query) {
|
|
7671
|
+
function formatIndexTable(entries, query, forceProjectColumn = false) {
|
|
5950
7672
|
if (entries.length === 0) {
|
|
5951
7673
|
return query ? `No observations found matching "${query}".` : "No observations found.";
|
|
5952
7674
|
}
|
|
5953
7675
|
const lines = [];
|
|
5954
7676
|
if (query) {
|
|
5955
|
-
lines.push(`Found ${entries.length} observation(s) matching "${query}"
|
|
5956
|
-
|
|
7677
|
+
lines.push(`Found ${entries.length} observation(s) matching "${query}":`);
|
|
7678
|
+
lines.push("");
|
|
7679
|
+
}
|
|
7680
|
+
const distinctProjects = [...new Set(entries.map((entry) => entry.projectId).filter(Boolean))];
|
|
7681
|
+
const hasProject = forceProjectColumn || distinctProjects.length > 1;
|
|
7682
|
+
const hasExplanation = entries.some((entry) => (entry.matchedFields?.length ?? 0) > 0);
|
|
7683
|
+
const header = ["ID", "Time", "T", "Title", "Tokens"];
|
|
7684
|
+
const divider = ["----", "------", "---", "-------", "--------"];
|
|
7685
|
+
if (hasProject) {
|
|
7686
|
+
header.push("Project");
|
|
7687
|
+
divider.push("---------");
|
|
5957
7688
|
}
|
|
5958
|
-
lines.push("| ID | Time | T | Title | Tokens |");
|
|
5959
|
-
lines.push("|----|------|---|-------|--------|");
|
|
5960
|
-
const hasExplanation = entries.some((e) => e["matchedFields"]);
|
|
5961
7689
|
if (hasExplanation) {
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
lines.push("| ID | Time | T | Title | Tokens | Matched |");
|
|
5965
|
-
lines.push("|----|------|---|-------|--------|---------|");
|
|
7690
|
+
header.push("Matched");
|
|
7691
|
+
divider.push("---------");
|
|
5966
7692
|
}
|
|
7693
|
+
lines.push(`| ${header.join(" | ")} |`);
|
|
7694
|
+
lines.push(`|${divider.map((part) => ` ${part} `).join("|")}|`);
|
|
5967
7695
|
for (const entry of entries) {
|
|
5968
|
-
const
|
|
5969
|
-
if (
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
);
|
|
5973
|
-
} else {
|
|
5974
|
-
lines.push(
|
|
5975
|
-
`| #${entry.id} | ${entry.time} | ${entry.icon} | ${entry.title} | ~${entry.tokens} |`
|
|
5976
|
-
);
|
|
5977
|
-
}
|
|
7696
|
+
const row = [`#${entry.id}`, entry.time, entry.icon, entry.title, `~${entry.tokens}`];
|
|
7697
|
+
if (hasProject) row.push(entry.projectId ?? "-");
|
|
7698
|
+
if (hasExplanation) row.push(entry.matchedFields?.join(", ") ?? "-");
|
|
7699
|
+
lines.push(`| ${row.join(" | ")} |`);
|
|
5978
7700
|
}
|
|
5979
7701
|
lines.push("");
|
|
5980
|
-
lines.push(
|
|
7702
|
+
lines.push(getProgressiveDisclosureHint(hasProject));
|
|
5981
7703
|
return lines.join("\n");
|
|
5982
7704
|
}
|
|
5983
7705
|
function formatTimeline(timeline) {
|
|
@@ -5985,8 +7707,8 @@ function formatTimeline(timeline) {
|
|
|
5985
7707
|
return `Observation #${timeline.anchorId} not found.`;
|
|
5986
7708
|
}
|
|
5987
7709
|
const lines = [];
|
|
5988
|
-
lines.push(`Timeline around #${timeline.anchorId}
|
|
5989
|
-
|
|
7710
|
+
lines.push(`Timeline around #${timeline.anchorId}:`);
|
|
7711
|
+
lines.push("");
|
|
5990
7712
|
if (timeline.before.length > 0) {
|
|
5991
7713
|
lines.push("**Before:**");
|
|
5992
7714
|
lines.push("| ID | Time | T | Title | Tokens |");
|
|
@@ -5996,11 +7718,11 @@ function formatTimeline(timeline) {
|
|
|
5996
7718
|
}
|
|
5997
7719
|
lines.push("");
|
|
5998
7720
|
}
|
|
5999
|
-
lines.push("
|
|
7721
|
+
lines.push("**Anchor:**");
|
|
6000
7722
|
lines.push("| ID | Time | T | Title | Tokens |");
|
|
6001
7723
|
lines.push("|----|------|---|-------|--------|");
|
|
6002
|
-
const
|
|
6003
|
-
lines.push(`| #${
|
|
7724
|
+
const anchor = timeline.anchorEntry;
|
|
7725
|
+
lines.push(`| #${anchor.id} | ${anchor.time} | ${anchor.icon} | ${anchor.title} | ~${anchor.tokens} |`);
|
|
6004
7726
|
lines.push("");
|
|
6005
7727
|
if (timeline.after.length > 0) {
|
|
6006
7728
|
lines.push("**After:**");
|
|
@@ -6011,14 +7733,14 @@ function formatTimeline(timeline) {
|
|
|
6011
7733
|
}
|
|
6012
7734
|
lines.push("");
|
|
6013
7735
|
}
|
|
6014
|
-
lines.push(
|
|
7736
|
+
lines.push(getProgressiveDisclosureHint(false));
|
|
6015
7737
|
return lines.join("\n");
|
|
6016
7738
|
}
|
|
6017
7739
|
function formatObservationDetail(doc) {
|
|
6018
7740
|
const icon = getTypeIcon(doc.type);
|
|
6019
7741
|
const lines = [];
|
|
6020
7742
|
lines.push(`#${doc.observationId} ${icon} ${doc.title}`);
|
|
6021
|
-
lines.push("
|
|
7743
|
+
lines.push("=".repeat(50));
|
|
6022
7744
|
lines.push(`Date: ${new Date(doc.createdAt).toLocaleString()}`);
|
|
6023
7745
|
lines.push(`Type: ${doc.type}`);
|
|
6024
7746
|
lines.push(`Entity: ${doc.entityName}`);
|
|
@@ -6057,20 +7779,29 @@ function getTypeIcon(type) {
|
|
|
6057
7779
|
"discovery": "\u{1F7E3}",
|
|
6058
7780
|
"why-it-exists": "\u{1F7E0}",
|
|
6059
7781
|
"decision": "\u{1F7E4}",
|
|
6060
|
-
"trade-off": "\u2696\uFE0F"
|
|
7782
|
+
"trade-off": "\u2696\uFE0F",
|
|
7783
|
+
"reasoning": "\u{1F9E0}"
|
|
6061
7784
|
};
|
|
6062
7785
|
return icons[type] ?? "\u2753";
|
|
6063
7786
|
}
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
-
|
|
7787
|
+
function getProgressiveDisclosureHint(hasProject) {
|
|
7788
|
+
const lines = [
|
|
7789
|
+
"\u{1F4A1} **Progressive Disclosure:** This index shows WHAT exists and retrieval COST.",
|
|
7790
|
+
"- Use `memorix_detail` to fetch full observation details by ID",
|
|
7791
|
+
"- Use `memorix_timeline` to see chronological context around an observation",
|
|
7792
|
+
"- Critical types (\u{1F534} gotcha, \u{1F7E4} decision, \u2696\uFE0F trade-off) are often worth fetching immediately"
|
|
7793
|
+
];
|
|
7794
|
+
if (hasProject) {
|
|
7795
|
+
lines.push("- For global results, prefer `memorix_detail refs=[{ id, projectId }]` to avoid cross-project ID ambiguity");
|
|
7796
|
+
}
|
|
7797
|
+
return lines.join("\n");
|
|
7798
|
+
}
|
|
6068
7799
|
|
|
6069
7800
|
// src/compact/engine.ts
|
|
6070
7801
|
init_token_budget();
|
|
6071
7802
|
async function compactSearch(options) {
|
|
6072
7803
|
const entries = await searchObservations(options);
|
|
6073
|
-
const formatted = formatIndexTable(entries, options.query);
|
|
7804
|
+
const formatted = formatIndexTable(entries, options.query, !options.projectId);
|
|
6074
7805
|
const totalTokens = countTextTokens(formatted);
|
|
6075
7806
|
return { entries, formatted, totalTokens };
|
|
6076
7807
|
}
|
|
@@ -6086,13 +7817,18 @@ async function compactTimeline(anchorId, projectId, depthBefore = 3, depthAfter
|
|
|
6086
7817
|
const totalTokens = countTextTokens(formatted);
|
|
6087
7818
|
return { timeline, formatted, totalTokens };
|
|
6088
7819
|
}
|
|
6089
|
-
async function compactDetail(
|
|
6090
|
-
const
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
7820
|
+
async function compactDetail(idsOrRefs) {
|
|
7821
|
+
const refs = idsOrRefs.map(
|
|
7822
|
+
(item) => typeof item === "number" ? { id: item } : item
|
|
7823
|
+
);
|
|
7824
|
+
const toRefKey = (ref) => `${ref.projectId ?? ""}::${ref.id}`;
|
|
7825
|
+
const documentMap = /* @__PURE__ */ new Map();
|
|
7826
|
+
const missingRefs = [];
|
|
7827
|
+
for (const ref of refs) {
|
|
7828
|
+
const obs = getObservation(ref.id);
|
|
7829
|
+
if (obs && (!ref.projectId || obs.projectId === ref.projectId)) {
|
|
7830
|
+
documentMap.set(toRefKey(ref), {
|
|
7831
|
+
id: makeOramaObservationId(obs.projectId, obs.id),
|
|
6096
7832
|
observationId: obs.id,
|
|
6097
7833
|
entityName: obs.entityName,
|
|
6098
7834
|
type: obs.type,
|
|
@@ -6106,166 +7842,90 @@ async function compactDetail(ids) {
|
|
|
6106
7842
|
projectId: obs.projectId,
|
|
6107
7843
|
accessCount: 0,
|
|
6108
7844
|
lastAccessedAt: "",
|
|
6109
|
-
status: obs.status ?? "active"
|
|
7845
|
+
status: obs.status ?? "active",
|
|
7846
|
+
source: obs.source ?? "agent"
|
|
6110
7847
|
});
|
|
7848
|
+
} else {
|
|
7849
|
+
missingRefs.push(ref);
|
|
6111
7850
|
}
|
|
6112
7851
|
}
|
|
6113
|
-
|
|
6114
|
-
(
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
}
|
|
6120
|
-
|
|
6121
|
-
// src/project/detector.ts
|
|
6122
|
-
init_esm_shims();
|
|
6123
|
-
import { execSync } from "child_process";
|
|
6124
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
6125
|
-
import os3 from "os";
|
|
6126
|
-
import path5 from "path";
|
|
6127
|
-
function detectProject(cwd) {
|
|
6128
|
-
const basePath = cwd ?? process.cwd();
|
|
6129
|
-
const rootPath = getGitRoot(basePath) ?? findPackageRoot(basePath) ?? basePath;
|
|
6130
|
-
const gitRemote = getGitRemote(rootPath);
|
|
6131
|
-
if (gitRemote) {
|
|
6132
|
-
const id2 = normalizeGitRemote(gitRemote);
|
|
6133
|
-
const name2 = id2.split("/").pop() ?? path5.basename(rootPath);
|
|
6134
|
-
return { id: id2, name: name2, gitRemote, rootPath };
|
|
6135
|
-
}
|
|
6136
|
-
if (isDangerousRoot(rootPath)) {
|
|
6137
|
-
const name2 = path5.basename(rootPath) || "unknown";
|
|
6138
|
-
const id2 = `placeholder/${name2}`;
|
|
6139
|
-
console.error(`[memorix] WARNING: cwd "${rootPath}" is not a project directory \u2014 using degraded mode (${id2})`);
|
|
6140
|
-
console.error(`[memorix] For best results, set MEMORIX_PROJECT_ROOT or --cwd to your project path.`);
|
|
6141
|
-
return { id: id2, name: name2, rootPath };
|
|
6142
|
-
}
|
|
6143
|
-
const name = path5.basename(rootPath);
|
|
6144
|
-
const id = `local/${name}`;
|
|
6145
|
-
return { id, name, rootPath };
|
|
6146
|
-
}
|
|
6147
|
-
function isDangerousRoot(dirPath) {
|
|
6148
|
-
const resolved = path5.resolve(dirPath);
|
|
6149
|
-
const home = path5.resolve(os3.homedir());
|
|
6150
|
-
if (resolved === home) return true;
|
|
6151
|
-
if (resolved === path5.parse(resolved).root) return true;
|
|
6152
|
-
const basename2 = path5.basename(resolved).toLowerCase();
|
|
6153
|
-
const knownNonProjectDirs = /* @__PURE__ */ new Set([
|
|
6154
|
-
// IDE / editor config dirs
|
|
6155
|
-
".vscode",
|
|
6156
|
-
".cursor",
|
|
6157
|
-
".windsurf",
|
|
6158
|
-
".kiro",
|
|
6159
|
-
".codex",
|
|
6160
|
-
".gemini",
|
|
6161
|
-
".claude",
|
|
6162
|
-
".github",
|
|
6163
|
-
".git",
|
|
6164
|
-
// OS / system dirs
|
|
6165
|
-
"desktop",
|
|
6166
|
-
"documents",
|
|
6167
|
-
"downloads",
|
|
6168
|
-
"pictures",
|
|
6169
|
-
"videos",
|
|
6170
|
-
"music",
|
|
6171
|
-
"appdata",
|
|
6172
|
-
"application data",
|
|
6173
|
-
"library",
|
|
6174
|
-
// Package manager / tool dirs
|
|
6175
|
-
"node_modules",
|
|
6176
|
-
".npm",
|
|
6177
|
-
".yarn",
|
|
6178
|
-
".pnpm-store",
|
|
6179
|
-
".config",
|
|
6180
|
-
".local",
|
|
6181
|
-
".cache",
|
|
6182
|
-
".ssh",
|
|
6183
|
-
".memorix"
|
|
6184
|
-
]);
|
|
6185
|
-
if (knownNonProjectDirs.has(basename2)) {
|
|
6186
|
-
const parent = path5.resolve(path5.dirname(resolved));
|
|
6187
|
-
if (parent === home || parent === path5.parse(parent).root) {
|
|
6188
|
-
return true;
|
|
6189
|
-
}
|
|
6190
|
-
}
|
|
6191
|
-
return false;
|
|
6192
|
-
}
|
|
6193
|
-
function findPackageRoot(cwd) {
|
|
6194
|
-
let dir = path5.resolve(cwd);
|
|
6195
|
-
const root = path5.parse(dir).root;
|
|
6196
|
-
while (dir !== root) {
|
|
6197
|
-
if (isDangerousRoot(dir)) return null;
|
|
6198
|
-
if (existsSync2(path5.join(dir, "package.json"))) {
|
|
6199
|
-
return dir;
|
|
6200
|
-
}
|
|
6201
|
-
dir = path5.dirname(dir);
|
|
6202
|
-
}
|
|
6203
|
-
return null;
|
|
6204
|
-
}
|
|
6205
|
-
function getGitRoot(cwd) {
|
|
6206
|
-
let dir = path5.resolve(cwd);
|
|
6207
|
-
const fsRoot = path5.parse(dir).root;
|
|
6208
|
-
while (dir !== fsRoot) {
|
|
6209
|
-
if (existsSync2(path5.join(dir, ".git"))) return dir;
|
|
6210
|
-
dir = path5.dirname(dir);
|
|
6211
|
-
}
|
|
6212
|
-
try {
|
|
6213
|
-
const root = execSync("git -c safe.directory=* rev-parse --show-toplevel", {
|
|
6214
|
-
cwd,
|
|
6215
|
-
encoding: "utf-8",
|
|
6216
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
6217
|
-
timeout: 5e3
|
|
6218
|
-
}).trim();
|
|
6219
|
-
return root || null;
|
|
6220
|
-
} catch {
|
|
6221
|
-
return null;
|
|
6222
|
-
}
|
|
6223
|
-
}
|
|
6224
|
-
function getGitRemote(cwd) {
|
|
6225
|
-
const fsRemote = readGitConfigRemote(cwd);
|
|
6226
|
-
if (fsRemote) return fsRemote;
|
|
6227
|
-
try {
|
|
6228
|
-
const remote = execSync("git -c safe.directory=* remote get-url origin", {
|
|
6229
|
-
cwd,
|
|
6230
|
-
encoding: "utf-8",
|
|
6231
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
6232
|
-
timeout: 5e3
|
|
6233
|
-
}).trim();
|
|
6234
|
-
return remote || null;
|
|
6235
|
-
} catch {
|
|
6236
|
-
return null;
|
|
6237
|
-
}
|
|
6238
|
-
}
|
|
6239
|
-
function readGitConfigRemote(cwd) {
|
|
6240
|
-
try {
|
|
6241
|
-
const configPath = path5.join(cwd, ".git", "config");
|
|
6242
|
-
if (!existsSync2(configPath)) return null;
|
|
6243
|
-
const content = readFileSync2(configPath, "utf-8");
|
|
6244
|
-
const remoteMatch = content.match(/\[remote\s+"origin"\]([\s\S]*?)(?=\n\[|$)/);
|
|
6245
|
-
if (!remoteMatch) return null;
|
|
6246
|
-
const urlMatch = remoteMatch[1].match(/^\s*url\s*=\s*(.+)$/m);
|
|
6247
|
-
return urlMatch ? urlMatch[1].trim() : null;
|
|
6248
|
-
} catch {
|
|
6249
|
-
return null;
|
|
6250
|
-
}
|
|
6251
|
-
}
|
|
6252
|
-
function normalizeGitRemote(remote) {
|
|
6253
|
-
let normalized = remote;
|
|
6254
|
-
normalized = normalized.replace(/\.git$/, "");
|
|
6255
|
-
const sshMatch = normalized.match(/^[\w-]+@[\w.-]+:(.+)$/);
|
|
6256
|
-
if (sshMatch) {
|
|
6257
|
-
return sshMatch[1];
|
|
7852
|
+
if (missingRefs.length > 0) {
|
|
7853
|
+
for (const ref of missingRefs) {
|
|
7854
|
+
const fallbackDocs = await getObservationsByIds([ref.id], ref.projectId);
|
|
7855
|
+
const doc = fallbackDocs[0];
|
|
7856
|
+
if (doc) {
|
|
7857
|
+
documentMap.set(toRefKey(ref), doc);
|
|
7858
|
+
}
|
|
7859
|
+
}
|
|
6258
7860
|
}
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
const
|
|
6264
|
-
|
|
7861
|
+
const documents = refs.map((ref) => documentMap.get(toRefKey(ref))).filter((doc) => Boolean(doc));
|
|
7862
|
+
const allObs = getAllObservations();
|
|
7863
|
+
const crossRefMap = /* @__PURE__ */ new Map();
|
|
7864
|
+
for (const ref of refs) {
|
|
7865
|
+
const obs = getObservation(ref.id);
|
|
7866
|
+
const doc = documentMap.get(toRefKey(ref));
|
|
7867
|
+
if (!obs && !doc) continue;
|
|
7868
|
+
const refs2 = [];
|
|
7869
|
+
if (obs?.source === "git" && obs.commitHash) {
|
|
7870
|
+
refs2.push(`Source: git commit ${obs.commitHash.substring(0, 7)}`);
|
|
7871
|
+
} else if (obs?.source) {
|
|
7872
|
+
refs2.push(`Source: ${obs.source}`);
|
|
7873
|
+
} else if (doc?.source) {
|
|
7874
|
+
refs2.push(`Source: ${doc.source}`);
|
|
7875
|
+
}
|
|
7876
|
+
if (!obs) {
|
|
7877
|
+
if (refs2.length > 0 && doc) crossRefMap.set(doc.id, refs2);
|
|
7878
|
+
continue;
|
|
7879
|
+
}
|
|
7880
|
+
if (obs.relatedCommits && obs.relatedCommits.length > 0) {
|
|
7881
|
+
refs2.push(`Related commits: ${obs.relatedCommits.map((h) => h.substring(0, 7)).join(", ")}`);
|
|
7882
|
+
const gitMems = allObs.filter((o) => o.source === "git" && o.commitHash && obs.relatedCommits.includes(o.commitHash));
|
|
7883
|
+
for (const gm of gitMems) {
|
|
7884
|
+
refs2.push(` \u2192 #${gm.id} \u{1F7E2} ${gm.title}`);
|
|
7885
|
+
}
|
|
7886
|
+
}
|
|
7887
|
+
if (obs.relatedEntities && obs.relatedEntities.length > 0) {
|
|
7888
|
+
refs2.push(`Related entities: ${obs.relatedEntities.join(", ")}`);
|
|
7889
|
+
}
|
|
7890
|
+
if (obs.source === "git") {
|
|
7891
|
+
const reasoning = allObs.filter(
|
|
7892
|
+
(o) => o.type === "reasoning" && o.entityName === obs.entityName && o.id !== obs.id && o.status !== "archived"
|
|
7893
|
+
).slice(0, 3);
|
|
7894
|
+
if (reasoning.length > 0) {
|
|
7895
|
+
refs2.push("Related reasoning:");
|
|
7896
|
+
for (const r of reasoning) {
|
|
7897
|
+
refs2.push(` \u2192 #${r.id} \u{1F9E0} ${r.title}`);
|
|
7898
|
+
}
|
|
7899
|
+
}
|
|
7900
|
+
}
|
|
7901
|
+
if (obs.type === "reasoning") {
|
|
7902
|
+
const gitMems = allObs.filter(
|
|
7903
|
+
(o) => o.source === "git" && o.entityName === obs.entityName && o.id !== obs.id && o.status !== "archived"
|
|
7904
|
+
).slice(0, 3);
|
|
7905
|
+
if (gitMems.length > 0) {
|
|
7906
|
+
refs2.push("Related commits:");
|
|
7907
|
+
for (const g of gitMems) {
|
|
7908
|
+
refs2.push(` \u2192 #${g.id} \u{1F7E2} ${g.title}`);
|
|
7909
|
+
}
|
|
7910
|
+
}
|
|
7911
|
+
}
|
|
7912
|
+
if (refs2.length > 0) crossRefMap.set(makeOramaObservationId(obs.projectId, obs.id), refs2);
|
|
6265
7913
|
}
|
|
7914
|
+
const formattedParts = documents.map((doc) => {
|
|
7915
|
+
let detail = formatObservationDetail(doc);
|
|
7916
|
+
const refs2 = crossRefMap.get(doc.id);
|
|
7917
|
+
if (refs2 && refs2.length > 0) {
|
|
7918
|
+
detail += "\n\nCross-references:\n" + refs2.join("\n");
|
|
7919
|
+
}
|
|
7920
|
+
return detail;
|
|
7921
|
+
});
|
|
7922
|
+
const formatted = formattedParts.join("\n\n" + "\u2550".repeat(50) + "\n\n");
|
|
7923
|
+
const totalTokens = countTextTokens(formatted);
|
|
7924
|
+
return { documents, formatted, totalTokens };
|
|
6266
7925
|
}
|
|
6267
7926
|
|
|
6268
7927
|
// src/server.ts
|
|
7928
|
+
init_detector();
|
|
6269
7929
|
init_aliases();
|
|
6270
7930
|
init_persistence();
|
|
6271
7931
|
|
|
@@ -6998,14 +8658,14 @@ var RulesSyncer = class {
|
|
|
6998
8658
|
|
|
6999
8659
|
// src/workspace/engine.ts
|
|
7000
8660
|
init_esm_shims();
|
|
7001
|
-
import { readFileSync as
|
|
7002
|
-
import { join as
|
|
7003
|
-
import { homedir as
|
|
8661
|
+
import { readFileSync as readFileSync5, readdirSync as readdirSync2, existsSync as existsSync6, cpSync, mkdirSync as mkdirSync2 } from "fs";
|
|
8662
|
+
import { join as join16 } from "path";
|
|
8663
|
+
import { homedir as homedir15 } from "os";
|
|
7004
8664
|
|
|
7005
8665
|
// src/workspace/mcp-adapters/windsurf.ts
|
|
7006
8666
|
init_esm_shims();
|
|
7007
|
-
import { homedir as
|
|
7008
|
-
import { join as
|
|
8667
|
+
import { homedir as homedir6 } from "os";
|
|
8668
|
+
import { join as join6 } from "path";
|
|
7009
8669
|
var WindsurfMCPAdapter = class {
|
|
7010
8670
|
source = "windsurf";
|
|
7011
8671
|
parse(content) {
|
|
@@ -7062,14 +8722,14 @@ var WindsurfMCPAdapter = class {
|
|
|
7062
8722
|
return JSON.stringify({ mcpServers }, null, 2);
|
|
7063
8723
|
}
|
|
7064
8724
|
getConfigPath(_projectRoot) {
|
|
7065
|
-
return
|
|
8725
|
+
return join6(homedir6(), ".codeium", "windsurf", "mcp_config.json");
|
|
7066
8726
|
}
|
|
7067
8727
|
};
|
|
7068
8728
|
|
|
7069
8729
|
// src/workspace/mcp-adapters/cursor.ts
|
|
7070
8730
|
init_esm_shims();
|
|
7071
|
-
import { homedir as
|
|
7072
|
-
import { join as
|
|
8731
|
+
import { homedir as homedir7 } from "os";
|
|
8732
|
+
import { join as join7 } from "path";
|
|
7073
8733
|
var CursorMCPAdapter = class {
|
|
7074
8734
|
source = "cursor";
|
|
7075
8735
|
parse(content) {
|
|
@@ -7106,16 +8766,16 @@ var CursorMCPAdapter = class {
|
|
|
7106
8766
|
}
|
|
7107
8767
|
getConfigPath(projectRoot) {
|
|
7108
8768
|
if (projectRoot) {
|
|
7109
|
-
return
|
|
8769
|
+
return join7(projectRoot, ".cursor", "mcp.json");
|
|
7110
8770
|
}
|
|
7111
|
-
return
|
|
8771
|
+
return join7(homedir7(), ".cursor", "mcp.json");
|
|
7112
8772
|
}
|
|
7113
8773
|
};
|
|
7114
8774
|
|
|
7115
8775
|
// src/workspace/mcp-adapters/codex.ts
|
|
7116
8776
|
init_esm_shims();
|
|
7117
|
-
import { homedir as
|
|
7118
|
-
import { join as
|
|
8777
|
+
import { homedir as homedir8 } from "os";
|
|
8778
|
+
import { join as join8 } from "path";
|
|
7119
8779
|
var CodexMCPAdapter = class {
|
|
7120
8780
|
source = "codex";
|
|
7121
8781
|
parse(content) {
|
|
@@ -7205,9 +8865,9 @@ var CodexMCPAdapter = class {
|
|
|
7205
8865
|
}
|
|
7206
8866
|
getConfigPath(projectRoot) {
|
|
7207
8867
|
if (projectRoot) {
|
|
7208
|
-
return
|
|
8868
|
+
return join8(projectRoot, ".codex", "config.toml");
|
|
7209
8869
|
}
|
|
7210
|
-
return
|
|
8870
|
+
return join8(homedir8(), ".codex", "config.toml");
|
|
7211
8871
|
}
|
|
7212
8872
|
// ---- TOML helpers ----
|
|
7213
8873
|
parseTomlString(raw) {
|
|
@@ -7254,8 +8914,8 @@ var CodexMCPAdapter = class {
|
|
|
7254
8914
|
|
|
7255
8915
|
// src/workspace/mcp-adapters/claude-code.ts
|
|
7256
8916
|
init_esm_shims();
|
|
7257
|
-
import { homedir as
|
|
7258
|
-
import { join as
|
|
8917
|
+
import { homedir as homedir9 } from "os";
|
|
8918
|
+
import { join as join9 } from "path";
|
|
7259
8919
|
var ClaudeCodeMCPAdapter = class {
|
|
7260
8920
|
source = "claude-code";
|
|
7261
8921
|
parse(content) {
|
|
@@ -7292,16 +8952,16 @@ var ClaudeCodeMCPAdapter = class {
|
|
|
7292
8952
|
}
|
|
7293
8953
|
getConfigPath(projectRoot) {
|
|
7294
8954
|
if (projectRoot) {
|
|
7295
|
-
return
|
|
8955
|
+
return join9(projectRoot, ".claude", "settings.json");
|
|
7296
8956
|
}
|
|
7297
|
-
return
|
|
8957
|
+
return join9(homedir9(), ".claude.json");
|
|
7298
8958
|
}
|
|
7299
8959
|
};
|
|
7300
8960
|
|
|
7301
8961
|
// src/workspace/mcp-adapters/copilot.ts
|
|
7302
8962
|
init_esm_shims();
|
|
7303
|
-
import { homedir as
|
|
7304
|
-
import { join as
|
|
8963
|
+
import { homedir as homedir10 } from "os";
|
|
8964
|
+
import { join as join10 } from "path";
|
|
7305
8965
|
var CopilotMCPAdapter = class {
|
|
7306
8966
|
source = "copilot";
|
|
7307
8967
|
parse(content) {
|
|
@@ -7356,23 +9016,23 @@ var CopilotMCPAdapter = class {
|
|
|
7356
9016
|
}
|
|
7357
9017
|
getConfigPath(projectRoot) {
|
|
7358
9018
|
if (projectRoot) {
|
|
7359
|
-
return
|
|
9019
|
+
return join10(projectRoot, ".vscode", "mcp.json");
|
|
7360
9020
|
}
|
|
7361
|
-
const home =
|
|
9021
|
+
const home = homedir10();
|
|
7362
9022
|
if (process.platform === "win32") {
|
|
7363
|
-
return
|
|
9023
|
+
return join10(home, "AppData", "Roaming", "Code", "User", "settings.json");
|
|
7364
9024
|
} else if (process.platform === "darwin") {
|
|
7365
|
-
return
|
|
9025
|
+
return join10(home, "Library", "Application Support", "Code", "User", "settings.json");
|
|
7366
9026
|
} else {
|
|
7367
|
-
return
|
|
9027
|
+
return join10(home, ".config", "Code", "User", "settings.json");
|
|
7368
9028
|
}
|
|
7369
9029
|
}
|
|
7370
9030
|
};
|
|
7371
9031
|
|
|
7372
9032
|
// src/workspace/mcp-adapters/antigravity.ts
|
|
7373
9033
|
init_esm_shims();
|
|
7374
|
-
import { homedir as
|
|
7375
|
-
import { join as
|
|
9034
|
+
import { homedir as homedir11 } from "os";
|
|
9035
|
+
import { join as join11 } from "path";
|
|
7376
9036
|
var AntigravityMCPAdapter = class {
|
|
7377
9037
|
source = "antigravity";
|
|
7378
9038
|
parse(content) {
|
|
@@ -7430,16 +9090,16 @@ var AntigravityMCPAdapter = class {
|
|
|
7430
9090
|
}
|
|
7431
9091
|
getConfigPath(projectRoot) {
|
|
7432
9092
|
if (projectRoot) {
|
|
7433
|
-
return
|
|
9093
|
+
return join11(projectRoot, ".gemini", "settings.json");
|
|
7434
9094
|
}
|
|
7435
|
-
return
|
|
9095
|
+
return join11(homedir11(), ".gemini", "settings.json");
|
|
7436
9096
|
}
|
|
7437
9097
|
};
|
|
7438
9098
|
|
|
7439
9099
|
// src/workspace/mcp-adapters/kiro.ts
|
|
7440
9100
|
init_esm_shims();
|
|
7441
|
-
import { homedir as
|
|
7442
|
-
import { join as
|
|
9101
|
+
import { homedir as homedir12 } from "os";
|
|
9102
|
+
import { join as join12 } from "path";
|
|
7443
9103
|
var KiroMCPAdapter = class {
|
|
7444
9104
|
source = "kiro";
|
|
7445
9105
|
parse(content) {
|
|
@@ -7479,16 +9139,16 @@ var KiroMCPAdapter = class {
|
|
|
7479
9139
|
}
|
|
7480
9140
|
getConfigPath(projectRoot) {
|
|
7481
9141
|
if (projectRoot) {
|
|
7482
|
-
return
|
|
9142
|
+
return join12(projectRoot, ".kiro", "settings", "mcp.json");
|
|
7483
9143
|
}
|
|
7484
|
-
return
|
|
9144
|
+
return join12(homedir12(), ".kiro", "settings", "mcp.json");
|
|
7485
9145
|
}
|
|
7486
9146
|
};
|
|
7487
9147
|
|
|
7488
9148
|
// src/workspace/mcp-adapters/opencode.ts
|
|
7489
9149
|
init_esm_shims();
|
|
7490
|
-
import { homedir as
|
|
7491
|
-
import { join as
|
|
9150
|
+
import { homedir as homedir13 } from "os";
|
|
9151
|
+
import { join as join13 } from "path";
|
|
7492
9152
|
var OpenCodeMCPAdapter = class {
|
|
7493
9153
|
source = "opencode";
|
|
7494
9154
|
parse(content) {
|
|
@@ -7553,16 +9213,16 @@ var OpenCodeMCPAdapter = class {
|
|
|
7553
9213
|
}
|
|
7554
9214
|
getConfigPath(projectRoot) {
|
|
7555
9215
|
if (projectRoot) {
|
|
7556
|
-
return
|
|
9216
|
+
return join13(projectRoot, "opencode.json");
|
|
7557
9217
|
}
|
|
7558
|
-
return
|
|
9218
|
+
return join13(homedir13(), ".config", "opencode", "opencode.json");
|
|
7559
9219
|
}
|
|
7560
9220
|
};
|
|
7561
9221
|
|
|
7562
9222
|
// src/workspace/mcp-adapters/trae.ts
|
|
7563
9223
|
init_esm_shims();
|
|
7564
|
-
import { join as
|
|
7565
|
-
import { homedir as
|
|
9224
|
+
import { join as join14 } from "path";
|
|
9225
|
+
import { homedir as homedir14 } from "os";
|
|
7566
9226
|
var TraeMCPAdapter = class {
|
|
7567
9227
|
source = "trae";
|
|
7568
9228
|
parse(content) {
|
|
@@ -7626,14 +9286,14 @@ var TraeMCPAdapter = class {
|
|
|
7626
9286
|
return JSON.stringify({ mcpServers }, null, 2);
|
|
7627
9287
|
}
|
|
7628
9288
|
getConfigPath(_projectRoot) {
|
|
7629
|
-
const home =
|
|
9289
|
+
const home = homedir14();
|
|
7630
9290
|
if (process.platform === "win32") {
|
|
7631
|
-
return
|
|
9291
|
+
return join14(process.env.APPDATA || join14(home, "AppData", "Roaming"), "Trae", "User", "mcp.json");
|
|
7632
9292
|
}
|
|
7633
9293
|
if (process.platform === "darwin") {
|
|
7634
|
-
return
|
|
9294
|
+
return join14(home, "Library", "Application Support", "Trae", "User", "mcp.json");
|
|
7635
9295
|
}
|
|
7636
|
-
return
|
|
9296
|
+
return join14(home, ".config", "Trae", "User", "mcp.json");
|
|
7637
9297
|
}
|
|
7638
9298
|
};
|
|
7639
9299
|
|
|
@@ -7765,7 +9425,7 @@ function sanitize(input) {
|
|
|
7765
9425
|
|
|
7766
9426
|
// src/workspace/applier.ts
|
|
7767
9427
|
init_esm_shims();
|
|
7768
|
-
import { existsSync as
|
|
9428
|
+
import { existsSync as existsSync5, mkdirSync, copyFileSync, writeFileSync, unlinkSync, renameSync } from "fs";
|
|
7769
9429
|
import { dirname } from "path";
|
|
7770
9430
|
var WorkspaceSyncApplier = class {
|
|
7771
9431
|
/**
|
|
@@ -7791,7 +9451,7 @@ var WorkspaceSyncApplier = class {
|
|
|
7791
9451
|
for (const file of files) {
|
|
7792
9452
|
try {
|
|
7793
9453
|
const dir = dirname(file.filePath);
|
|
7794
|
-
if (!
|
|
9454
|
+
if (!existsSync5(dir)) {
|
|
7795
9455
|
mkdirSync(dir, { recursive: true });
|
|
7796
9456
|
}
|
|
7797
9457
|
} catch (err) {
|
|
@@ -7800,7 +9460,7 @@ var WorkspaceSyncApplier = class {
|
|
|
7800
9460
|
}
|
|
7801
9461
|
}
|
|
7802
9462
|
for (const file of files) {
|
|
7803
|
-
if (
|
|
9463
|
+
if (existsSync5(file.filePath)) {
|
|
7804
9464
|
try {
|
|
7805
9465
|
const backupPath = file.filePath + `.backup-${Date.now()}`;
|
|
7806
9466
|
copyFileSync(file.filePath, backupPath);
|
|
@@ -7851,7 +9511,7 @@ var WorkspaceSyncApplier = class {
|
|
|
7851
9511
|
cleanBackups(backups) {
|
|
7852
9512
|
for (const backup of backups) {
|
|
7853
9513
|
try {
|
|
7854
|
-
if (
|
|
9514
|
+
if (existsSync5(backup.backupPath)) {
|
|
7855
9515
|
unlinkSync(backup.backupPath);
|
|
7856
9516
|
}
|
|
7857
9517
|
} catch {
|
|
@@ -7901,13 +9561,13 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
7901
9561
|
const globalPath = adapter.getConfigPath();
|
|
7902
9562
|
const pathsToCheck = [configPath, globalPath];
|
|
7903
9563
|
if (target === "antigravity") {
|
|
7904
|
-
pathsToCheck.push(
|
|
9564
|
+
pathsToCheck.push(join16(homedir15(), ".gemini", "antigravity", "mcp_config.json"));
|
|
7905
9565
|
}
|
|
7906
9566
|
const merged = /* @__PURE__ */ new Map();
|
|
7907
|
-
for (const
|
|
7908
|
-
if (
|
|
9567
|
+
for (const path11 of pathsToCheck) {
|
|
9568
|
+
if (existsSync6(path11)) {
|
|
7909
9569
|
try {
|
|
7910
|
-
const content =
|
|
9570
|
+
const content = readFileSync5(path11, "utf-8");
|
|
7911
9571
|
const servers = adapter.parse(content);
|
|
7912
9572
|
for (const s of servers) {
|
|
7913
9573
|
if (!merged.has(s.name)) merged.set(s.name, s);
|
|
@@ -7959,9 +9619,9 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
7959
9619
|
const adapter = this.adapters.get(target);
|
|
7960
9620
|
const configPath = adapter.getConfigPath(this.projectRoot);
|
|
7961
9621
|
let configContent;
|
|
7962
|
-
if (target === "antigravity" &&
|
|
9622
|
+
if (target === "antigravity" && existsSync6(configPath)) {
|
|
7963
9623
|
try {
|
|
7964
|
-
const existing = JSON.parse(
|
|
9624
|
+
const existing = JSON.parse(readFileSync5(configPath, "utf-8"));
|
|
7965
9625
|
const generated = JSON.parse(adapter.generate(result.mcpServers.scanned));
|
|
7966
9626
|
existing.mcpServers = { ...existing.mcpServers ?? {}, ...generated.mcpServers };
|
|
7967
9627
|
configContent = JSON.stringify(existing, null, 2);
|
|
@@ -8014,7 +9674,7 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
8014
9674
|
getTargetSkillsDir(target) {
|
|
8015
9675
|
const dirs = _WorkspaceSyncEngine.SKILLS_DIRS[target];
|
|
8016
9676
|
if (!dirs || dirs.length === 0) return null;
|
|
8017
|
-
return
|
|
9677
|
+
return join16(this.projectRoot, dirs[0]);
|
|
8018
9678
|
}
|
|
8019
9679
|
/**
|
|
8020
9680
|
* Scan all agent skills directories and collect unique skills.
|
|
@@ -8023,24 +9683,24 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
8023
9683
|
const skills = [];
|
|
8024
9684
|
const conflicts = [];
|
|
8025
9685
|
const seen = /* @__PURE__ */ new Map();
|
|
8026
|
-
const home =
|
|
9686
|
+
const home = homedir15();
|
|
8027
9687
|
for (const [agent, dirs] of Object.entries(_WorkspaceSyncEngine.SKILLS_DIRS)) {
|
|
8028
9688
|
for (const dir of dirs) {
|
|
8029
9689
|
const paths = [
|
|
8030
|
-
|
|
8031
|
-
|
|
9690
|
+
join16(this.projectRoot, dir),
|
|
9691
|
+
join16(home, dir)
|
|
8032
9692
|
];
|
|
8033
9693
|
for (const skillsRoot of paths) {
|
|
8034
|
-
if (!
|
|
9694
|
+
if (!existsSync6(skillsRoot)) continue;
|
|
8035
9695
|
try {
|
|
8036
|
-
const entries =
|
|
9696
|
+
const entries = readdirSync2(skillsRoot, { withFileTypes: true });
|
|
8037
9697
|
for (const entry of entries) {
|
|
8038
9698
|
if (!entry.isDirectory()) continue;
|
|
8039
|
-
const skillMd =
|
|
8040
|
-
if (!
|
|
9699
|
+
const skillMd = join16(skillsRoot, entry.name, "SKILL.md");
|
|
9700
|
+
if (!existsSync6(skillMd)) continue;
|
|
8041
9701
|
let description = "";
|
|
8042
9702
|
try {
|
|
8043
|
-
const content =
|
|
9703
|
+
const content = readFileSync5(skillMd, "utf-8");
|
|
8044
9704
|
const match = content.match(/^---[\s\S]*?description:\s*["']?(.+?)["']?\s*$/m);
|
|
8045
9705
|
if (match) description = match[1];
|
|
8046
9706
|
} catch {
|
|
@@ -8048,7 +9708,7 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
8048
9708
|
const newEntry = {
|
|
8049
9709
|
name: entry.name,
|
|
8050
9710
|
description,
|
|
8051
|
-
sourcePath:
|
|
9711
|
+
sourcePath: join16(skillsRoot, entry.name),
|
|
8052
9712
|
sourceAgent: agent
|
|
8053
9713
|
};
|
|
8054
9714
|
const existing = seen.get(entry.name);
|
|
@@ -8085,8 +9745,8 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
8085
9745
|
}
|
|
8086
9746
|
for (const skill of skills) {
|
|
8087
9747
|
if (skill.sourceAgent === target) continue;
|
|
8088
|
-
const dest =
|
|
8089
|
-
if (
|
|
9748
|
+
const dest = join16(targetDir, skill.name);
|
|
9749
|
+
if (existsSync6(dest)) {
|
|
8090
9750
|
skipped.push(`${skill.name} (already exists in ${target})`);
|
|
8091
9751
|
continue;
|
|
8092
9752
|
}
|
|
@@ -8101,13 +9761,13 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
8101
9761
|
}
|
|
8102
9762
|
scanWorkflows() {
|
|
8103
9763
|
const workflows = [];
|
|
8104
|
-
const wfDir =
|
|
8105
|
-
if (!
|
|
9764
|
+
const wfDir = join16(this.projectRoot, ".windsurf", "workflows");
|
|
9765
|
+
if (!existsSync6(wfDir)) return workflows;
|
|
8106
9766
|
try {
|
|
8107
|
-
const files =
|
|
9767
|
+
const files = readdirSync2(wfDir).filter((f) => f.endsWith(".md"));
|
|
8108
9768
|
for (const file of files) {
|
|
8109
9769
|
try {
|
|
8110
|
-
const content =
|
|
9770
|
+
const content = readFileSync5(join16(wfDir, file), "utf-8");
|
|
8111
9771
|
workflows.push(this.workflowSyncer.parseWindsurfWorkflow(file, content));
|
|
8112
9772
|
} catch {
|
|
8113
9773
|
}
|
|
@@ -8207,6 +9867,7 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
8207
9867
|
// src/server.ts
|
|
8208
9868
|
init_provider2();
|
|
8209
9869
|
init_memory_manager();
|
|
9870
|
+
init_formation();
|
|
8210
9871
|
var lastInternalWriteMs = 0;
|
|
8211
9872
|
var markInternalWrite = () => {
|
|
8212
9873
|
lastInternalWriteMs = Date.now();
|
|
@@ -8215,6 +9876,7 @@ var OBSERVATION_TYPES = [
|
|
|
8215
9876
|
"session-request",
|
|
8216
9877
|
"gotcha",
|
|
8217
9878
|
"problem-solution",
|
|
9879
|
+
"reasoning",
|
|
8218
9880
|
"how-it-works",
|
|
8219
9881
|
"what-changed",
|
|
8220
9882
|
"discovery",
|
|
@@ -8233,6 +9895,29 @@ function coerceNumberArray(val) {
|
|
|
8233
9895
|
}
|
|
8234
9896
|
return [];
|
|
8235
9897
|
}
|
|
9898
|
+
function coerceObservationRefs(val) {
|
|
9899
|
+
if (Array.isArray(val)) {
|
|
9900
|
+
const refs = [];
|
|
9901
|
+
for (const item of val) {
|
|
9902
|
+
if (!item || typeof item !== "object") continue;
|
|
9903
|
+
const record = item;
|
|
9904
|
+
const id = Number(record["id"]);
|
|
9905
|
+
if (!Number.isFinite(id) || id <= 0) continue;
|
|
9906
|
+
const projectId = typeof record["projectId"] === "string" ? record["projectId"] : void 0;
|
|
9907
|
+
refs.push(projectId ? { id, projectId } : { id });
|
|
9908
|
+
}
|
|
9909
|
+
return refs;
|
|
9910
|
+
}
|
|
9911
|
+
if (typeof val === "string") {
|
|
9912
|
+
try {
|
|
9913
|
+
const parsed = JSON.parse(val);
|
|
9914
|
+
return coerceObservationRefs(parsed);
|
|
9915
|
+
} catch {
|
|
9916
|
+
return [];
|
|
9917
|
+
}
|
|
9918
|
+
}
|
|
9919
|
+
return [];
|
|
9920
|
+
}
|
|
8236
9921
|
function coerceNumber(val, fallback) {
|
|
8237
9922
|
if (typeof val === "number") return val;
|
|
8238
9923
|
if (typeof val === "string") {
|
|
@@ -8275,7 +9960,17 @@ function coerceObjectArray(val) {
|
|
|
8275
9960
|
return [];
|
|
8276
9961
|
}
|
|
8277
9962
|
async function createMemorixServer(cwd, existingServer, sharedTeam) {
|
|
8278
|
-
const
|
|
9963
|
+
const detectedProject = detectProject(cwd);
|
|
9964
|
+
let rawProject;
|
|
9965
|
+
if (detectedProject) {
|
|
9966
|
+
rawProject = detectedProject;
|
|
9967
|
+
} else {
|
|
9968
|
+
const basePath = cwd ?? process.cwd();
|
|
9969
|
+
const name = (await import("path")).basename(basePath) || "unknown";
|
|
9970
|
+
rawProject = { id: `untracked/${name}`, name, rootPath: basePath };
|
|
9971
|
+
console.error(`[memorix] WARNING: No .git found in "${basePath}" \u2014 project isolation degraded`);
|
|
9972
|
+
console.error(`[memorix] Run "git init" in your project for proper isolation.`);
|
|
9973
|
+
}
|
|
8279
9974
|
try {
|
|
8280
9975
|
const { migrateSubdirsToFlat: migrateSubdirsToFlat2 } = await Promise.resolve().then(() => (init_persistence(), persistence_exports));
|
|
8281
9976
|
const migrated = await migrateSubdirsToFlat2();
|
|
@@ -8284,14 +9979,21 @@ async function createMemorixServer(cwd, existingServer, sharedTeam) {
|
|
|
8284
9979
|
}
|
|
8285
9980
|
} catch {
|
|
8286
9981
|
}
|
|
8287
|
-
|
|
9982
|
+
let projectDir2 = await getProjectDataDir(rawProject.id);
|
|
8288
9983
|
initAliasRegistry(projectDir2);
|
|
8289
9984
|
const canonicalId = await registerAlias(rawProject);
|
|
8290
|
-
|
|
9985
|
+
let project = { ...rawProject, id: canonicalId };
|
|
8291
9986
|
if (canonicalId !== rawProject.id) {
|
|
8292
9987
|
console.error(`[memorix] Alias resolved: ${rawProject.id} \u2192 ${canonicalId}`);
|
|
8293
9988
|
}
|
|
8294
|
-
|
|
9989
|
+
try {
|
|
9990
|
+
const { initProjectRoot: initProjectRoot2 } = await Promise.resolve().then(() => (init_yaml_loader(), yaml_loader_exports));
|
|
9991
|
+
initProjectRoot2(project.rootPath);
|
|
9992
|
+
const { loadDotenv: loadDotenv2 } = await Promise.resolve().then(() => (init_dotenv_loader(), dotenv_loader_exports));
|
|
9993
|
+
loadDotenv2(project.rootPath);
|
|
9994
|
+
} catch {
|
|
9995
|
+
}
|
|
9996
|
+
let graphManager = new KnowledgeGraphManager(projectDir2);
|
|
8295
9997
|
await graphManager.init();
|
|
8296
9998
|
await initObservations(projectDir2);
|
|
8297
9999
|
try {
|
|
@@ -8338,7 +10040,7 @@ async function createMemorixServer(cwd, existingServer, sharedTeam) {
|
|
|
8338
10040
|
let syncAdvisory = null;
|
|
8339
10041
|
const server = existingServer ?? new McpServer({
|
|
8340
10042
|
name: "memorix",
|
|
8341
|
-
version: true ? "1.0.
|
|
10043
|
+
version: true ? "1.0.4" : "1.0.1"
|
|
8342
10044
|
});
|
|
8343
10045
|
server.registerTool(
|
|
8344
10046
|
"memorix_store",
|
|
@@ -8360,16 +10062,148 @@ async function createMemorixServer(cwd, existingServer, sharedTeam) {
|
|
|
8360
10062
|
feature: z.string().describe("Feature or task name"),
|
|
8361
10063
|
status: z.enum(["in-progress", "completed", "blocked"]).describe("Current status"),
|
|
8362
10064
|
completion: z.number().optional().describe("Completion percentage 0-100")
|
|
8363
|
-
}).optional().describe("Progress tracking for task/feature observations")
|
|
10065
|
+
}).optional().describe("Progress tracking for task/feature observations"),
|
|
10066
|
+
relatedCommits: z.array(z.string()).optional().describe("Git commit hashes this memory relates to (links ground truth \u2194 reasoning)"),
|
|
10067
|
+
relatedEntities: z.array(z.string()).optional().describe("Other entity names this memory cross-references")
|
|
8364
10068
|
}
|
|
8365
10069
|
},
|
|
8366
|
-
async ({ entityName, type, title, narrative, facts, filesModified, concepts, topicKey, progress }) => {
|
|
8367
|
-
|
|
10070
|
+
async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities }) => {
|
|
10071
|
+
let entityName = rawEntityName;
|
|
10072
|
+
let type = rawType;
|
|
10073
|
+
let title = rawTitle;
|
|
10074
|
+
let safeFacts = facts ? coerceStringArray(facts) : void 0;
|
|
8368
10075
|
const safeFiles = filesModified ? coerceStringArray(filesModified) : void 0;
|
|
8369
10076
|
const safeConcepts = concepts ? coerceStringArray(concepts) : void 0;
|
|
10077
|
+
let formationMode = "active";
|
|
10078
|
+
if (process.env.MEMORIX_FORMATION_MODE) {
|
|
10079
|
+
formationMode = process.env.MEMORIX_FORMATION_MODE;
|
|
10080
|
+
} else {
|
|
10081
|
+
try {
|
|
10082
|
+
const { getBehaviorConfig: getBehaviorConfig2 } = await Promise.resolve().then(() => (init_behavior(), behavior_exports));
|
|
10083
|
+
formationMode = getBehaviorConfig2().formationMode;
|
|
10084
|
+
} catch {
|
|
10085
|
+
}
|
|
10086
|
+
}
|
|
10087
|
+
const useFormation = formationMode === "active";
|
|
10088
|
+
let formationResult = null;
|
|
10089
|
+
let formationNote = "";
|
|
10090
|
+
if (useFormation && !topicKey && !progress) {
|
|
10091
|
+
try {
|
|
10092
|
+
const formationConfig = {
|
|
10093
|
+
mode: "active",
|
|
10094
|
+
useLLM: isLLMEnabled(),
|
|
10095
|
+
minValueScore: 0.3,
|
|
10096
|
+
searchMemories: async (q, limit, pid) => {
|
|
10097
|
+
const result = await compactSearch({ query: q, limit, projectId: pid, status: "active" });
|
|
10098
|
+
if (result.entries.length === 0) return [];
|
|
10099
|
+
const details = await compactDetail(result.entries.map((e) => e.id));
|
|
10100
|
+
return details.documents.map((d, i) => ({
|
|
10101
|
+
id: Number(d.id.replace("obs-", "")),
|
|
10102
|
+
observationId: d.observationId,
|
|
10103
|
+
title: d.title,
|
|
10104
|
+
narrative: d.narrative,
|
|
10105
|
+
facts: d.facts,
|
|
10106
|
+
entityName: d.entityName,
|
|
10107
|
+
type: d.type,
|
|
10108
|
+
score: result.entries[i]?.score ?? 0
|
|
10109
|
+
}));
|
|
10110
|
+
},
|
|
10111
|
+
getObservation: (id) => {
|
|
10112
|
+
const o = getObservation(id);
|
|
10113
|
+
if (!o) return null;
|
|
10114
|
+
return {
|
|
10115
|
+
id: o.id,
|
|
10116
|
+
entityName: o.entityName,
|
|
10117
|
+
type: o.type,
|
|
10118
|
+
title: o.title,
|
|
10119
|
+
narrative: o.narrative,
|
|
10120
|
+
facts: o.facts,
|
|
10121
|
+
topicKey: o.topicKey
|
|
10122
|
+
};
|
|
10123
|
+
},
|
|
10124
|
+
getEntityNames: () => graphManager.getEntityNames()
|
|
10125
|
+
};
|
|
10126
|
+
formationResult = await runFormation({
|
|
10127
|
+
entityName,
|
|
10128
|
+
type,
|
|
10129
|
+
title,
|
|
10130
|
+
narrative,
|
|
10131
|
+
facts: safeFacts,
|
|
10132
|
+
projectId: project.id,
|
|
10133
|
+
source: "explicit"
|
|
10134
|
+
}, formationConfig);
|
|
10135
|
+
const modeIcon = "\u26A1";
|
|
10136
|
+
formationNote = `
|
|
10137
|
+
${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formationResult.evaluation.score.toFixed(2)}) | ${formationResult.resolution.action} | ${formationResult.pipeline.durationMs}ms`;
|
|
10138
|
+
if (formationResult.extraction.extractedFacts.length > 0) {
|
|
10139
|
+
formationNote += ` | +${formationResult.extraction.extractedFacts.length} facts`;
|
|
10140
|
+
}
|
|
10141
|
+
if (formationResult.extraction.titleImproved) formationNote += " | title\u2191";
|
|
10142
|
+
if (formationResult.extraction.entityResolved) formationNote += ` | entity\u2192${formationResult.entityName}`;
|
|
10143
|
+
if (formationResult.extraction.typeCorrected) formationNote += ` | type\u2192${formationResult.type}`;
|
|
10144
|
+
} catch {
|
|
10145
|
+
}
|
|
10146
|
+
}
|
|
10147
|
+
if (useFormation && formationResult && formationResult.resolution.action !== "new") {
|
|
10148
|
+
const { action: action2, targetId, reason } = formationResult.resolution;
|
|
10149
|
+
if (action2 === "merge" && targetId) {
|
|
10150
|
+
const targetObs = getObservation(targetId);
|
|
10151
|
+
if (targetObs) {
|
|
10152
|
+
markInternalWrite();
|
|
10153
|
+
await storeObservation({
|
|
10154
|
+
entityName: targetObs.entityName,
|
|
10155
|
+
type: targetObs.type,
|
|
10156
|
+
title: formationResult.title,
|
|
10157
|
+
narrative: formationResult.narrative,
|
|
10158
|
+
facts: formationResult.facts,
|
|
10159
|
+
filesModified: safeFiles,
|
|
10160
|
+
concepts: safeConcepts,
|
|
10161
|
+
projectId: project.id,
|
|
10162
|
+
topicKey: targetObs.topicKey,
|
|
10163
|
+
progress
|
|
10164
|
+
});
|
|
10165
|
+
return {
|
|
10166
|
+
content: [{
|
|
10167
|
+
type: "text",
|
|
10168
|
+
text: `\u{1F504} Formation MERGE: merged into #${targetId} (${reason})${formationNote}`
|
|
10169
|
+
}]
|
|
10170
|
+
};
|
|
10171
|
+
}
|
|
10172
|
+
} else if (action2 === "evolve" && targetId) {
|
|
10173
|
+
const targetObs = getObservation(targetId);
|
|
10174
|
+
if (targetObs) {
|
|
10175
|
+
markInternalWrite();
|
|
10176
|
+
await storeObservation({
|
|
10177
|
+
entityName: targetObs.entityName,
|
|
10178
|
+
type: targetObs.type,
|
|
10179
|
+
title: formationResult.title,
|
|
10180
|
+
narrative: formationResult.narrative,
|
|
10181
|
+
facts: formationResult.facts,
|
|
10182
|
+
filesModified: safeFiles,
|
|
10183
|
+
concepts: safeConcepts,
|
|
10184
|
+
projectId: project.id,
|
|
10185
|
+
topicKey: targetObs.topicKey,
|
|
10186
|
+
progress
|
|
10187
|
+
});
|
|
10188
|
+
return {
|
|
10189
|
+
content: [{
|
|
10190
|
+
type: "text",
|
|
10191
|
+
text: `\u{1F504} Formation EVOLVE: evolved #${targetId} (${reason})${formationNote}`
|
|
10192
|
+
}]
|
|
10193
|
+
};
|
|
10194
|
+
}
|
|
10195
|
+
} else if (action2 === "discard") {
|
|
10196
|
+
return {
|
|
10197
|
+
content: [{
|
|
10198
|
+
type: "text",
|
|
10199
|
+
text: `\u23ED\uFE0F Formation DISCARD: ${reason}${formationNote}`
|
|
10200
|
+
}]
|
|
10201
|
+
};
|
|
10202
|
+
}
|
|
10203
|
+
}
|
|
8370
10204
|
let compactAction = "";
|
|
8371
10205
|
let compactMerged = false;
|
|
8372
|
-
if (!topicKey && !progress) {
|
|
10206
|
+
if (!useFormation && !topicKey && !progress) {
|
|
8373
10207
|
try {
|
|
8374
10208
|
const searchResult = await compactSearch({
|
|
8375
10209
|
query: `${title} ${narrative.substring(0, 200)}`,
|
|
@@ -8443,6 +10277,26 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
|
8443
10277
|
} catch {
|
|
8444
10278
|
}
|
|
8445
10279
|
}
|
|
10280
|
+
if (formationResult && formationResult.resolution.action === "new") {
|
|
10281
|
+
const llmFacts = formationResult.extraction.extractedFacts;
|
|
10282
|
+
if (llmFacts.length > 0) {
|
|
10283
|
+
const currentFacts = safeFacts ?? [];
|
|
10284
|
+
const currentLower = new Set(currentFacts.map((f) => f.toLowerCase().trim()));
|
|
10285
|
+
const newFacts = llmFacts.filter((f) => !currentLower.has(f.toLowerCase().trim()));
|
|
10286
|
+
if (newFacts.length > 0) {
|
|
10287
|
+
safeFacts = [...currentFacts, ...newFacts];
|
|
10288
|
+
}
|
|
10289
|
+
}
|
|
10290
|
+
if (formationResult.extraction.titleImproved && formationResult.title) {
|
|
10291
|
+
title = formationResult.title;
|
|
10292
|
+
}
|
|
10293
|
+
if (formationResult.extraction.typeCorrected && formationResult.type) {
|
|
10294
|
+
type = formationResult.type;
|
|
10295
|
+
}
|
|
10296
|
+
if (formationResult.extraction.entityResolved && formationResult.entityName) {
|
|
10297
|
+
entityName = formationResult.entityName;
|
|
10298
|
+
}
|
|
10299
|
+
}
|
|
8446
10300
|
await graphManager.createEntities([
|
|
8447
10301
|
{ name: entityName, entityType: "auto", observations: [] }
|
|
8448
10302
|
]);
|
|
@@ -8476,7 +10330,9 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
|
8476
10330
|
projectId: project.id,
|
|
8477
10331
|
topicKey,
|
|
8478
10332
|
sessionId,
|
|
8479
|
-
progress
|
|
10333
|
+
progress,
|
|
10334
|
+
relatedCommits,
|
|
10335
|
+
relatedEntities
|
|
8480
10336
|
});
|
|
8481
10337
|
await graphManager.addObservations([
|
|
8482
10338
|
{ entityName, contents: [`[#${obs.id}] ${title}`] }
|
|
@@ -8494,12 +10350,123 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
|
8494
10350
|
const enrichment = enrichmentParts.length > 0 ? `
|
|
8495
10351
|
Auto-enriched: ${enrichmentParts.join(", ")}` : "";
|
|
8496
10352
|
const action = upserted ? "\u{1F504} Updated" : "\u2705 Stored";
|
|
10353
|
+
if (!useFormation) {
|
|
10354
|
+
try {
|
|
10355
|
+
let oldCompactDecision = null;
|
|
10356
|
+
if (!topicKey && !progress) {
|
|
10357
|
+
try {
|
|
10358
|
+
const compactStart = Date.now();
|
|
10359
|
+
const searchResult = await compactSearch({
|
|
10360
|
+
query: `${title} ${narrative.substring(0, 200)}`,
|
|
10361
|
+
limit: 5,
|
|
10362
|
+
projectId: project.id,
|
|
10363
|
+
status: "active"
|
|
10364
|
+
});
|
|
10365
|
+
const similarEntries = searchResult.entries.map((e) => e);
|
|
10366
|
+
if (similarEntries.length > 0) {
|
|
10367
|
+
const similarIds = similarEntries.map((e) => e.id);
|
|
10368
|
+
const details = await compactDetail(similarIds);
|
|
10369
|
+
const existingMemories = details.documents.map((d, i) => ({
|
|
10370
|
+
id: d.observationId,
|
|
10371
|
+
title: d.title,
|
|
10372
|
+
narrative: d.narrative,
|
|
10373
|
+
facts: d.facts,
|
|
10374
|
+
score: similarEntries[i]?.score ?? 0
|
|
10375
|
+
}));
|
|
10376
|
+
const decision = await compactOnWrite(
|
|
10377
|
+
{ title, narrative, facts: safeFacts ?? [] },
|
|
10378
|
+
existingMemories
|
|
10379
|
+
);
|
|
10380
|
+
const compactDuration = Date.now() - compactStart;
|
|
10381
|
+
oldCompactDecision = {
|
|
10382
|
+
action: decision.action,
|
|
10383
|
+
targetId: decision.targetId,
|
|
10384
|
+
reason: decision.reason,
|
|
10385
|
+
durationMs: compactDuration
|
|
10386
|
+
};
|
|
10387
|
+
}
|
|
10388
|
+
} catch {
|
|
10389
|
+
}
|
|
10390
|
+
}
|
|
10391
|
+
const formationConfig = {
|
|
10392
|
+
mode: formationMode,
|
|
10393
|
+
useLLM: isLLMEnabled(),
|
|
10394
|
+
minValueScore: 0.3,
|
|
10395
|
+
searchMemories: async (q, limit, pid) => {
|
|
10396
|
+
const result = await compactSearch({ query: q, limit, projectId: pid, status: "active" });
|
|
10397
|
+
if (result.entries.length === 0) return [];
|
|
10398
|
+
const details = await compactDetail(result.entries.map((e) => e.id));
|
|
10399
|
+
return details.documents.map((d, i) => ({
|
|
10400
|
+
id: Number(d.id.replace("obs-", "")),
|
|
10401
|
+
observationId: d.observationId,
|
|
10402
|
+
title: d.title,
|
|
10403
|
+
narrative: d.narrative,
|
|
10404
|
+
facts: d.facts,
|
|
10405
|
+
entityName: d.entityName,
|
|
10406
|
+
type: d.type,
|
|
10407
|
+
score: result.entries[i]?.score ?? 0
|
|
10408
|
+
}));
|
|
10409
|
+
},
|
|
10410
|
+
getObservation: (id) => {
|
|
10411
|
+
const o = getObservation(id);
|
|
10412
|
+
if (!o) return null;
|
|
10413
|
+
return {
|
|
10414
|
+
id: o.id,
|
|
10415
|
+
entityName: o.entityName,
|
|
10416
|
+
type: o.type,
|
|
10417
|
+
title: o.title,
|
|
10418
|
+
narrative: o.narrative,
|
|
10419
|
+
facts: o.facts,
|
|
10420
|
+
topicKey: o.topicKey
|
|
10421
|
+
};
|
|
10422
|
+
},
|
|
10423
|
+
getEntityNames: () => graphManager.getEntityNames()
|
|
10424
|
+
};
|
|
10425
|
+
const formed = await runFormation({
|
|
10426
|
+
entityName,
|
|
10427
|
+
type,
|
|
10428
|
+
title,
|
|
10429
|
+
narrative,
|
|
10430
|
+
facts: safeFacts,
|
|
10431
|
+
projectId: project.id,
|
|
10432
|
+
source: "explicit",
|
|
10433
|
+
topicKey
|
|
10434
|
+
}, formationConfig);
|
|
10435
|
+
const { recordBeforeAfterMetrics: recordBeforeAfterMetrics2 } = await Promise.resolve().then(() => (init_formation(), formation_exports));
|
|
10436
|
+
if (oldCompactDecision) {
|
|
10437
|
+
recordBeforeAfterMetrics2({
|
|
10438
|
+
formationAction: formed.resolution.action,
|
|
10439
|
+
formationTargetId: formed.resolution.targetId,
|
|
10440
|
+
oldCompactAction: oldCompactDecision.action,
|
|
10441
|
+
oldCompactTargetId: oldCompactDecision.targetId,
|
|
10442
|
+
oldCompactReason: oldCompactDecision.reason,
|
|
10443
|
+
formationValueScore: formed.evaluation.score,
|
|
10444
|
+
formationValueCategory: formed.evaluation.category,
|
|
10445
|
+
formationDurationMs: formed.pipeline.durationMs,
|
|
10446
|
+
compactDurationMs: oldCompactDecision.durationMs
|
|
10447
|
+
});
|
|
10448
|
+
}
|
|
10449
|
+
const modeIcon = formationMode === "shadow" ? "\u{1F52C}" : "\u{1F6E1}\uFE0F";
|
|
10450
|
+
formationNote = `
|
|
10451
|
+
${modeIcon} Formation[${formationMode}]: ${formed.evaluation.category} (${formed.evaluation.score.toFixed(2)}) | ${formed.resolution.action} | ${formed.pipeline.durationMs}ms`;
|
|
10452
|
+
if (oldCompactDecision) {
|
|
10453
|
+
formationNote += ` | Compact: ${oldCompactDecision.action}${oldCompactDecision.targetId ? ` #${oldCompactDecision.targetId}` : ""}${oldCompactDecision.durationMs ? ` (${oldCompactDecision.durationMs}ms)` : ""}`;
|
|
10454
|
+
}
|
|
10455
|
+
if (formed.extraction.extractedFacts.length > 0) {
|
|
10456
|
+
formationNote += ` | +${formed.extraction.extractedFacts.length} facts`;
|
|
10457
|
+
}
|
|
10458
|
+
if (formed.extraction.titleImproved) formationNote += " | title\u2191";
|
|
10459
|
+
if (formed.extraction.entityResolved) formationNote += ` | entity\u2192${formed.entityName}`;
|
|
10460
|
+
if (formed.extraction.typeCorrected) formationNote += ` | type\u2192${formed.type}`;
|
|
10461
|
+
} catch {
|
|
10462
|
+
}
|
|
10463
|
+
}
|
|
8497
10464
|
return {
|
|
8498
10465
|
content: [
|
|
8499
10466
|
{
|
|
8500
10467
|
type: "text",
|
|
8501
10468
|
text: `${action} observation #${obs.id} "${title}" (~${obs.tokens} tokens)
|
|
8502
|
-
Entity: ${entityName} | Type: ${type} | Project: ${project.id}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ""}${compactAction}${compressionNote}${enrichment}`
|
|
10469
|
+
Entity: ${entityName} | Type: ${type} | Project: ${project.id}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ""}${compactAction}${compressionNote}${enrichment}${formationNote}`
|
|
8503
10470
|
}
|
|
8504
10471
|
]
|
|
8505
10472
|
};
|
|
@@ -8548,13 +10515,17 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
8548
10515
|
until: z.string().optional().describe('Only return observations created before this date (ISO 8601 or natural like "2025-02-01")'),
|
|
8549
10516
|
status: z.enum(["active", "resolved", "archived", "all"]).optional().default("active").describe(
|
|
8550
10517
|
'Filter by memory status. "active" (default) shows current memories, "all" includes resolved/archived.'
|
|
10518
|
+
),
|
|
10519
|
+
source: z.enum(["agent", "git", "manual"]).optional().describe(
|
|
10520
|
+
'Filter by memory source. "git" returns only commit-derived ground truth memories. Omit for all sources.'
|
|
8551
10521
|
)
|
|
8552
10522
|
}
|
|
8553
10523
|
},
|
|
8554
|
-
async ({ query, limit, type, maxTokens, scope, since, until, status }) => {
|
|
10524
|
+
async ({ query, limit, type, maxTokens, scope, since, until, status, source }) => {
|
|
8555
10525
|
const safeLimit = limit != null ? coerceNumber(limit, 20) : void 0;
|
|
8556
10526
|
const safeMaxTokens = maxTokens != null ? coerceNumber(maxTokens, 0) : void 0;
|
|
8557
|
-
const
|
|
10527
|
+
const TIMEOUT_MS = 3e4;
|
|
10528
|
+
const searchPromise = compactSearch({
|
|
8558
10529
|
query,
|
|
8559
10530
|
limit: safeLimit,
|
|
8560
10531
|
type,
|
|
@@ -8564,8 +10535,29 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
8564
10535
|
// Default to project-scoped search to prevent cross-project pollution.
|
|
8565
10536
|
// Use scope: 'global' to explicitly search all projects.
|
|
8566
10537
|
projectId: scope === "global" ? void 0 : project.id,
|
|
8567
|
-
status: status ?? "active"
|
|
10538
|
+
status: status ?? "active",
|
|
10539
|
+
source
|
|
8568
10540
|
});
|
|
10541
|
+
const timeoutPromise = new Promise(
|
|
10542
|
+
(_, reject) => setTimeout(() => reject(new Error(`Search timeout after ${TIMEOUT_MS}ms`)), TIMEOUT_MS)
|
|
10543
|
+
);
|
|
10544
|
+
let result;
|
|
10545
|
+
try {
|
|
10546
|
+
result = await Promise.race([searchPromise, timeoutPromise]);
|
|
10547
|
+
} catch (error) {
|
|
10548
|
+
if (error instanceof Error && error.message.includes("timeout")) {
|
|
10549
|
+
return {
|
|
10550
|
+
content: [
|
|
10551
|
+
{
|
|
10552
|
+
type: "text",
|
|
10553
|
+
text: `Error: Search timeout after ${TIMEOUT_MS}ms. Try a simpler query or check if the service is responsive.`
|
|
10554
|
+
}
|
|
10555
|
+
],
|
|
10556
|
+
isError: true
|
|
10557
|
+
};
|
|
10558
|
+
}
|
|
10559
|
+
throw error;
|
|
10560
|
+
}
|
|
8569
10561
|
let text = result.formatted;
|
|
8570
10562
|
if (!syncAdvisoryShown && syncAdvisory) {
|
|
8571
10563
|
text += syncAdvisory;
|
|
@@ -8610,6 +10602,102 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
8610
10602
|
};
|
|
8611
10603
|
}
|
|
8612
10604
|
);
|
|
10605
|
+
server.registerTool(
|
|
10606
|
+
"memorix_store_reasoning",
|
|
10607
|
+
{
|
|
10608
|
+
title: "Store Reasoning Trace",
|
|
10609
|
+
description: "Store a reasoning trace \u2014 WHY you chose this approach, what alternatives you considered, and what outcome you expect. This creates a searchable record of your decision-making process. Use this when making non-trivial technical decisions, choosing between approaches, or solving complex problems. Unlike regular memories that record WHAT happened, reasoning memories record HOW you thought about it.",
|
|
10610
|
+
inputSchema: {
|
|
10611
|
+
entityName: z.string().describe('The entity this reasoning applies to (e.g., "auth-module", "database-schema")'),
|
|
10612
|
+
decision: z.string().describe("What was decided or chosen"),
|
|
10613
|
+
alternatives: z.array(z.string()).optional().describe("Other options that were considered"),
|
|
10614
|
+
rationale: z.string().describe("Why this approach was chosen over alternatives"),
|
|
10615
|
+
constraints: z.array(z.string()).optional().describe("Constraints that influenced the decision (time, perf, compat, etc.)"),
|
|
10616
|
+
expectedOutcome: z.string().optional().describe("What outcome is expected from this decision"),
|
|
10617
|
+
risks: z.array(z.string()).optional().describe("Known risks or potential downsides"),
|
|
10618
|
+
concepts: z.array(z.string()).optional().describe("Related technical concepts"),
|
|
10619
|
+
filesModified: z.array(z.string()).optional().describe("Files related to this reasoning"),
|
|
10620
|
+
relatedCommits: z.array(z.string()).optional().describe("Git commit hashes this reasoning explains (links ground truth \u2194 reasoning)"),
|
|
10621
|
+
relatedEntities: z.array(z.string()).optional().describe("Other entity names this reasoning relates to (cross-references)")
|
|
10622
|
+
}
|
|
10623
|
+
},
|
|
10624
|
+
async ({ entityName, decision, alternatives, rationale, constraints, expectedOutcome, risks, concepts, filesModified, relatedCommits, relatedEntities }) => {
|
|
10625
|
+
const narrativeParts = [rationale];
|
|
10626
|
+
if (alternatives && alternatives.length > 0) {
|
|
10627
|
+
narrativeParts.push(`Alternatives considered: ${alternatives.join("; ")}`);
|
|
10628
|
+
}
|
|
10629
|
+
if (constraints && constraints.length > 0) {
|
|
10630
|
+
narrativeParts.push(`Constraints: ${constraints.join("; ")}`);
|
|
10631
|
+
}
|
|
10632
|
+
if (expectedOutcome) {
|
|
10633
|
+
narrativeParts.push(`Expected outcome: ${expectedOutcome}`);
|
|
10634
|
+
}
|
|
10635
|
+
const narrative = narrativeParts.join(". ");
|
|
10636
|
+
const facts = [`Decision: ${decision}`];
|
|
10637
|
+
if (alternatives) alternatives.forEach((a) => facts.push(`Alternative considered: ${a}`));
|
|
10638
|
+
if (constraints) constraints.forEach((c) => facts.push(`Constraint: ${c}`));
|
|
10639
|
+
if (risks) risks.forEach((r) => facts.push(`Risk: ${r}`));
|
|
10640
|
+
if (expectedOutcome) facts.push(`Expected outcome: ${expectedOutcome}`);
|
|
10641
|
+
await graphManager.createEntities([
|
|
10642
|
+
{ name: entityName, entityType: "auto", observations: [] }
|
|
10643
|
+
]);
|
|
10644
|
+
markInternalWrite();
|
|
10645
|
+
const { observation: obs } = await storeObservation({
|
|
10646
|
+
entityName,
|
|
10647
|
+
type: "reasoning",
|
|
10648
|
+
title: decision.length > 80 ? decision.substring(0, 77) + "..." : decision,
|
|
10649
|
+
narrative,
|
|
10650
|
+
facts,
|
|
10651
|
+
concepts: concepts ?? [],
|
|
10652
|
+
filesModified: filesModified ?? [],
|
|
10653
|
+
projectId: project.id,
|
|
10654
|
+
source: "agent",
|
|
10655
|
+
relatedCommits,
|
|
10656
|
+
relatedEntities
|
|
10657
|
+
});
|
|
10658
|
+
await graphManager.addObservations([
|
|
10659
|
+
{ entityName, contents: [`[#${obs.id}] \u{1F9E0} ${decision}`] }
|
|
10660
|
+
]);
|
|
10661
|
+
return {
|
|
10662
|
+
content: [{
|
|
10663
|
+
type: "text",
|
|
10664
|
+
text: `\u{1F9E0} Reasoning trace stored #${obs.id}: "${decision}"
|
|
10665
|
+
Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens`
|
|
10666
|
+
}]
|
|
10667
|
+
};
|
|
10668
|
+
}
|
|
10669
|
+
);
|
|
10670
|
+
server.registerTool(
|
|
10671
|
+
"memorix_search_reasoning",
|
|
10672
|
+
{
|
|
10673
|
+
title: "Search Reasoning Patterns",
|
|
10674
|
+
description: "Search past reasoning traces to understand WHY decisions were made. Returns reasoning memories that explain the thought process behind technical choices. Use this when revisiting code, questioning a design decision, or looking for precedent on how similar problems were solved before.",
|
|
10675
|
+
inputSchema: {
|
|
10676
|
+
query: z.string().describe('Search query \u2014 describe what reasoning you want to find (e.g., "why did we choose PostgreSQL", "auth approach rationale")'),
|
|
10677
|
+
limit: z.number().optional().describe("Max results (default: 10)"),
|
|
10678
|
+
scope: z.enum(["project", "global"]).optional().default("project").describe("Search scope")
|
|
10679
|
+
}
|
|
10680
|
+
},
|
|
10681
|
+
async ({ query, limit, scope }) => {
|
|
10682
|
+
const safeLimit = limit != null ? coerceNumber(limit, 10) : 10;
|
|
10683
|
+
const result = await compactSearch({
|
|
10684
|
+
query,
|
|
10685
|
+
limit: safeLimit,
|
|
10686
|
+
type: "reasoning",
|
|
10687
|
+
projectId: scope === "global" ? void 0 : project.id,
|
|
10688
|
+
status: "active"
|
|
10689
|
+
});
|
|
10690
|
+
if (result.entries.length === 0) {
|
|
10691
|
+
return {
|
|
10692
|
+
content: [{ type: "text", text: "No reasoning traces found. Use memorix_store_reasoning to record decision rationale." }]
|
|
10693
|
+
};
|
|
10694
|
+
}
|
|
10695
|
+
return {
|
|
10696
|
+
content: [{ type: "text", text: `\u{1F9E0} Reasoning Traces:
|
|
10697
|
+
${result.formatted}` }]
|
|
10698
|
+
};
|
|
10699
|
+
}
|
|
10700
|
+
);
|
|
8613
10701
|
server.registerTool(
|
|
8614
10702
|
"memorix_deduplicate",
|
|
8615
10703
|
{
|
|
@@ -8742,19 +10830,27 @@ ${actions.join("\n")}`
|
|
|
8742
10830
|
"memorix_detail",
|
|
8743
10831
|
{
|
|
8744
10832
|
title: "Memory Details",
|
|
8745
|
-
description: "Fetch full observation details by IDs (~500-1000 tokens each). Always use memorix_search first to find relevant IDs, then fetch only what you need.",
|
|
10833
|
+
description: "Fetch full observation details by IDs (~500-1000 tokens each). Always use memorix_search first to find relevant IDs, then fetch only what you need. For global search results, prefer refs with projectId to avoid cross-project ID ambiguity.",
|
|
8746
10834
|
inputSchema: {
|
|
8747
|
-
ids: z.array(z.number()).describe("Observation IDs to fetch (from memorix_search results)")
|
|
10835
|
+
ids: z.array(z.number()).optional().describe("Observation IDs to fetch (from memorix_search results)"),
|
|
10836
|
+
refs: z.array(
|
|
10837
|
+
z.object({
|
|
10838
|
+
id: z.number().describe("Observation ID"),
|
|
10839
|
+
projectId: z.string().optional().describe("Project ID for global-search disambiguation")
|
|
10840
|
+
})
|
|
10841
|
+
).optional().describe("Explicit observation refs. Prefer this for global search results.")
|
|
8748
10842
|
}
|
|
8749
10843
|
},
|
|
8750
|
-
async ({ ids }) => {
|
|
10844
|
+
async ({ ids, refs }) => {
|
|
8751
10845
|
const safeIds = coerceNumberArray(ids);
|
|
8752
|
-
const
|
|
10846
|
+
const safeRefs = coerceObservationRefs(refs);
|
|
10847
|
+
const detailInput = safeRefs.length > 0 ? safeRefs : safeIds;
|
|
10848
|
+
const result = await compactDetail(detailInput);
|
|
8753
10849
|
return {
|
|
8754
10850
|
content: [
|
|
8755
10851
|
{
|
|
8756
10852
|
type: "text",
|
|
8757
|
-
text: result.documents.length > 0 ? result.formatted : `No observations found for IDs: ${safeIds.join(", ")}`
|
|
10853
|
+
text: result.documents.length > 0 ? result.formatted : safeRefs.length > 0 ? `No observations found for refs: ${safeRefs.map((ref) => `${ref.projectId ?? "current"}#${ref.id}`).join(", ")}` : `No observations found for IDs: ${safeIds.join(", ")}`
|
|
8758
10854
|
}
|
|
8759
10855
|
]
|
|
8760
10856
|
};
|
|
@@ -8804,7 +10900,8 @@ Archived memories can be restored manually if needed.` }]
|
|
|
8804
10900
|
projectId: obs.projectId,
|
|
8805
10901
|
accessCount: 0,
|
|
8806
10902
|
lastAccessedAt: "",
|
|
8807
|
-
status: obs.status ?? "active"
|
|
10903
|
+
status: obs.status ?? "active",
|
|
10904
|
+
source: obs.source ?? "agent"
|
|
8808
10905
|
}));
|
|
8809
10906
|
if (docs.length === 0) {
|
|
8810
10907
|
return {
|
|
@@ -8854,12 +10951,89 @@ Archived memories can be restored manually if needed.` }]
|
|
|
8854
10951
|
};
|
|
8855
10952
|
}
|
|
8856
10953
|
);
|
|
10954
|
+
server.registerTool(
|
|
10955
|
+
"memorix_formation_metrics",
|
|
10956
|
+
{
|
|
10957
|
+
title: "Formation Pipeline Metrics",
|
|
10958
|
+
description: "Show aggregated metrics from recent Memory Formation Pipeline runs. Reports value scores, resolution actions, fact extraction rates, and processing times.",
|
|
10959
|
+
inputSchema: {}
|
|
10960
|
+
},
|
|
10961
|
+
async () => {
|
|
10962
|
+
const summary = getMetricsSummary();
|
|
10963
|
+
const beforeAfter = getBeforeAfterMetrics();
|
|
10964
|
+
if (summary.total === 0) {
|
|
10965
|
+
return {
|
|
10966
|
+
content: [{
|
|
10967
|
+
type: "text",
|
|
10968
|
+
text: "\u{1F4CA} Formation Pipeline: No metrics collected yet.\nStore some observations to start collecting runtime data."
|
|
10969
|
+
}]
|
|
10970
|
+
};
|
|
10971
|
+
}
|
|
10972
|
+
const lines = [
|
|
10973
|
+
"\u{1F4CA} **Formation Pipeline Metrics**",
|
|
10974
|
+
"",
|
|
10975
|
+
`**Total observations processed:** ${summary.total}`,
|
|
10976
|
+
`**Average value score:** ${summary.avgValueScore.toFixed(3)}`,
|
|
10977
|
+
`**Average processing time:** ${summary.avgDurationMs.toFixed(1)}ms`,
|
|
10978
|
+
"",
|
|
10979
|
+
"### Quality Indicators",
|
|
10980
|
+
`- **Avg system-extracted facts:** ${summary.avgExtractedFacts.toFixed(1)} per observation`,
|
|
10981
|
+
`- **Title improved rate:** ${(summary.titleImprovedRate * 100).toFixed(1)}%`,
|
|
10982
|
+
`- **Entity resolved rate:** ${(summary.entityResolvedRate * 100).toFixed(1)}%`,
|
|
10983
|
+
`- **Type corrected rate:** ${(summary.typeCorectedRate * 100).toFixed(1)}%`,
|
|
10984
|
+
"",
|
|
10985
|
+
"### Value Categories"
|
|
10986
|
+
];
|
|
10987
|
+
for (const [cat, count2] of Object.entries(summary.categoryBreakdown)) {
|
|
10988
|
+
const pct = (count2 / summary.total * 100).toFixed(1);
|
|
10989
|
+
const icon = cat === "core" ? "\u{1F7E2}" : cat === "contextual" ? "\u{1F7E1}" : "\u{1F534}";
|
|
10990
|
+
lines.push(`- ${icon} **${cat}:** ${count2} (${pct}%)`);
|
|
10991
|
+
}
|
|
10992
|
+
lines.push("", "### Resolution Actions");
|
|
10993
|
+
for (const [action, count2] of Object.entries(summary.resolutionBreakdown)) {
|
|
10994
|
+
const pct = (count2 / summary.total * 100).toFixed(1);
|
|
10995
|
+
lines.push(`- **${action}:** ${count2} (${pct}%)`);
|
|
10996
|
+
}
|
|
10997
|
+
if (beforeAfter.totalProcessed > 0) {
|
|
10998
|
+
lines.push(
|
|
10999
|
+
"",
|
|
11000
|
+
"### Before/After Comparison (Formation vs Old Compact)",
|
|
11001
|
+
`**Total comparisons:** ${beforeAfter.totalProcessed}`,
|
|
11002
|
+
`**Agreements:** ${beforeAfter.agreements} (${(beforeAfter.agreements / beforeAfter.totalProcessed * 100).toFixed(1)}%)`,
|
|
11003
|
+
`**Disagreements:** ${beforeAfter.disagreements} (${(beforeAfter.disagreements / beforeAfter.totalProcessed * 100).toFixed(1)}%)`,
|
|
11004
|
+
"",
|
|
11005
|
+
"### Disagreement Breakdown",
|
|
11006
|
+
`- Formation discarded, Compact added: ${beforeAfter.disagreementBreakdown.formationDiscardedCompactAdded}`,
|
|
11007
|
+
`- Formation merged, Compact added: ${beforeAfter.disagreementBreakdown.formationMergedCompactAdded}`,
|
|
11008
|
+
`- Formation added, Compact discarded: ${beforeAfter.disagreementBreakdown.formationAddedCompactDiscarded}`,
|
|
11009
|
+
"- Formation added, Compact merged: " + beforeAfter.disagreementBreakdown.formationAddedCompactMerged,
|
|
11010
|
+
"- Formation evolved, Compact added: " + beforeAfter.disagreementBreakdown.formationEvolvedCompactAdded,
|
|
11011
|
+
"- Other: " + beforeAfter.disagreementBreakdown.other,
|
|
11012
|
+
"",
|
|
11013
|
+
"### Quality Improvements",
|
|
11014
|
+
`- Formation discarded low-value: ${beforeAfter.quality.formationDiscardedLowValue}`,
|
|
11015
|
+
`- Formation merged duplicates: ${beforeAfter.quality.formationMergedDuplicates}`,
|
|
11016
|
+
`- Formation evolved outdated: ${beforeAfter.quality.formationEvolvedOutdated}`,
|
|
11017
|
+
`- Compact missed duplicates: ${beforeAfter.quality.compactMissedDuplicates}`,
|
|
11018
|
+
`- Compact kept low-value: ${beforeAfter.quality.compactKeptLowValue}`,
|
|
11019
|
+
"",
|
|
11020
|
+
`### Duration Comparison`,
|
|
11021
|
+
`- Formation avg: ${beforeAfter.duration.formationAvgMs.toFixed(1)}ms`,
|
|
11022
|
+
`- Compact avg: ${beforeAfter.duration.compactAvgMs.toFixed(1)}ms`,
|
|
11023
|
+
`- Diff: ${beforeAfter.duration.diffMs.toFixed(1)}ms`
|
|
11024
|
+
);
|
|
11025
|
+
}
|
|
11026
|
+
return {
|
|
11027
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
11028
|
+
};
|
|
11029
|
+
}
|
|
11030
|
+
);
|
|
8857
11031
|
let enableKG = false;
|
|
8858
11032
|
try {
|
|
8859
|
-
const { homedir:
|
|
8860
|
-
const { join:
|
|
8861
|
-
const { readFile:
|
|
8862
|
-
const raw = await
|
|
11033
|
+
const { homedir: homedir20 } = await import("os");
|
|
11034
|
+
const { join: join21 } = await import("path");
|
|
11035
|
+
const { readFile: readFile6 } = await import("fs/promises");
|
|
11036
|
+
const raw = await readFile6(join21(homedir20(), ".memorix", "settings.json"), "utf-8");
|
|
8863
11037
|
const s = JSON.parse(raw);
|
|
8864
11038
|
if (s.knowledgeGraph === true) enableKG = true;
|
|
8865
11039
|
} catch {
|
|
@@ -9257,7 +11431,9 @@ ${skill.content}` }]
|
|
|
9257
11431
|
facts: o.facts,
|
|
9258
11432
|
concepts: o.concepts,
|
|
9259
11433
|
filesModified: o.filesModified,
|
|
9260
|
-
createdAt: o.createdAt
|
|
11434
|
+
createdAt: o.createdAt,
|
|
11435
|
+
status: o.status,
|
|
11436
|
+
source: o.source
|
|
9261
11437
|
}));
|
|
9262
11438
|
const generated = engine.generateFromObservations(obsData);
|
|
9263
11439
|
if (generated.length === 0) {
|
|
@@ -9276,9 +11452,9 @@ ${skill.content}` }]
|
|
|
9276
11452
|
lines.push(`- **Description**: ${sk.description}`);
|
|
9277
11453
|
lines.push(`- **Observations**: ${sk.content.split("\n").length} lines of knowledge`);
|
|
9278
11454
|
if (write && target) {
|
|
9279
|
-
const
|
|
9280
|
-
if (
|
|
9281
|
-
lines.push(`- \u2705 **Written**: \`${
|
|
11455
|
+
const path11 = engine.writeSkill(sk, target);
|
|
11456
|
+
if (path11) {
|
|
11457
|
+
lines.push(`- \u2705 **Written**: \`${path11}\``);
|
|
9282
11458
|
} else {
|
|
9283
11459
|
lines.push(`- \u274C Failed to write`);
|
|
9284
11460
|
}
|
|
@@ -9734,9 +11910,9 @@ ${json}
|
|
|
9734
11910
|
let teamPersist = null;
|
|
9735
11911
|
if (!sharedTeam) {
|
|
9736
11912
|
const { TeamPersistence: TeamPersistence2 } = await Promise.resolve().then(() => (init_persistence2(), persistence_exports2));
|
|
9737
|
-
const { join:
|
|
11913
|
+
const { join: join21 } = await import("path");
|
|
9738
11914
|
teamPersist = new TeamPersistence2(
|
|
9739
|
-
|
|
11915
|
+
join21(projectDir2, "team-state.json"),
|
|
9740
11916
|
teamRegistry,
|
|
9741
11917
|
messageBus,
|
|
9742
11918
|
taskManager,
|
|
@@ -9969,58 +12145,67 @@ ${lines.join("\n")}` }] };
|
|
|
9969
12145
|
);
|
|
9970
12146
|
const deferredInit = async () => {
|
|
9971
12147
|
try {
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
9980
|
-
if (settings.autoInstallHooks === false) {
|
|
9981
|
-
autoInstall = false;
|
|
9982
|
-
console.error("[memorix] autoInstallHooks disabled in ~/.memorix/settings.json \u2014 skipping hook auto-install");
|
|
9983
|
-
}
|
|
9984
|
-
} catch {
|
|
12148
|
+
const { getHookStatus: getHookStatus2 } = await Promise.resolve().then(() => (init_installers(), installers_exports));
|
|
12149
|
+
const workDir = cwd ?? process.cwd();
|
|
12150
|
+
const statuses = await getHookStatus2(workDir);
|
|
12151
|
+
const installedAgents = statuses.filter((s) => s.installed).map((s) => s.agent);
|
|
12152
|
+
if (installedAgents.length === 0) {
|
|
12153
|
+
console.error('[memorix] No hooks installed. Run "memorix hooks install" to set up auto-capture.');
|
|
12154
|
+
} else {
|
|
12155
|
+
console.error(`[memorix] Hooks active: ${installedAgents.join(", ")}`);
|
|
9985
12156
|
}
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
|
|
9992
|
-
const
|
|
9993
|
-
const
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
12157
|
+
} catch {
|
|
12158
|
+
}
|
|
12159
|
+
try {
|
|
12160
|
+
const { getGitConfig: getGitConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
12161
|
+
const gitCfg = getGitConfig2();
|
|
12162
|
+
if (gitCfg.autoHook && project.rootPath) {
|
|
12163
|
+
const { ensureHooksDir: ensureHooksDir2 } = await Promise.resolve().then(() => (init_hooks_path(), hooks_path_exports));
|
|
12164
|
+
const resolved = ensureHooksDir2(project.rootPath);
|
|
12165
|
+
if (resolved) {
|
|
12166
|
+
const { existsSync: existsSync10, readFileSync: readFileSync9, writeFileSync: writeFileSync3, chmodSync } = await import("fs");
|
|
12167
|
+
const { hookPath } = resolved;
|
|
12168
|
+
const HOOK_MARKER = "# [memorix-git-hook]";
|
|
12169
|
+
const needsInstall = !existsSync10(hookPath) || !readFileSync9(hookPath, "utf-8").includes(HOOK_MARKER);
|
|
12170
|
+
if (needsInstall) {
|
|
12171
|
+
const hookScript = `#!/bin/sh
|
|
12172
|
+
${HOOK_MARKER}
|
|
12173
|
+
# Memorix: Auto-ingest git commits as memories
|
|
12174
|
+
if command -v memorix >/dev/null 2>&1; then
|
|
12175
|
+
memorix ingest commit --auto >/dev/null 2>&1 &
|
|
12176
|
+
fi
|
|
12177
|
+
`;
|
|
12178
|
+
if (existsSync10(hookPath)) {
|
|
12179
|
+
const existing = readFileSync9(hookPath, "utf-8");
|
|
12180
|
+
writeFileSync3(hookPath, existing.trimEnd() + `
|
|
12181
|
+
|
|
12182
|
+
${HOOK_MARKER}
|
|
12183
|
+
if command -v memorix >/dev/null 2>&1; then
|
|
12184
|
+
memorix ingest commit --auto >/dev/null 2>&1 &
|
|
12185
|
+
fi
|
|
12186
|
+
`, "utf-8");
|
|
12187
|
+
} else {
|
|
12188
|
+
writeFileSync3(hookPath, hookScript, "utf-8");
|
|
12189
|
+
}
|
|
10008
12190
|
try {
|
|
10009
|
-
|
|
12191
|
+
chmodSync(hookPath, 493);
|
|
10010
12192
|
} catch {
|
|
10011
|
-
continue;
|
|
10012
12193
|
}
|
|
10013
|
-
|
|
10014
|
-
try {
|
|
10015
|
-
const config = await installHooks2(agent, workDir);
|
|
10016
|
-
console.error(`[memorix] Auto-installed hooks for ${agent} \u2192 ${config.configPath}`);
|
|
10017
|
-
} catch {
|
|
12194
|
+
console.error("[memorix] Auto-installed git post-commit hook (git.autoHook: true)");
|
|
10018
12195
|
}
|
|
10019
12196
|
}
|
|
10020
12197
|
}
|
|
10021
12198
|
} catch {
|
|
10022
12199
|
}
|
|
12200
|
+
let behaviorConfig = { syncAdvisory: true, autoCleanup: true };
|
|
10023
12201
|
try {
|
|
12202
|
+
const { getBehaviorConfig: getBehaviorConfig2 } = await Promise.resolve().then(() => (init_behavior(), behavior_exports));
|
|
12203
|
+
behaviorConfig = getBehaviorConfig2();
|
|
12204
|
+
} catch {
|
|
12205
|
+
}
|
|
12206
|
+
if (!behaviorConfig.syncAdvisory) {
|
|
12207
|
+
console.error("[memorix] Sync advisory disabled via config.");
|
|
12208
|
+
} else try {
|
|
10024
12209
|
const engine = new WorkspaceSyncEngine(project.rootPath);
|
|
10025
12210
|
const scan = await engine.scan();
|
|
10026
12211
|
const lines = [];
|
|
@@ -10062,59 +12247,63 @@ ${lines.join("\n")}` }] };
|
|
|
10062
12247
|
console.error(`[memorix] Sync advisory: ${syncAdvisory ? "available" : "nothing to sync"}`);
|
|
10063
12248
|
} catch {
|
|
10064
12249
|
}
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
12250
|
+
if (!behaviorConfig.autoCleanup) {
|
|
12251
|
+
console.error("[memorix] Auto-cleanup disabled via config.");
|
|
12252
|
+
} else {
|
|
12253
|
+
try {
|
|
12254
|
+
const { archiveExpired: archiveExpired2 } = await Promise.resolve().then(() => (init_retention(), retention_exports));
|
|
12255
|
+
const archiveResult = await archiveExpired2(projectDir2);
|
|
12256
|
+
if (archiveResult.archived > 0) {
|
|
12257
|
+
console.error(`[memorix] Auto-archived ${archiveResult.archived} expired observation(s)`);
|
|
12258
|
+
}
|
|
12259
|
+
} catch {
|
|
10070
12260
|
}
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
12261
|
+
try {
|
|
12262
|
+
if (isLLMEnabled()) {
|
|
12263
|
+
const { getAllObservations: getAllObservations2, resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
|
|
12264
|
+
const { deduplicateMemory: deduplicateMemory2 } = await Promise.resolve().then(() => (init_memory_manager(), memory_manager_exports));
|
|
12265
|
+
const allObs = getAllObservations2().filter((o) => (o.status ?? "active") === "active" && o.projectId === project.id);
|
|
12266
|
+
if (allObs.length > 10) {
|
|
12267
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
12268
|
+
for (const obs of allObs) {
|
|
12269
|
+
const key = `${obs.entityName}::${obs.type}`;
|
|
12270
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
12271
|
+
grouped.get(key).push(obs);
|
|
12272
|
+
}
|
|
12273
|
+
const toResolve = [];
|
|
12274
|
+
for (const [, group] of grouped) {
|
|
12275
|
+
if (group.length < 2) continue;
|
|
12276
|
+
group.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
12277
|
+
for (let i = 0; i < group.length - 1 && i < 5; i++) {
|
|
12278
|
+
try {
|
|
12279
|
+
const older = group[i], newer = group[i + 1];
|
|
12280
|
+
const decision = await deduplicateMemory2(
|
|
12281
|
+
{ title: newer.title, narrative: newer.narrative, facts: newer.facts },
|
|
12282
|
+
[{ id: older.id, title: older.title, narrative: older.narrative, facts: older.facts.join("\n") }]
|
|
12283
|
+
);
|
|
12284
|
+
if (decision && (decision.action === "UPDATE" || decision.action === "NONE")) {
|
|
12285
|
+
toResolve.push(decision.action === "UPDATE" ? older.id : newer.id);
|
|
12286
|
+
} else if (decision?.action === "DELETE" && decision.targetId) {
|
|
12287
|
+
toResolve.push(decision.targetId);
|
|
12288
|
+
}
|
|
12289
|
+
} catch {
|
|
10100
12290
|
}
|
|
10101
|
-
} catch {
|
|
10102
12291
|
}
|
|
10103
12292
|
}
|
|
12293
|
+
if (toResolve.length > 0) {
|
|
12294
|
+
await resolveObservations2([...new Set(toResolve)], "resolved");
|
|
12295
|
+
console.error(`[memorix] Auto-dedup (LLM): resolved ${toResolve.length} redundant observation(s)`);
|
|
12296
|
+
}
|
|
10104
12297
|
}
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
12298
|
+
} else {
|
|
12299
|
+
const { executeConsolidation: executeConsolidation2 } = await Promise.resolve().then(() => (init_consolidation(), consolidation_exports));
|
|
12300
|
+
const result = await executeConsolidation2(projectDir2, project.id, { threshold: 0.55 });
|
|
12301
|
+
if (result.observationsMerged > 0) {
|
|
12302
|
+
console.error(`[memorix] Auto-consolidated: merged ${result.observationsMerged} duplicate(s) across ${result.clustersFound} cluster(s)`);
|
|
10108
12303
|
}
|
|
10109
12304
|
}
|
|
10110
|
-
}
|
|
10111
|
-
const { executeConsolidation: executeConsolidation2 } = await Promise.resolve().then(() => (init_consolidation(), consolidation_exports));
|
|
10112
|
-
const result = await executeConsolidation2(projectDir2, project.id, { threshold: 0.55 });
|
|
10113
|
-
if (result.observationsMerged > 0) {
|
|
10114
|
-
console.error(`[memorix] Auto-consolidated: merged ${result.observationsMerged} duplicate(s) across ${result.clustersFound} cluster(s)`);
|
|
10115
|
-
}
|
|
12305
|
+
} catch {
|
|
10116
12306
|
}
|
|
10117
|
-
} catch {
|
|
10118
12307
|
}
|
|
10119
12308
|
const observationsFile = projectDir2 + "/observations.json";
|
|
10120
12309
|
let reloadDebounce = null;
|
|
@@ -10145,7 +12334,32 @@ ${lines.join("\n")}` }] };
|
|
|
10145
12334
|
console.error(`[memorix] Warning: could not watch observations file for hot-reload`);
|
|
10146
12335
|
}
|
|
10147
12336
|
};
|
|
10148
|
-
|
|
12337
|
+
const switchProject = async (newCwd) => {
|
|
12338
|
+
const newDetected = detectProject(newCwd);
|
|
12339
|
+
if (!newDetected) return false;
|
|
12340
|
+
const newCanonicalId = await registerAlias(newDetected);
|
|
12341
|
+
if (newCanonicalId === project.id) return false;
|
|
12342
|
+
console.error(`[memorix] Switching project: ${project.id} \u2192 ${newCanonicalId}`);
|
|
12343
|
+
const newProjectDir = await getProjectDataDir(newCanonicalId);
|
|
12344
|
+
project = { ...newDetected, id: newCanonicalId };
|
|
12345
|
+
projectDir2 = newProjectDir;
|
|
12346
|
+
try {
|
|
12347
|
+
const { initProjectRoot: initProjectRoot2 } = await Promise.resolve().then(() => (init_yaml_loader(), yaml_loader_exports));
|
|
12348
|
+
initProjectRoot2(project.rootPath);
|
|
12349
|
+
const { resetDotenv: resetDotenv2, loadDotenv: loadDotenv2 } = await Promise.resolve().then(() => (init_dotenv_loader(), dotenv_loader_exports));
|
|
12350
|
+
resetDotenv2();
|
|
12351
|
+
loadDotenv2(project.rootPath);
|
|
12352
|
+
} catch {
|
|
12353
|
+
}
|
|
12354
|
+
graphManager = new KnowledgeGraphManager(projectDir2);
|
|
12355
|
+
await graphManager.init();
|
|
12356
|
+
await initObservations(projectDir2);
|
|
12357
|
+
await reindexObservations();
|
|
12358
|
+
console.error(`[memorix] Project switched to: ${project.id} (${project.name})`);
|
|
12359
|
+
console.error(`[memorix] Data dir: ${projectDir2}`);
|
|
12360
|
+
return true;
|
|
12361
|
+
};
|
|
12362
|
+
return { server, graphManager, projectId: project.id, deferredInit, switchProject };
|
|
10149
12363
|
}
|
|
10150
12364
|
|
|
10151
12365
|
// src/index.ts
|