@fractary/codex 0.12.16 → 0.12.18
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/README.md +219 -52
- package/dist/index.cjs +607 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +109 -1
- package/dist/index.d.ts +109 -1
- package/dist/index.js +605 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7,6 +7,7 @@ var zod = require('zod');
|
|
|
7
7
|
var fs4 = require('fs/promises');
|
|
8
8
|
var util = require('util');
|
|
9
9
|
var yaml2 = require('js-yaml');
|
|
10
|
+
var fs = require('fs');
|
|
10
11
|
|
|
11
12
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
13
|
|
|
@@ -35,6 +36,12 @@ var yaml2__namespace = /*#__PURE__*/_interopNamespace(yaml2);
|
|
|
35
36
|
|
|
36
37
|
var __defProp = Object.defineProperty;
|
|
37
38
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
39
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
40
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
41
|
+
}) : x)(function(x) {
|
|
42
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
43
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
44
|
+
});
|
|
38
45
|
var __esm = (fn, res) => function __init() {
|
|
39
46
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
40
47
|
};
|
|
@@ -540,6 +547,22 @@ var init_built_in = __esm({
|
|
|
540
547
|
defaultTtl: exports.TTL.TWO_WEEKS,
|
|
541
548
|
archiveAfterDays: null,
|
|
542
549
|
archiveStorage: null
|
|
550
|
+
},
|
|
551
|
+
memory: {
|
|
552
|
+
name: "memory",
|
|
553
|
+
description: "Institutional memory entries (troubleshooting, decisions, patterns)",
|
|
554
|
+
patterns: [
|
|
555
|
+
"memory/**",
|
|
556
|
+
".fractary/codex/memory/**",
|
|
557
|
+
"**/knowledge-base/**"
|
|
558
|
+
],
|
|
559
|
+
defaultTtl: exports.TTL.ONE_MONTH,
|
|
560
|
+
archiveAfterDays: 365,
|
|
561
|
+
archiveStorage: "cloud",
|
|
562
|
+
syncPatterns: [
|
|
563
|
+
"memory/**",
|
|
564
|
+
".fractary/codex/memory/**"
|
|
565
|
+
]
|
|
543
566
|
}
|
|
544
567
|
};
|
|
545
568
|
exports.DEFAULT_TYPE = {
|
|
@@ -3669,10 +3692,11 @@ var SYNC_PATTERN_PRESETS = {
|
|
|
3669
3692
|
config: {
|
|
3670
3693
|
to_codex: {
|
|
3671
3694
|
include: ["docs/**", "README.md", "CLAUDE.md"],
|
|
3672
|
-
exclude: ["*.tmp"]
|
|
3695
|
+
exclude: ["*.tmp", "_archive/**"]
|
|
3673
3696
|
},
|
|
3674
3697
|
from_codex: {
|
|
3675
|
-
include: ["codex://{org}/{codex_repo}/docs/**"]
|
|
3698
|
+
include: ["codex://{org}/{codex_repo}/docs/**"],
|
|
3699
|
+
exclude: ["_archive/**"]
|
|
3676
3700
|
}
|
|
3677
3701
|
}
|
|
3678
3702
|
},
|
|
@@ -6724,6 +6748,583 @@ function createHealthChecker(options = {}) {
|
|
|
6724
6748
|
return new HealthChecker(options);
|
|
6725
6749
|
}
|
|
6726
6750
|
|
|
6751
|
+
// src/memory/types.ts
|
|
6752
|
+
var MEMORY_TYPE_PREFIXES = {
|
|
6753
|
+
"troubleshooting": "TS",
|
|
6754
|
+
"architectural-decision": "AD",
|
|
6755
|
+
"performance": "PF",
|
|
6756
|
+
"pattern": "PT",
|
|
6757
|
+
"integration": "IN",
|
|
6758
|
+
"convention": "CV"
|
|
6759
|
+
};
|
|
6760
|
+
var DEFAULT_MEMORY_CONFIG = {
|
|
6761
|
+
memoryDir: ".fractary/codex/memory",
|
|
6762
|
+
cacheDir: ".fractary/codex/cache",
|
|
6763
|
+
syncedMemoryPatterns: [
|
|
6764
|
+
".fractary/codex/cache/projects/*/*/.fractary/codex/memory/**/*.md"
|
|
6765
|
+
]
|
|
6766
|
+
};
|
|
6767
|
+
function parseFrontmatter(content) {
|
|
6768
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
6769
|
+
if (!match) return null;
|
|
6770
|
+
const yaml3 = match[1];
|
|
6771
|
+
const frontmatter = {};
|
|
6772
|
+
let currentKey = "";
|
|
6773
|
+
let inArray = false;
|
|
6774
|
+
let arrayValues = [];
|
|
6775
|
+
for (const line of yaml3.split("\n")) {
|
|
6776
|
+
const trimmed = line.trim();
|
|
6777
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
6778
|
+
if (trimmed.startsWith("- ") && inArray) {
|
|
6779
|
+
let value = trimmed.slice(2).trim();
|
|
6780
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
6781
|
+
value = value.slice(1, -1);
|
|
6782
|
+
}
|
|
6783
|
+
arrayValues.push(value);
|
|
6784
|
+
continue;
|
|
6785
|
+
}
|
|
6786
|
+
if (inArray && currentKey) {
|
|
6787
|
+
frontmatter[currentKey] = arrayValues;
|
|
6788
|
+
inArray = false;
|
|
6789
|
+
arrayValues = [];
|
|
6790
|
+
currentKey = "";
|
|
6791
|
+
}
|
|
6792
|
+
const kvMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.*)$/);
|
|
6793
|
+
if (kvMatch) {
|
|
6794
|
+
const key = kvMatch[1];
|
|
6795
|
+
let value = kvMatch[2].trim();
|
|
6796
|
+
if (value === "" || value === "[]") {
|
|
6797
|
+
currentKey = key;
|
|
6798
|
+
inArray = true;
|
|
6799
|
+
arrayValues = [];
|
|
6800
|
+
if (value === "[]") {
|
|
6801
|
+
frontmatter[key] = [];
|
|
6802
|
+
inArray = false;
|
|
6803
|
+
currentKey = "";
|
|
6804
|
+
}
|
|
6805
|
+
continue;
|
|
6806
|
+
}
|
|
6807
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
6808
|
+
value = value.slice(1, -1);
|
|
6809
|
+
}
|
|
6810
|
+
if (value === "true") frontmatter[key] = true;
|
|
6811
|
+
else if (value === "false") frontmatter[key] = false;
|
|
6812
|
+
else if (value === "null") frontmatter[key] = null;
|
|
6813
|
+
else if (/^-?\d+$/.test(value)) frontmatter[key] = parseInt(value, 10);
|
|
6814
|
+
else if (/^-?\d+\.\d+$/.test(value)) frontmatter[key] = parseFloat(value);
|
|
6815
|
+
else frontmatter[key] = value;
|
|
6816
|
+
}
|
|
6817
|
+
}
|
|
6818
|
+
if (inArray && currentKey) {
|
|
6819
|
+
frontmatter[currentKey] = arrayValues;
|
|
6820
|
+
}
|
|
6821
|
+
if (!frontmatter.title && !frontmatter.memory_id && !frontmatter.id) {
|
|
6822
|
+
return null;
|
|
6823
|
+
}
|
|
6824
|
+
return frontmatter;
|
|
6825
|
+
}
|
|
6826
|
+
function findMarkdownFiles(dir) {
|
|
6827
|
+
const results = [];
|
|
6828
|
+
if (!fs.existsSync(dir)) return results;
|
|
6829
|
+
try {
|
|
6830
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
6831
|
+
for (const entry of entries) {
|
|
6832
|
+
const fullPath = path3.join(dir, entry.name);
|
|
6833
|
+
if (entry.isDirectory()) {
|
|
6834
|
+
results.push(...findMarkdownFiles(fullPath));
|
|
6835
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
6836
|
+
results.push(fullPath);
|
|
6837
|
+
}
|
|
6838
|
+
}
|
|
6839
|
+
} catch {
|
|
6840
|
+
}
|
|
6841
|
+
return results;
|
|
6842
|
+
}
|
|
6843
|
+
function findSyncedMemoryFiles(cacheDir) {
|
|
6844
|
+
const results = [];
|
|
6845
|
+
const projectsDir = path3.join(cacheDir, "projects");
|
|
6846
|
+
if (!fs.existsSync(projectsDir)) return results;
|
|
6847
|
+
try {
|
|
6848
|
+
const orgs = fs.readdirSync(projectsDir, { withFileTypes: true });
|
|
6849
|
+
for (const org of orgs) {
|
|
6850
|
+
if (!org.isDirectory()) continue;
|
|
6851
|
+
const orgDir = path3.join(projectsDir, org.name);
|
|
6852
|
+
const projects = fs.readdirSync(orgDir, { withFileTypes: true });
|
|
6853
|
+
for (const project of projects) {
|
|
6854
|
+
if (!project.isDirectory()) continue;
|
|
6855
|
+
const memoryDir = path3.join(orgDir, project.name, ".fractary", "codex", "memory");
|
|
6856
|
+
if (!fs.existsSync(memoryDir)) continue;
|
|
6857
|
+
const files = findMarkdownFiles(memoryDir);
|
|
6858
|
+
const source = `${org.name}/${project.name}`;
|
|
6859
|
+
for (const file of files) {
|
|
6860
|
+
results.push({ path: file, source });
|
|
6861
|
+
}
|
|
6862
|
+
}
|
|
6863
|
+
}
|
|
6864
|
+
} catch {
|
|
6865
|
+
}
|
|
6866
|
+
return results;
|
|
6867
|
+
}
|
|
6868
|
+
var MemorySearcher = class {
|
|
6869
|
+
config;
|
|
6870
|
+
projectRoot;
|
|
6871
|
+
constructor(projectRoot, config) {
|
|
6872
|
+
this.projectRoot = projectRoot;
|
|
6873
|
+
this.config = { ...DEFAULT_MEMORY_CONFIG, ...config };
|
|
6874
|
+
}
|
|
6875
|
+
/**
|
|
6876
|
+
* Search memories with cascading scope
|
|
6877
|
+
*
|
|
6878
|
+
* 1. Project memories (highest priority)
|
|
6879
|
+
* 2. Synced memories from other projects
|
|
6880
|
+
*/
|
|
6881
|
+
search(query) {
|
|
6882
|
+
const index = this.loadOrRebuildIndex();
|
|
6883
|
+
let candidates = index.entries.filter((entry) => {
|
|
6884
|
+
const fm = entry.frontmatter;
|
|
6885
|
+
if (query.memory_type && fm.memory_type !== query.memory_type) return false;
|
|
6886
|
+
if (query.category && fm.category !== query.category) return false;
|
|
6887
|
+
if (query.phase) {
|
|
6888
|
+
const phases = fm.phases || (fm.phase ? [fm.phase] : []);
|
|
6889
|
+
if (!phases.includes(query.phase)) return false;
|
|
6890
|
+
}
|
|
6891
|
+
if (query.agent) {
|
|
6892
|
+
const agents = fm.agents || (fm.agent ? [fm.agent] : []);
|
|
6893
|
+
if (!agents.includes(query.agent)) return false;
|
|
6894
|
+
}
|
|
6895
|
+
if (query.tags && query.tags.length > 0) {
|
|
6896
|
+
if (!fm.tags || !query.tags.some((t) => fm.tags.includes(t))) return false;
|
|
6897
|
+
}
|
|
6898
|
+
if (query.status && fm.status !== query.status) return false;
|
|
6899
|
+
return true;
|
|
6900
|
+
});
|
|
6901
|
+
const scored = candidates.map((entry) => ({
|
|
6902
|
+
entry,
|
|
6903
|
+
score: this.calculateRelevanceScore(entry, query),
|
|
6904
|
+
source: entry.source,
|
|
6905
|
+
filePath: entry.file_path
|
|
6906
|
+
}));
|
|
6907
|
+
scored.sort((a, b) => b.score - a.score);
|
|
6908
|
+
const limit = query.limit || 20;
|
|
6909
|
+
return scored.slice(0, limit);
|
|
6910
|
+
}
|
|
6911
|
+
/**
|
|
6912
|
+
* Calculate relevance score for a memory entry
|
|
6913
|
+
*/
|
|
6914
|
+
calculateRelevanceScore(entry, query) {
|
|
6915
|
+
const fm = entry.frontmatter;
|
|
6916
|
+
let score = 0;
|
|
6917
|
+
score += Math.min((fm.success_count || 0) * 2, 40);
|
|
6918
|
+
if (fm.status === "verified" || fm.verified === true) {
|
|
6919
|
+
score += 10;
|
|
6920
|
+
}
|
|
6921
|
+
if (query.agent) {
|
|
6922
|
+
const agents = fm.agents || (fm.agent ? [fm.agent] : []);
|
|
6923
|
+
if (agents.includes(query.agent)) {
|
|
6924
|
+
score += 20;
|
|
6925
|
+
}
|
|
6926
|
+
}
|
|
6927
|
+
if (query.phase) {
|
|
6928
|
+
const phases = fm.phases || (fm.phase ? [fm.phase] : []);
|
|
6929
|
+
if (phases.includes(query.phase)) {
|
|
6930
|
+
score += 10;
|
|
6931
|
+
}
|
|
6932
|
+
}
|
|
6933
|
+
score += entry.source === "local" ? 10 : 5;
|
|
6934
|
+
if (query.text) {
|
|
6935
|
+
const queryLower = query.text.toLowerCase();
|
|
6936
|
+
if (fm.symptoms && fm.symptoms.some((s) => s.toLowerCase().includes(queryLower) || queryLower.includes(s.toLowerCase()))) {
|
|
6937
|
+
score += 15;
|
|
6938
|
+
}
|
|
6939
|
+
if (fm.keywords && fm.keywords.some((k) => queryLower.includes(k.toLowerCase()))) {
|
|
6940
|
+
score += 5;
|
|
6941
|
+
}
|
|
6942
|
+
if (fm.description && fm.description.toLowerCase().includes(queryLower)) {
|
|
6943
|
+
score += 8;
|
|
6944
|
+
}
|
|
6945
|
+
if (fm.title && fm.title.toLowerCase().includes(queryLower)) {
|
|
6946
|
+
score += 5;
|
|
6947
|
+
}
|
|
6948
|
+
}
|
|
6949
|
+
return score;
|
|
6950
|
+
}
|
|
6951
|
+
/**
|
|
6952
|
+
* Load the frontmatter index from cache, or rebuild if stale
|
|
6953
|
+
*/
|
|
6954
|
+
loadOrRebuildIndex() {
|
|
6955
|
+
const indexPath = path3.join(this.projectRoot, this.config.cacheDir, "memory-index.json");
|
|
6956
|
+
let existingIndex = null;
|
|
6957
|
+
if (fs.existsSync(indexPath)) {
|
|
6958
|
+
try {
|
|
6959
|
+
const raw = fs.readFileSync(indexPath, "utf-8");
|
|
6960
|
+
existingIndex = JSON.parse(raw);
|
|
6961
|
+
} catch {
|
|
6962
|
+
existingIndex = null;
|
|
6963
|
+
}
|
|
6964
|
+
}
|
|
6965
|
+
const memoryDir = path3.join(this.projectRoot, this.config.memoryDir);
|
|
6966
|
+
const localFiles = findMarkdownFiles(memoryDir);
|
|
6967
|
+
const syncedFiles = findSyncedMemoryFiles(path3.join(this.projectRoot, this.config.cacheDir));
|
|
6968
|
+
const needsRebuild = !existingIndex || this.isIndexStale(existingIndex, localFiles, syncedFiles.map((f) => f.path));
|
|
6969
|
+
if (!needsRebuild && existingIndex) {
|
|
6970
|
+
return existingIndex;
|
|
6971
|
+
}
|
|
6972
|
+
return this.rebuildIndex(localFiles, syncedFiles, indexPath);
|
|
6973
|
+
}
|
|
6974
|
+
/**
|
|
6975
|
+
* Check if the index is stale (any source file newer than index)
|
|
6976
|
+
*/
|
|
6977
|
+
isIndexStale(index, localFiles, syncedFilePaths) {
|
|
6978
|
+
const indexTime = new Date(index.built_at).getTime();
|
|
6979
|
+
const allFiles = [...localFiles, ...syncedFilePaths];
|
|
6980
|
+
for (const file of allFiles) {
|
|
6981
|
+
try {
|
|
6982
|
+
const stat = fs.statSync(file);
|
|
6983
|
+
if (stat.mtimeMs > indexTime) return true;
|
|
6984
|
+
} catch {
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6987
|
+
const indexedPaths = new Set(index.entries.map((e) => e.file_path));
|
|
6988
|
+
for (const file of allFiles) {
|
|
6989
|
+
if (!indexedPaths.has(file)) return true;
|
|
6990
|
+
}
|
|
6991
|
+
return false;
|
|
6992
|
+
}
|
|
6993
|
+
/**
|
|
6994
|
+
* Rebuild the frontmatter index from all memory sources
|
|
6995
|
+
*/
|
|
6996
|
+
rebuildIndex(localFiles, syncedFiles, indexPath) {
|
|
6997
|
+
const entries = [];
|
|
6998
|
+
for (const file of localFiles) {
|
|
6999
|
+
const entry = this.indexFile(file, "local");
|
|
7000
|
+
if (entry) entries.push(entry);
|
|
7001
|
+
}
|
|
7002
|
+
for (const { path: path9, source } of syncedFiles) {
|
|
7003
|
+
const entry = this.indexFile(path9, source);
|
|
7004
|
+
if (entry) entries.push(entry);
|
|
7005
|
+
}
|
|
7006
|
+
const index = {
|
|
7007
|
+
version: 1,
|
|
7008
|
+
built_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7009
|
+
entries
|
|
7010
|
+
};
|
|
7011
|
+
try {
|
|
7012
|
+
const dir = path3.dirname(indexPath);
|
|
7013
|
+
if (!fs.existsSync(dir)) {
|
|
7014
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
7015
|
+
}
|
|
7016
|
+
fs.writeFileSync(indexPath, JSON.stringify(index, null, 2), "utf-8");
|
|
7017
|
+
} catch {
|
|
7018
|
+
}
|
|
7019
|
+
return index;
|
|
7020
|
+
}
|
|
7021
|
+
/**
|
|
7022
|
+
* Index a single memory file by parsing its frontmatter
|
|
7023
|
+
*/
|
|
7024
|
+
indexFile(filePath, source) {
|
|
7025
|
+
try {
|
|
7026
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
7027
|
+
const frontmatter = parseFrontmatter(content);
|
|
7028
|
+
if (!frontmatter) return null;
|
|
7029
|
+
const stat = fs.statSync(filePath);
|
|
7030
|
+
return {
|
|
7031
|
+
file_path: filePath,
|
|
7032
|
+
mtime: stat.mtime.toISOString(),
|
|
7033
|
+
source,
|
|
7034
|
+
frontmatter
|
|
7035
|
+
};
|
|
7036
|
+
} catch {
|
|
7037
|
+
return null;
|
|
7038
|
+
}
|
|
7039
|
+
}
|
|
7040
|
+
/**
|
|
7041
|
+
* Invalidate the index cache (call after writing memories)
|
|
7042
|
+
*/
|
|
7043
|
+
invalidateIndex() {
|
|
7044
|
+
const indexPath = path3.join(this.projectRoot, this.config.cacheDir, "memory-index.json");
|
|
7045
|
+
try {
|
|
7046
|
+
if (fs.existsSync(indexPath)) {
|
|
7047
|
+
const { unlinkSync } = __require("fs");
|
|
7048
|
+
unlinkSync(indexPath);
|
|
7049
|
+
}
|
|
7050
|
+
} catch {
|
|
7051
|
+
}
|
|
7052
|
+
}
|
|
7053
|
+
};
|
|
7054
|
+
function sanitizeSlug(text, maxLength = 50) {
|
|
7055
|
+
return text.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, maxLength);
|
|
7056
|
+
}
|
|
7057
|
+
function serializeFrontmatter(fm) {
|
|
7058
|
+
const lines = ["---"];
|
|
7059
|
+
for (const [key, value] of Object.entries(fm)) {
|
|
7060
|
+
if (value === void 0 || value === null) continue;
|
|
7061
|
+
if (Array.isArray(value)) {
|
|
7062
|
+
if (value.length === 0) {
|
|
7063
|
+
lines.push(`${key}: []`);
|
|
7064
|
+
} else {
|
|
7065
|
+
lines.push(`${key}:`);
|
|
7066
|
+
for (const item of value) {
|
|
7067
|
+
const itemStr = typeof item === "string" && (item.includes(":") || item.includes('"') || item.includes("'")) ? `"${item.replace(/"/g, '\\"')}"` : String(item);
|
|
7068
|
+
lines.push(` - ${itemStr}`);
|
|
7069
|
+
}
|
|
7070
|
+
}
|
|
7071
|
+
} else if (typeof value === "string") {
|
|
7072
|
+
if (value.includes(":") || value.includes('"') || value.includes("'") || value.includes("\n")) {
|
|
7073
|
+
lines.push(`${key}: "${value.replace(/"/g, '\\"')}"`);
|
|
7074
|
+
} else {
|
|
7075
|
+
lines.push(`${key}: ${value}`);
|
|
7076
|
+
}
|
|
7077
|
+
} else {
|
|
7078
|
+
lines.push(`${key}: ${value}`);
|
|
7079
|
+
}
|
|
7080
|
+
}
|
|
7081
|
+
lines.push("---");
|
|
7082
|
+
return lines.join("\n");
|
|
7083
|
+
}
|
|
7084
|
+
function getNextSequence(typeDir, prefix) {
|
|
7085
|
+
if (!fs.existsSync(typeDir)) return 1;
|
|
7086
|
+
let maxSeq = 0;
|
|
7087
|
+
try {
|
|
7088
|
+
const files = fs.readdirSync(typeDir);
|
|
7089
|
+
const pattern = new RegExp(`^MEM-${prefix}-(\\d+)-`);
|
|
7090
|
+
for (const file of files) {
|
|
7091
|
+
const match = file.match(pattern);
|
|
7092
|
+
if (match) {
|
|
7093
|
+
const seq = parseInt(match[1], 10);
|
|
7094
|
+
if (seq > maxSeq) maxSeq = seq;
|
|
7095
|
+
}
|
|
7096
|
+
}
|
|
7097
|
+
} catch {
|
|
7098
|
+
}
|
|
7099
|
+
return maxSeq + 1;
|
|
7100
|
+
}
|
|
7101
|
+
function calculateSimilarity(existing, newEntry) {
|
|
7102
|
+
let score = 0;
|
|
7103
|
+
let factors = 0;
|
|
7104
|
+
if (existing.title && newEntry.title) {
|
|
7105
|
+
const existingWords = new Set(existing.title.toLowerCase().split(/\s+/));
|
|
7106
|
+
const newWords = new Set(newEntry.title.toLowerCase().split(/\s+/));
|
|
7107
|
+
const intersection = [...existingWords].filter((w) => newWords.has(w)).length;
|
|
7108
|
+
const union = (/* @__PURE__ */ new Set([...existingWords, ...newWords])).size;
|
|
7109
|
+
score += union > 0 ? intersection / union : 0;
|
|
7110
|
+
factors++;
|
|
7111
|
+
}
|
|
7112
|
+
if (existing.symptoms && newEntry.symptoms) {
|
|
7113
|
+
const existingSymptoms = new Set(existing.symptoms.map((s) => s.toLowerCase()));
|
|
7114
|
+
const newSymptoms = new Set(newEntry.symptoms.map((s) => s.toLowerCase()));
|
|
7115
|
+
const intersection = [...existingSymptoms].filter((s) => newSymptoms.has(s)).length;
|
|
7116
|
+
const union = (/* @__PURE__ */ new Set([...existingSymptoms, ...newSymptoms])).size;
|
|
7117
|
+
score += union > 0 ? intersection / union : 0;
|
|
7118
|
+
factors++;
|
|
7119
|
+
}
|
|
7120
|
+
if (existing.category && newEntry.category) {
|
|
7121
|
+
score += existing.category === newEntry.category ? 1 : 0;
|
|
7122
|
+
factors++;
|
|
7123
|
+
}
|
|
7124
|
+
if (existing.memory_type && newEntry.memory_type) {
|
|
7125
|
+
score += existing.memory_type === newEntry.memory_type ? 1 : 0;
|
|
7126
|
+
factors++;
|
|
7127
|
+
}
|
|
7128
|
+
return factors > 0 ? score / factors : 0;
|
|
7129
|
+
}
|
|
7130
|
+
var MemoryWriter = class {
|
|
7131
|
+
config;
|
|
7132
|
+
projectRoot;
|
|
7133
|
+
searcher;
|
|
7134
|
+
constructor(projectRoot, config) {
|
|
7135
|
+
this.projectRoot = projectRoot;
|
|
7136
|
+
this.config = { ...DEFAULT_MEMORY_CONFIG, ...config };
|
|
7137
|
+
this.searcher = new MemorySearcher(projectRoot, config);
|
|
7138
|
+
}
|
|
7139
|
+
/**
|
|
7140
|
+
* Write a new memory entry
|
|
7141
|
+
*
|
|
7142
|
+
* Handles ID generation, deduplication check, file creation,
|
|
7143
|
+
* and index invalidation.
|
|
7144
|
+
*/
|
|
7145
|
+
write(options) {
|
|
7146
|
+
const { memory_type, title, description, body, frontmatter } = options;
|
|
7147
|
+
const dupResult = this.checkDuplicate(frontmatter, memory_type);
|
|
7148
|
+
if (dupResult) {
|
|
7149
|
+
return {
|
|
7150
|
+
memory_id: dupResult.memory_id,
|
|
7151
|
+
file_path: dupResult.file_path,
|
|
7152
|
+
deduplicated: true,
|
|
7153
|
+
existing_id: dupResult.memory_id
|
|
7154
|
+
};
|
|
7155
|
+
}
|
|
7156
|
+
const prefix = MEMORY_TYPE_PREFIXES[memory_type];
|
|
7157
|
+
const typeDir = path3.join(this.projectRoot, this.config.memoryDir, memory_type);
|
|
7158
|
+
const seq = getNextSequence(typeDir, prefix);
|
|
7159
|
+
const slug = sanitizeSlug(title);
|
|
7160
|
+
const memoryId = `MEM-${prefix}-${String(seq).padStart(3, "0")}-${slug}`;
|
|
7161
|
+
const fullFrontmatter = {
|
|
7162
|
+
title,
|
|
7163
|
+
description,
|
|
7164
|
+
memory_type,
|
|
7165
|
+
memory_id: memoryId,
|
|
7166
|
+
status: "draft",
|
|
7167
|
+
created: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
7168
|
+
verified: false,
|
|
7169
|
+
success_count: 0,
|
|
7170
|
+
...frontmatter
|
|
7171
|
+
};
|
|
7172
|
+
fullFrontmatter.memory_type = memory_type;
|
|
7173
|
+
fullFrontmatter.memory_id = memoryId;
|
|
7174
|
+
const fileContent = `${serializeFrontmatter(fullFrontmatter)}
|
|
7175
|
+
|
|
7176
|
+
${body}
|
|
7177
|
+
`;
|
|
7178
|
+
const fileName = `${memoryId}.md`;
|
|
7179
|
+
const filePath = path3.join(typeDir, fileName);
|
|
7180
|
+
if (!fs.existsSync(typeDir)) {
|
|
7181
|
+
fs.mkdirSync(typeDir, { recursive: true });
|
|
7182
|
+
}
|
|
7183
|
+
fs.writeFileSync(filePath, fileContent, "utf-8");
|
|
7184
|
+
this.searcher.invalidateIndex();
|
|
7185
|
+
return {
|
|
7186
|
+
memory_id: memoryId,
|
|
7187
|
+
file_path: filePath,
|
|
7188
|
+
deduplicated: false
|
|
7189
|
+
};
|
|
7190
|
+
}
|
|
7191
|
+
/**
|
|
7192
|
+
* Update an existing memory's frontmatter
|
|
7193
|
+
*/
|
|
7194
|
+
update(memoryId, changes) {
|
|
7195
|
+
const filePath = this.findMemoryFile(memoryId);
|
|
7196
|
+
if (!filePath) {
|
|
7197
|
+
throw new Error(`Memory not found: ${memoryId}`);
|
|
7198
|
+
}
|
|
7199
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
7200
|
+
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
7201
|
+
if (!fmMatch) {
|
|
7202
|
+
throw new Error(`Invalid memory file format: ${filePath}`);
|
|
7203
|
+
}
|
|
7204
|
+
const existingContent = fmMatch[1];
|
|
7205
|
+
const bodyContent = fmMatch[2];
|
|
7206
|
+
const lines = existingContent.split("\n");
|
|
7207
|
+
const updatedFm = {};
|
|
7208
|
+
let currentKey = "";
|
|
7209
|
+
let inArray = false;
|
|
7210
|
+
let arrayValues = [];
|
|
7211
|
+
for (const line of lines) {
|
|
7212
|
+
const trimmed = line.trim();
|
|
7213
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
7214
|
+
if (trimmed.startsWith("- ") && inArray) {
|
|
7215
|
+
let value = trimmed.slice(2).trim();
|
|
7216
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
7217
|
+
value = value.slice(1, -1);
|
|
7218
|
+
}
|
|
7219
|
+
arrayValues.push(value);
|
|
7220
|
+
continue;
|
|
7221
|
+
}
|
|
7222
|
+
if (inArray && currentKey) {
|
|
7223
|
+
updatedFm[currentKey] = arrayValues;
|
|
7224
|
+
inArray = false;
|
|
7225
|
+
arrayValues = [];
|
|
7226
|
+
currentKey = "";
|
|
7227
|
+
}
|
|
7228
|
+
const kvMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.*)$/);
|
|
7229
|
+
if (kvMatch) {
|
|
7230
|
+
const key = kvMatch[1];
|
|
7231
|
+
let value = kvMatch[2].trim();
|
|
7232
|
+
if (value === "" || value === "[]") {
|
|
7233
|
+
currentKey = key;
|
|
7234
|
+
inArray = true;
|
|
7235
|
+
arrayValues = [];
|
|
7236
|
+
if (value === "[]") {
|
|
7237
|
+
updatedFm[key] = [];
|
|
7238
|
+
inArray = false;
|
|
7239
|
+
currentKey = "";
|
|
7240
|
+
}
|
|
7241
|
+
continue;
|
|
7242
|
+
}
|
|
7243
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
7244
|
+
value = value.slice(1, -1);
|
|
7245
|
+
}
|
|
7246
|
+
if (value === "true") updatedFm[key] = true;
|
|
7247
|
+
else if (value === "false") updatedFm[key] = false;
|
|
7248
|
+
else if (value === "null") updatedFm[key] = null;
|
|
7249
|
+
else if (/^-?\d+$/.test(value)) updatedFm[key] = parseInt(value, 10);
|
|
7250
|
+
else if (/^-?\d+\.\d+$/.test(value)) updatedFm[key] = parseFloat(value);
|
|
7251
|
+
else updatedFm[key] = value;
|
|
7252
|
+
}
|
|
7253
|
+
}
|
|
7254
|
+
if (inArray && currentKey) {
|
|
7255
|
+
updatedFm[currentKey] = arrayValues;
|
|
7256
|
+
}
|
|
7257
|
+
const merged = { ...updatedFm, ...changes, updated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] };
|
|
7258
|
+
const newContent = `${serializeFrontmatter(merged)}
|
|
7259
|
+
|
|
7260
|
+
${bodyContent}`;
|
|
7261
|
+
fs.writeFileSync(filePath, newContent, "utf-8");
|
|
7262
|
+
this.searcher.invalidateIndex();
|
|
7263
|
+
}
|
|
7264
|
+
/**
|
|
7265
|
+
* Deprecate a memory
|
|
7266
|
+
*/
|
|
7267
|
+
deprecate(memoryId, reason, supersededBy) {
|
|
7268
|
+
const changes = {
|
|
7269
|
+
status: "deprecated",
|
|
7270
|
+
deprecated_reason: reason
|
|
7271
|
+
};
|
|
7272
|
+
if (supersededBy) {
|
|
7273
|
+
changes.superseded_by = supersededBy;
|
|
7274
|
+
}
|
|
7275
|
+
this.update(memoryId, changes);
|
|
7276
|
+
}
|
|
7277
|
+
/**
|
|
7278
|
+
* Check for duplicate memories (>0.95 similarity)
|
|
7279
|
+
*/
|
|
7280
|
+
checkDuplicate(frontmatter, memoryType) {
|
|
7281
|
+
const results = this.searcher.search({
|
|
7282
|
+
memory_type: memoryType,
|
|
7283
|
+
category: frontmatter.category,
|
|
7284
|
+
limit: 10
|
|
7285
|
+
});
|
|
7286
|
+
for (const result of results) {
|
|
7287
|
+
const similarity = calculateSimilarity(result.entry.frontmatter, frontmatter);
|
|
7288
|
+
if (similarity > 0.95) {
|
|
7289
|
+
return {
|
|
7290
|
+
memory_id: result.entry.frontmatter.memory_id,
|
|
7291
|
+
file_path: result.filePath
|
|
7292
|
+
};
|
|
7293
|
+
}
|
|
7294
|
+
}
|
|
7295
|
+
return null;
|
|
7296
|
+
}
|
|
7297
|
+
/**
|
|
7298
|
+
* Find a memory file by its ID
|
|
7299
|
+
*/
|
|
7300
|
+
findMemoryFile(memoryId) {
|
|
7301
|
+
const memoryDir = path3.join(this.projectRoot, this.config.memoryDir);
|
|
7302
|
+
if (!fs.existsSync(memoryDir)) return null;
|
|
7303
|
+
const files = findMarkdownFilesRecursive(memoryDir);
|
|
7304
|
+
for (const file of files) {
|
|
7305
|
+
if (file.includes(memoryId)) return file;
|
|
7306
|
+
}
|
|
7307
|
+
return null;
|
|
7308
|
+
}
|
|
7309
|
+
};
|
|
7310
|
+
function findMarkdownFilesRecursive(dir) {
|
|
7311
|
+
const results = [];
|
|
7312
|
+
if (!fs.existsSync(dir)) return results;
|
|
7313
|
+
try {
|
|
7314
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
7315
|
+
for (const entry of entries) {
|
|
7316
|
+
const fullPath = path3.join(dir, entry.name);
|
|
7317
|
+
if (entry.isDirectory()) {
|
|
7318
|
+
results.push(...findMarkdownFilesRecursive(fullPath));
|
|
7319
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7320
|
+
results.push(fullPath);
|
|
7321
|
+
}
|
|
7322
|
+
}
|
|
7323
|
+
} catch {
|
|
7324
|
+
}
|
|
7325
|
+
return results;
|
|
7326
|
+
}
|
|
7327
|
+
|
|
6727
7328
|
// src/client/codex-client.ts
|
|
6728
7329
|
var CodexClient = class _CodexClient {
|
|
6729
7330
|
cache;
|
|
@@ -7043,11 +7644,15 @@ exports.CommonRules = CommonRules;
|
|
|
7043
7644
|
exports.ConfigManager = ConfigManager;
|
|
7044
7645
|
exports.DEFAULT_FRACTARY_GITIGNORE = DEFAULT_FRACTARY_GITIGNORE;
|
|
7045
7646
|
exports.DEFAULT_GLOBAL_EXCLUDES = DEFAULT_GLOBAL_EXCLUDES;
|
|
7647
|
+
exports.DEFAULT_MEMORY_CONFIG = DEFAULT_MEMORY_CONFIG;
|
|
7046
7648
|
exports.DEFAULT_MIGRATION_OPTIONS = DEFAULT_MIGRATION_OPTIONS;
|
|
7047
7649
|
exports.DEFAULT_PERMISSION_CONFIG = DEFAULT_PERMISSION_CONFIG;
|
|
7048
7650
|
exports.DEFAULT_SYNC_CONFIG = DEFAULT_SYNC_CONFIG;
|
|
7049
7651
|
exports.HealthChecker = HealthChecker;
|
|
7050
7652
|
exports.LEGACY_PATTERNS = LEGACY_PATTERNS;
|
|
7653
|
+
exports.MEMORY_TYPE_PREFIXES = MEMORY_TYPE_PREFIXES;
|
|
7654
|
+
exports.MemorySearcher = MemorySearcher;
|
|
7655
|
+
exports.MemoryWriter = MemoryWriter;
|
|
7051
7656
|
exports.MetadataSchema = MetadataSchema;
|
|
7052
7657
|
exports.PERMISSION_LEVEL_ORDER = PERMISSION_LEVEL_ORDER;
|
|
7053
7658
|
exports.PermissionDeniedError = PermissionDeniedError;
|