@aipper/aiws 0.0.43 → 0.0.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +114 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/memory.d.ts +35 -0
- package/dist/commands/memory.js +239 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/memory-bank/disclosure.d.ts +24 -0
- package/dist/memory-bank/disclosure.js +143 -0
- package/dist/memory-bank/disclosure.js.map +1 -0
- package/dist/memory-bank/index.d.ts +24 -0
- package/dist/memory-bank/index.js +137 -0
- package/dist/memory-bank/index.js.map +1 -0
- package/dist/memory-bank/link.d.ts +21 -0
- package/dist/memory-bank/link.js +113 -0
- package/dist/memory-bank/link.js.map +1 -0
- package/dist/memory-bank/search.d.ts +6 -0
- package/dist/memory-bank/search.js +88 -0
- package/dist/memory-bank/search.js.map +1 -0
- package/dist/memory-bank/seed.d.ts +23 -0
- package/dist/memory-bank/seed.js +164 -0
- package/dist/memory-bank/seed.js.map +1 -0
- package/dist/memory-bank/store.d.ts +24 -0
- package/dist/memory-bank/store.js +193 -0
- package/dist/memory-bank/store.js.map +1 -0
- package/dist/memory-bank/types.d.ts +45 -0
- package/dist/memory-bank/types.js +2 -0
- package/dist/memory-bank/types.js.map +1 -0
- package/dist/memory-bank/uri.d.ts +16 -0
- package/dist/memory-bank/uri.js +52 -0
- package/dist/memory-bank/uri.js.map +1 -0
- package/dist/workspace.d.ts +1 -0
- package/dist/workspace.js +13 -0
- package/dist/workspace.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { loadIndex } from "./index.js";
|
|
2
|
+
import { listLinks } from "./link.js";
|
|
3
|
+
import { readMemory } from "./store.js";
|
|
4
|
+
const MAX_RELATED = 10;
|
|
5
|
+
const BOOT_SNIPPET_LEN = 200;
|
|
6
|
+
/**
|
|
7
|
+
* Tokenize a string for token-set matching.
|
|
8
|
+
* Splits on whitespace and punctuation, lowercases all tokens.
|
|
9
|
+
*/
|
|
10
|
+
function tokenize(text) {
|
|
11
|
+
return new Set(text
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.split(/[\s,.;:!?'"`/\\()[\]{}|&^*+=~%@#$<>-]+/)
|
|
14
|
+
.filter((t) => t.length > 0));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check if a `tag:keyword` disclosure rule matches the context.
|
|
18
|
+
* Uses token-set membership (NOT substring) to avoid false positives
|
|
19
|
+
* like `tag:rust` matching "frustrated".
|
|
20
|
+
*/
|
|
21
|
+
function tagMatches(disclosure, contextTokens) {
|
|
22
|
+
const kw = disclosure.slice(4).trim().toLowerCase(); // "tag:rust" → "rust"
|
|
23
|
+
if (!kw)
|
|
24
|
+
return false;
|
|
25
|
+
const kwTokens = tokenize(kw);
|
|
26
|
+
// Match if ALL kw tokens are present in context
|
|
27
|
+
for (const t of kwTokens) {
|
|
28
|
+
if (!contextTokens.has(t))
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve related memories for a given URI based on disclosure rules.
|
|
35
|
+
*
|
|
36
|
+
* Disclosure rules (evaluated per linked target):
|
|
37
|
+
* - `always` → injected on every read
|
|
38
|
+
* - `boot` → injected on session boot (use listBootMemories instead)
|
|
39
|
+
* - `parent` → target注入 when source URI is read (A→B, B has `parent` → inject B when A read)
|
|
40
|
+
* - `tag:<kw>` → injected when kw is in the context token set
|
|
41
|
+
*
|
|
42
|
+
* @param wsRoot workspace root
|
|
43
|
+
* @param uri source URI being read
|
|
44
|
+
* @param context optional context string (e.g. source memory tags + title)
|
|
45
|
+
* @returns array of related entries, capped at MAX_RELATED
|
|
46
|
+
*/
|
|
47
|
+
export async function resolveRelated(wsRoot, uri, context) {
|
|
48
|
+
const index = await loadIndex(wsRoot);
|
|
49
|
+
const { outgoing } = await listLinks(wsRoot, uri);
|
|
50
|
+
// Build context token set from:
|
|
51
|
+
// 1. source memory's tags + title (if exists in index)
|
|
52
|
+
// 2. caller-provided context string
|
|
53
|
+
const sourceEntry = index.memories[uri];
|
|
54
|
+
const contextParts = [];
|
|
55
|
+
if (sourceEntry) {
|
|
56
|
+
contextParts.push(sourceEntry.title);
|
|
57
|
+
contextParts.push(...sourceEntry.tags);
|
|
58
|
+
}
|
|
59
|
+
if (context) {
|
|
60
|
+
contextParts.push(context);
|
|
61
|
+
}
|
|
62
|
+
const contextTokens = tokenize(contextParts.join(" "));
|
|
63
|
+
const results = [];
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
for (const link of outgoing) {
|
|
66
|
+
if (results.length >= MAX_RELATED)
|
|
67
|
+
break;
|
|
68
|
+
const targetUri = link.to;
|
|
69
|
+
if (seen.has(targetUri))
|
|
70
|
+
continue;
|
|
71
|
+
seen.add(targetUri);
|
|
72
|
+
const targetEntry = index.memories[targetUri];
|
|
73
|
+
if (!targetEntry)
|
|
74
|
+
continue; // dangling link — skip
|
|
75
|
+
const disclosure = targetEntry.disclosure;
|
|
76
|
+
if (!disclosure)
|
|
77
|
+
continue;
|
|
78
|
+
const matched = disclosure === "always" ||
|
|
79
|
+
disclosure === "parent" ||
|
|
80
|
+
(disclosure.startsWith("tag:") && tagMatches(disclosure, contextTokens));
|
|
81
|
+
if (!matched)
|
|
82
|
+
continue;
|
|
83
|
+
// Read file for snippet (index already gave us title)
|
|
84
|
+
let snippet = "";
|
|
85
|
+
try {
|
|
86
|
+
const mem = await readMemory(wsRoot, targetUri);
|
|
87
|
+
snippet = mem.body.slice(0, BOOT_SNIPPET_LEN).trim();
|
|
88
|
+
if (mem.body.length > BOOT_SNIPPET_LEN)
|
|
89
|
+
snippet += "...";
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Dangling link or unreadable file — skip
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
results.push({
|
|
96
|
+
uri: targetUri,
|
|
97
|
+
title: targetEntry.title,
|
|
98
|
+
disclosure,
|
|
99
|
+
snippet,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return results;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* List memories marked with `disclosure: boot`.
|
|
106
|
+
* Scan index only — zero file reads for match decision.
|
|
107
|
+
* Read file only for snippet when matched.
|
|
108
|
+
*
|
|
109
|
+
* @returns array of boot memories with snippets, capped at MAX_RELATED
|
|
110
|
+
*/
|
|
111
|
+
export async function listBootMemories(wsRoot) {
|
|
112
|
+
const index = await loadIndex(wsRoot);
|
|
113
|
+
const results = [];
|
|
114
|
+
// Sort by updated desc for deterministic ordering
|
|
115
|
+
const candidates = Object.entries(index.memories)
|
|
116
|
+
.filter(([, entry]) => {
|
|
117
|
+
const d = entry.disclosure;
|
|
118
|
+
return d === "boot" || d === "always";
|
|
119
|
+
})
|
|
120
|
+
.sort(([, a], [, b]) => (b.updated || "").localeCompare(a.updated || ""));
|
|
121
|
+
for (const [uri, entry] of candidates) {
|
|
122
|
+
if (results.length >= MAX_RELATED)
|
|
123
|
+
break;
|
|
124
|
+
let snippet = "";
|
|
125
|
+
try {
|
|
126
|
+
const mem = await readMemory(wsRoot, uri);
|
|
127
|
+
snippet = mem.body.slice(0, BOOT_SNIPPET_LEN).trim();
|
|
128
|
+
if (mem.body.length > BOOT_SNIPPET_LEN)
|
|
129
|
+
snippet += "...";
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
results.push({
|
|
135
|
+
uri,
|
|
136
|
+
title: entry.title,
|
|
137
|
+
disclosure: entry.disclosure || "boot",
|
|
138
|
+
snippet,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=disclosure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disclosure.js","sourceRoot":"","sources":["../../src/memory-bank/disclosure.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { IndexData, IndexEntry } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Load the memory index from .index.yaml.
|
|
4
|
+
* Returns an empty IndexData if the file doesn't exist or is invalid.
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadIndex(wsRoot: string): Promise<IndexData>;
|
|
7
|
+
/**
|
|
8
|
+
* Save the memory index to .index.yaml.
|
|
9
|
+
*/
|
|
10
|
+
export declare function saveIndex(wsRoot: string, data: IndexData): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Update or insert a single entry in the index.
|
|
13
|
+
*/
|
|
14
|
+
export declare function updateIndex(wsRoot: string, uri: string, entry: IndexEntry): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Remove an entry from the index.
|
|
17
|
+
*/
|
|
18
|
+
export declare function removeFromIndex(wsRoot: string, uri: string): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Rebuild the entire index by scanning all MD files in memory-bank/.
|
|
21
|
+
* For existing files without frontmatter, it auto-generates metadata
|
|
22
|
+
* and writes updated frontmatter back with the 'updated' timestamp.
|
|
23
|
+
*/
|
|
24
|
+
export declare function rebuildIndex(wsRoot: string): Promise<IndexData>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
import { getMemoryBankDir, readMemory, collectMemoryFiles } from "./store.js";
|
|
5
|
+
import { pathExists } from "../fs.js";
|
|
6
|
+
const INDEX_FILENAME = ".index.yaml";
|
|
7
|
+
/**
|
|
8
|
+
* Get the absolute path to the index file.
|
|
9
|
+
*/
|
|
10
|
+
function getIndexPath(wsRoot) {
|
|
11
|
+
return path.join(getMemoryBankDir(wsRoot), INDEX_FILENAME);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Load the memory index from .index.yaml.
|
|
15
|
+
* Returns an empty IndexData if the file doesn't exist or is invalid.
|
|
16
|
+
*/
|
|
17
|
+
export async function loadIndex(wsRoot) {
|
|
18
|
+
const indexPath = getIndexPath(wsRoot);
|
|
19
|
+
if (!(await pathExists(indexPath))) {
|
|
20
|
+
return { memories: {} };
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const raw = await fs.readFile(indexPath, "utf8");
|
|
24
|
+
const parsed = yaml.load(raw);
|
|
25
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
26
|
+
const data = parsed;
|
|
27
|
+
const memories = data.memories;
|
|
28
|
+
if (memories && typeof memories === "object" && !Array.isArray(memories)) {
|
|
29
|
+
const result = { memories: {} };
|
|
30
|
+
for (const [key, val] of Object.entries(memories)) {
|
|
31
|
+
if (val && typeof val === "object") {
|
|
32
|
+
const entry = val;
|
|
33
|
+
result.memories[key] = {
|
|
34
|
+
title: String(entry.title ?? ""),
|
|
35
|
+
domain: String(entry.domain ?? ""),
|
|
36
|
+
path: String(entry.path ?? ""),
|
|
37
|
+
created: String(entry.created ?? ""),
|
|
38
|
+
updated: String(entry.updated ?? ""),
|
|
39
|
+
tags: Array.isArray(entry.tags) ? entry.tags.map(String) : [],
|
|
40
|
+
disclosure: entry.disclosure ? String(entry.disclosure) : undefined,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { memories: {} };
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return { memories: {} };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Save the memory index to .index.yaml.
|
|
55
|
+
*/
|
|
56
|
+
export async function saveIndex(wsRoot, data) {
|
|
57
|
+
const indexPath = getIndexPath(wsRoot);
|
|
58
|
+
const yamlStr = yaml.dump(data, { lineWidth: 120, noRefs: true });
|
|
59
|
+
await fs.writeFile(indexPath, yamlStr, "utf8");
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Update or insert a single entry in the index.
|
|
63
|
+
*/
|
|
64
|
+
export async function updateIndex(wsRoot, uri, entry) {
|
|
65
|
+
const data = await loadIndex(wsRoot);
|
|
66
|
+
data.memories[uri] = entry;
|
|
67
|
+
await saveIndex(wsRoot, data);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Remove an entry from the index.
|
|
71
|
+
*/
|
|
72
|
+
export async function removeFromIndex(wsRoot, uri) {
|
|
73
|
+
const data = await loadIndex(wsRoot);
|
|
74
|
+
delete data.memories[uri];
|
|
75
|
+
await saveIndex(wsRoot, data);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Rebuild the entire index by scanning all MD files in memory-bank/.
|
|
79
|
+
* For existing files without frontmatter, it auto-generates metadata
|
|
80
|
+
* and writes updated frontmatter back with the 'updated' timestamp.
|
|
81
|
+
*/
|
|
82
|
+
export async function rebuildIndex(wsRoot) {
|
|
83
|
+
const files = await collectMemoryFiles(wsRoot);
|
|
84
|
+
const data = { memories: {} };
|
|
85
|
+
for (const relPath of files) {
|
|
86
|
+
// Derive URI from relative path:
|
|
87
|
+
// domain/path.md -> domain://path (file in subdirectory)
|
|
88
|
+
// filename.md -> general://filename (flat file, backward compat)
|
|
89
|
+
const parts = relPath.replaceAll(path.sep, "/").split("/");
|
|
90
|
+
let domain;
|
|
91
|
+
let name;
|
|
92
|
+
if (parts.length > 1) {
|
|
93
|
+
domain = parts[0] ?? "general";
|
|
94
|
+
name = (parts[parts.length - 1] ?? "").replace(/\.md$/, "");
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
domain = "general";
|
|
98
|
+
name = (parts[0] ?? "").replace(/\.md$/, "");
|
|
99
|
+
}
|
|
100
|
+
const uri = `${domain}://${name}`;
|
|
101
|
+
try {
|
|
102
|
+
const entry = await readMemory(wsRoot, uri);
|
|
103
|
+
// If the file had no frontmatter (auto-inferred), write it back
|
|
104
|
+
if (!entry.frontmatter || Object.keys(entry.frontmatter).length === 0) {
|
|
105
|
+
const frontmatter = {
|
|
106
|
+
title: entry.title,
|
|
107
|
+
created: entry.created,
|
|
108
|
+
updated: new Date().toISOString(),
|
|
109
|
+
};
|
|
110
|
+
if (entry.tags.length > 0)
|
|
111
|
+
frontmatter.tags = entry.tags;
|
|
112
|
+
if (entry.disclosure)
|
|
113
|
+
frontmatter.disclosure = entry.disclosure;
|
|
114
|
+
const memDir = getMemoryBankDir(wsRoot);
|
|
115
|
+
const absPath = path.join(memDir, relPath);
|
|
116
|
+
const yamlStr = yaml.dump(frontmatter, { lineWidth: 120, noRefs: true });
|
|
117
|
+
const content = `---\n${yamlStr}---\n${entry.body}\n`;
|
|
118
|
+
await fs.writeFile(absPath, content, "utf8");
|
|
119
|
+
}
|
|
120
|
+
data.memories[uri] = {
|
|
121
|
+
title: entry.title,
|
|
122
|
+
domain: entry.domain,
|
|
123
|
+
path: entry.path,
|
|
124
|
+
created: entry.created,
|
|
125
|
+
updated: entry.updated,
|
|
126
|
+
tags: entry.tags,
|
|
127
|
+
disclosure: entry.disclosure,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Skip files that can't be read
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
await saveIndex(wsRoot, data);
|
|
135
|
+
return data;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/memory-bank/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Link } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Get the absolute path to the links database file.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getLinksDbPath(wsRoot: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Add a directed link from `from` to `to`.
|
|
8
|
+
* Validates both URIs. Warns if either URI is not in the index.
|
|
9
|
+
*/
|
|
10
|
+
export declare function addLink(wsRoot: string, from: string, to: string): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* List outgoing and incoming links for a URI.
|
|
13
|
+
*/
|
|
14
|
+
export declare function listLinks(wsRoot: string, uri: string): Promise<{
|
|
15
|
+
outgoing: Link[];
|
|
16
|
+
incoming: Link[];
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Remove a specific link entry.
|
|
20
|
+
*/
|
|
21
|
+
export declare function removeLink(wsRoot: string, from: string, to: string): Promise<void>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
import { getMemoryBankDir } from "./store.js";
|
|
5
|
+
import { validateUri } from "./uri.js";
|
|
6
|
+
import { loadIndex } from "./index.js";
|
|
7
|
+
import { pathExists, ensureDir } from "../fs.js";
|
|
8
|
+
const LINKS_FILENAME = ".links.yaml";
|
|
9
|
+
/**
|
|
10
|
+
* Get the absolute path to the links database file.
|
|
11
|
+
*/
|
|
12
|
+
export function getLinksDbPath(wsRoot) {
|
|
13
|
+
return path.join(getMemoryBankDir(wsRoot), LINKS_FILENAME);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Load the links database.
|
|
17
|
+
*/
|
|
18
|
+
async function loadLinksDb(wsRoot) {
|
|
19
|
+
const dbPath = getLinksDbPath(wsRoot);
|
|
20
|
+
if (!(await pathExists(dbPath))) {
|
|
21
|
+
return { links: [] };
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const raw = await fs.readFile(dbPath, "utf8");
|
|
25
|
+
const parsed = yaml.load(raw);
|
|
26
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
27
|
+
const data = parsed;
|
|
28
|
+
const links = data.links;
|
|
29
|
+
if (Array.isArray(links)) {
|
|
30
|
+
return {
|
|
31
|
+
links: links.map((l) => {
|
|
32
|
+
const entry = l;
|
|
33
|
+
return {
|
|
34
|
+
from: String(entry.from ?? ""),
|
|
35
|
+
to: String(entry.to ?? ""),
|
|
36
|
+
created: String(entry.created ?? ""),
|
|
37
|
+
};
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { links: [] };
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return { links: [] };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Save the links database.
|
|
50
|
+
*/
|
|
51
|
+
async function saveLinksDb(wsRoot, db) {
|
|
52
|
+
const dbPath = getLinksDbPath(wsRoot);
|
|
53
|
+
await ensureDir(path.dirname(dbPath));
|
|
54
|
+
const yamlStr = yaml.dump(db, { lineWidth: 120, noRefs: true });
|
|
55
|
+
await fs.writeFile(dbPath, yamlStr, "utf8");
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Add a directed link from `from` to `to`.
|
|
59
|
+
* Validates both URIs. Warns if either URI is not in the index.
|
|
60
|
+
*/
|
|
61
|
+
export async function addLink(wsRoot, from, to) {
|
|
62
|
+
if (!validateUri(from)) {
|
|
63
|
+
throw new Error(`Invalid from URI: ${from}`);
|
|
64
|
+
}
|
|
65
|
+
if (!validateUri(to)) {
|
|
66
|
+
throw new Error(`Invalid to URI: ${to}`);
|
|
67
|
+
}
|
|
68
|
+
const index = await loadIndex(wsRoot);
|
|
69
|
+
if (!index.memories[from]) {
|
|
70
|
+
console.warn(`Warning: from-uri "${from}" not found in memory index`);
|
|
71
|
+
}
|
|
72
|
+
if (!index.memories[to]) {
|
|
73
|
+
console.warn(`Warning: to-uri "${to}" not found in memory index`);
|
|
74
|
+
}
|
|
75
|
+
const db = await loadLinksDb(wsRoot);
|
|
76
|
+
// Avoid duplicate entries
|
|
77
|
+
const exists = db.links.some((l) => l.from === from && l.to === to);
|
|
78
|
+
if (!exists) {
|
|
79
|
+
db.links.push({
|
|
80
|
+
from,
|
|
81
|
+
to,
|
|
82
|
+
created: new Date().toISOString(),
|
|
83
|
+
});
|
|
84
|
+
await saveLinksDb(wsRoot, db);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* List outgoing and incoming links for a URI.
|
|
89
|
+
*/
|
|
90
|
+
export async function listLinks(wsRoot, uri) {
|
|
91
|
+
if (!validateUri(uri)) {
|
|
92
|
+
throw new Error(`Invalid URI: ${uri}`);
|
|
93
|
+
}
|
|
94
|
+
const db = await loadLinksDb(wsRoot);
|
|
95
|
+
const outgoing = db.links.filter((l) => l.from === uri);
|
|
96
|
+
const incoming = db.links.filter((l) => l.to === uri);
|
|
97
|
+
return { outgoing, incoming };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Remove a specific link entry.
|
|
101
|
+
*/
|
|
102
|
+
export async function removeLink(wsRoot, from, to) {
|
|
103
|
+
if (!validateUri(from)) {
|
|
104
|
+
throw new Error(`Invalid from URI: ${from}`);
|
|
105
|
+
}
|
|
106
|
+
if (!validateUri(to)) {
|
|
107
|
+
throw new Error(`Invalid to URI: ${to}`);
|
|
108
|
+
}
|
|
109
|
+
const db = await loadLinksDb(wsRoot);
|
|
110
|
+
db.links = db.links.filter((l) => !(l.from === from && l.to === to));
|
|
111
|
+
await saveLinksDb(wsRoot, db);
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link.js","sourceRoot":"","sources":["../../src/memory-bank/link.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { loadIndex } from "./index.js";
|
|
2
|
+
import { readMemory } from "./store.js";
|
|
3
|
+
import { getMemoryBankDir } from "./store.js";
|
|
4
|
+
import { parseUri, uriToRelPath } from "./uri.js";
|
|
5
|
+
import { pathExists } from "../fs.js";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
const MAX_RESULTS = 20;
|
|
9
|
+
const SNIPPET_LENGTH = 120;
|
|
10
|
+
/**
|
|
11
|
+
* Search memory entries by keyword.
|
|
12
|
+
* Searches index (title + tags) first, then body content.
|
|
13
|
+
*/
|
|
14
|
+
export async function searchMemories(wsRoot, query) {
|
|
15
|
+
const q = query.toLowerCase().trim();
|
|
16
|
+
if (!q)
|
|
17
|
+
return [];
|
|
18
|
+
const index = await loadIndex(wsRoot);
|
|
19
|
+
const results = [];
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
// Phase 1: Search index for title/tag matches
|
|
22
|
+
const indexMatches = [];
|
|
23
|
+
for (const [uri, entry] of Object.entries(index.memories)) {
|
|
24
|
+
const titleMatch = entry.title.toLowerCase().includes(q);
|
|
25
|
+
const tagMatch = entry.tags.some((t) => t.toLowerCase().includes(q));
|
|
26
|
+
if (titleMatch || tagMatch) {
|
|
27
|
+
indexMatches.push(uri);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Phase 2: If no index matches, search all memories; otherwise search matches + index
|
|
31
|
+
const urisToSearch = indexMatches.length > 0
|
|
32
|
+
? indexMatches
|
|
33
|
+
: Object.keys(index.memories);
|
|
34
|
+
for (const uri of urisToSearch) {
|
|
35
|
+
if (results.length >= MAX_RESULTS)
|
|
36
|
+
break;
|
|
37
|
+
if (seen.has(uri))
|
|
38
|
+
continue;
|
|
39
|
+
seen.add(uri);
|
|
40
|
+
try {
|
|
41
|
+
const entry = index.memories[uri];
|
|
42
|
+
const title = entry?.title ?? uri;
|
|
43
|
+
// Read body from file for snippet
|
|
44
|
+
const memDir = getMemoryBankDir(wsRoot);
|
|
45
|
+
let bodyText = "";
|
|
46
|
+
try {
|
|
47
|
+
const mem = await readMemory(wsRoot, uri);
|
|
48
|
+
bodyText = mem.body;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Try direct read
|
|
52
|
+
const { domain, path: uriPath } = parseUri(uri);
|
|
53
|
+
const relPath = uriToRelPath(domain, uriPath);
|
|
54
|
+
const absPath = path.join(memDir, relPath);
|
|
55
|
+
if (await pathExists(absPath)) {
|
|
56
|
+
bodyText = await fs.readFile(absPath, "utf8");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const bodyMatch = bodyText.toLowerCase().includes(q);
|
|
60
|
+
const isIndexMatch = indexMatches.includes(uri);
|
|
61
|
+
if (!bodyMatch && !isIndexMatch)
|
|
62
|
+
continue;
|
|
63
|
+
// Generate snippet
|
|
64
|
+
const lowerBody = bodyText.toLowerCase();
|
|
65
|
+
const matchIdx = lowerBody.indexOf(q);
|
|
66
|
+
let snippet;
|
|
67
|
+
if (matchIdx !== -1) {
|
|
68
|
+
const start = Math.max(0, matchIdx - 40);
|
|
69
|
+
snippet = bodyText.slice(start, start + SNIPPET_LENGTH).trim();
|
|
70
|
+
if (start > 0)
|
|
71
|
+
snippet = "..." + snippet;
|
|
72
|
+
if (start + SNIPPET_LENGTH < bodyText.length)
|
|
73
|
+
snippet = snippet + "...";
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
snippet = bodyText.slice(0, SNIPPET_LENGTH).trim();
|
|
77
|
+
if (bodyText.length > SNIPPET_LENGTH)
|
|
78
|
+
snippet += "...";
|
|
79
|
+
}
|
|
80
|
+
results.push({ uri, title, snippet });
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Skip unreadable entries
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return results.slice(0, MAX_RESULTS);
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/memory-bank/search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of a seed operation.
|
|
3
|
+
*/
|
|
4
|
+
export interface SeedResult {
|
|
5
|
+
written: number;
|
|
6
|
+
skipped: number;
|
|
7
|
+
uris: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Seed project truth files and artifacts into the memory bank.
|
|
11
|
+
*
|
|
12
|
+
* Scans configured source files in the workspace, reads their content,
|
|
13
|
+
* and writes them as persistent memory entries under a predefined URI scheme.
|
|
14
|
+
*
|
|
15
|
+
* @param wsRoot - Absolute path to the workspace root (must contain .aiws/)
|
|
16
|
+
* @param options.dryRun - If true, only collect URIs without writing
|
|
17
|
+
* @param options.force - If true, overwrite existing memory entries
|
|
18
|
+
* @returns SeedResult with counts of written/skipped entries and list of URIs
|
|
19
|
+
*/
|
|
20
|
+
export declare function seedMemories(wsRoot: string, options?: {
|
|
21
|
+
dryRun?: boolean;
|
|
22
|
+
force?: boolean;
|
|
23
|
+
}): Promise<SeedResult>;
|