pi-harness-runtime 0.10.23 → 0.10.25
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 +39 -0
- package/package.json +1 -1
- package/packages/file-copy-helper/src/extension.js +98 -0
- package/packages/file-copy-helper/src/extension.ts +60 -12
- package/packages/todo-bd-sync/dist/src/detector.js +108 -0
- package/packages/todo-bd-sync/src/detector.ts +1 -1
- package/packages/auth/src/minimax-browser-auth.js +0 -889
- package/packages/capability-registry/src/types.js +0 -10
- package/packages/checkpoint/src/checkpoint-manager.js +0 -29
- package/packages/clipboard/src/copy-sync.js +0 -169
- package/packages/clipboard/src/gist-relay.js +0 -185
- package/packages/clipboard/src/github-login.js +0 -68
- package/packages/config-capture/src/index.js +0 -268
- package/packages/cookie-sanitizer/src/atomic-write.js +0 -66
- package/packages/cookie-sanitizer/src/detect-format.js +0 -87
- package/packages/cookie-sanitizer/src/index.js +0 -38
- package/packages/cookie-sanitizer/src/infer-provider.js +0 -95
- package/packages/cookie-sanitizer/src/normalize.js +0 -74
- package/packages/cookie-sanitizer/src/parse-json.js +0 -107
- package/packages/cookie-sanitizer/src/parse-netscape.js +0 -98
- package/packages/cookie-sanitizer/src/sync.js +0 -268
- package/packages/cookie-sanitizer/src/types.js +0 -12
- package/packages/cookie-sanitizer/src/watcher.js +0 -147
- package/packages/evaluation-engine/src/engine.js +0 -343
- package/packages/evaluation-engine/src/index.js +0 -7
- package/packages/evaluation-engine/src/types.js +0 -14
- package/packages/event-store/src/types.js +0 -5
- package/packages/experience-replay/src/index.js +0 -6
- package/packages/experience-replay/src/replay.js +0 -228
- package/packages/experience-replay/src/types.js +0 -6
- package/packages/learning-engine/src/engine.js +0 -294
- package/packages/learning-engine/src/index.js +0 -7
- package/packages/learning-engine/src/types.js +0 -63
- package/packages/memory-engine/src/engine.js +0 -507
- package/packages/memory-engine/src/index.js +0 -7
- package/packages/memory-engine/src/types.js +0 -39
- package/packages/model-registry/src/types.js +0 -5
- package/packages/performance-optimizer/src/index.js +0 -6
- package/packages/performance-optimizer/src/optimizer.js +0 -304
- package/packages/performance-optimizer/src/types.js +0 -6
- package/packages/projection-engine/src/types.js +0 -5
- package/packages/providers/src/provider-id.js +0 -112
- package/packages/quota-manager/src/quota-manager.js +0 -259
- package/packages/quota-manager/src/tui-usage-monitor.js +0 -267
- package/packages/todo-bd-sync/src/detector.js +0 -131
- package/packages/todo-bd-sync/src/extension.js +0 -104
- package/packages/todo-bd-sync/src/sync.js +0 -289
- package/packages/todo-bd-sync/src/task-analyzer.js +0 -239
- package/packages/todo-bd-sync/src/types.js +0 -66
- package/packages/token-estimation/src/index.js +0 -114
- package/packages/workflow-events/src/types.js +0 -5
- package/packages/write-review/src/blackboard.js +0 -228
- package/packages/write-review/src/gate.js +0 -137
- package/packages/write-review/src/index.js +0 -17
- package/packages/write-review/src/injection.js +0 -141
- package/packages/write-review/src/review.js +0 -153
- package/packages/write-review/src/trigger.js +0 -106
- package/packages/write-review/src/types.js +0 -25
|
@@ -1,507 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory Engine (RFC-0060)
|
|
3
|
-
*
|
|
4
|
-
* Manages durable knowledge using Google's Open Knowledge Format (OKF).
|
|
5
|
-
*/
|
|
6
|
-
import { randomUUID } from "node:crypto";
|
|
7
|
-
import * as fs from "node:fs/promises";
|
|
8
|
-
import * as path from "node:path";
|
|
9
|
-
import { RESERVED_FILES } from "./types.js";
|
|
10
|
-
import { filterSecrets, AUTHORITY_PRIORITY } from "./types.js";
|
|
11
|
-
// --- YAML Frontmatter Parsing -------------------------------------------------
|
|
12
|
-
function parseFrontmatter(content) {
|
|
13
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
14
|
-
if (!match) {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
const [, yamlStr, body] = match;
|
|
18
|
-
const frontmatter = { type: "" };
|
|
19
|
-
const lines = yamlStr.split("\n");
|
|
20
|
-
for (const line of lines) {
|
|
21
|
-
const colonIdx = line.indexOf(":");
|
|
22
|
-
if (colonIdx === -1)
|
|
23
|
-
continue;
|
|
24
|
-
const key = line.slice(0, colonIdx).trim();
|
|
25
|
-
const value = line.slice(colonIdx + 1).trim();
|
|
26
|
-
if (value.startsWith("[") && value.endsWith("]")) {
|
|
27
|
-
// Array
|
|
28
|
-
frontmatter[key] = value
|
|
29
|
-
.slice(1, -1)
|
|
30
|
-
.split(",")
|
|
31
|
-
.map((s) => s.trim());
|
|
32
|
-
}
|
|
33
|
-
else if (value === "true") {
|
|
34
|
-
frontmatter[key] = true;
|
|
35
|
-
}
|
|
36
|
-
else if (value === "false") {
|
|
37
|
-
frontmatter[key] = false;
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
frontmatter[key] = value.replace(/^["']|["']$/g, "");
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return { frontmatter, body: body.trim(), raw: yamlStr };
|
|
44
|
-
}
|
|
45
|
-
function serializeFrontmatter(data) {
|
|
46
|
-
const lines = [];
|
|
47
|
-
for (const [key, value] of Object.entries(data)) {
|
|
48
|
-
if (value === undefined || value === null)
|
|
49
|
-
continue;
|
|
50
|
-
if (Array.isArray(value)) {
|
|
51
|
-
lines.push(`${key}: [${value.join(", ")}]`);
|
|
52
|
-
}
|
|
53
|
-
else if (typeof value === "boolean") {
|
|
54
|
-
lines.push(`${key}: ${value}`);
|
|
55
|
-
}
|
|
56
|
-
else if (typeof value === "object") {
|
|
57
|
-
lines.push(`${key}: ${JSON.stringify(value)}`);
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
lines.push(`${key}: ${value}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return lines.join("\n");
|
|
64
|
-
}
|
|
65
|
-
// --- Concept Parsing --------------------------------------------------------
|
|
66
|
-
// --- Link Extraction ---------------------------------------------------------
|
|
67
|
-
/**
|
|
68
|
-
* Extract markdown links from content
|
|
69
|
-
* Used for link validation and concept parsing
|
|
70
|
-
*/
|
|
71
|
-
export function extractLinks(markdown) {
|
|
72
|
-
const links = [];
|
|
73
|
-
// Extract markdown links
|
|
74
|
-
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
75
|
-
let match;
|
|
76
|
-
while ((match = linkRegex.exec(markdown)) !== null) {
|
|
77
|
-
links.push({
|
|
78
|
-
href: match[2],
|
|
79
|
-
title: match[1],
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
return links;
|
|
83
|
-
}
|
|
84
|
-
// --- Validation --------------------------------------------------------------
|
|
85
|
-
export function validateConcept(content, path) {
|
|
86
|
-
const errors = [];
|
|
87
|
-
const warnings = [];
|
|
88
|
-
// Check for YAML frontmatter
|
|
89
|
-
if (!content.startsWith("---")) {
|
|
90
|
-
errors.push({
|
|
91
|
-
path,
|
|
92
|
-
message: "Missing YAML frontmatter",
|
|
93
|
-
severity: "error",
|
|
94
|
-
});
|
|
95
|
-
return { valid: false, errors, warnings };
|
|
96
|
-
}
|
|
97
|
-
// Parse frontmatter
|
|
98
|
-
const parsed = parseFrontmatter(content);
|
|
99
|
-
if (!parsed) {
|
|
100
|
-
errors.push({
|
|
101
|
-
path,
|
|
102
|
-
message: "Invalid YAML frontmatter",
|
|
103
|
-
severity: "error",
|
|
104
|
-
});
|
|
105
|
-
return { valid: false, errors, warnings };
|
|
106
|
-
}
|
|
107
|
-
// Check for required type field
|
|
108
|
-
if (!parsed.frontmatter.type) {
|
|
109
|
-
errors.push({
|
|
110
|
-
path,
|
|
111
|
-
message: "Missing required 'type' field in frontmatter",
|
|
112
|
-
severity: "error",
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
// Check for broken links - extractLinks would be used for validation
|
|
116
|
-
// Note: link validation would be done here with filesystem access
|
|
117
|
-
return {
|
|
118
|
-
valid: errors.length === 0,
|
|
119
|
-
errors,
|
|
120
|
-
warnings,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
// --- Index Generation ---------------------------------------------------------
|
|
124
|
-
function generateIndex(concepts) {
|
|
125
|
-
const lines = [
|
|
126
|
-
"# Knowledge Index",
|
|
127
|
-
"",
|
|
128
|
-
`Last updated: ${new Date().toISOString()}`,
|
|
129
|
-
"",
|
|
130
|
-
"## By Type",
|
|
131
|
-
"",
|
|
132
|
-
];
|
|
133
|
-
// Group by type
|
|
134
|
-
const byType = new Map();
|
|
135
|
-
for (const concept of concepts) {
|
|
136
|
-
const existing = byType.get(concept.type) || [];
|
|
137
|
-
existing.push(concept);
|
|
138
|
-
byType.set(concept.type, existing);
|
|
139
|
-
}
|
|
140
|
-
for (const [type, typeConcepts] of byType) {
|
|
141
|
-
lines.push(`### ${type}`);
|
|
142
|
-
for (const concept of typeConcepts) {
|
|
143
|
-
const title = concept.title || concept.id;
|
|
144
|
-
lines.push(`- [[${title}]]`);
|
|
145
|
-
}
|
|
146
|
-
lines.push("");
|
|
147
|
-
}
|
|
148
|
-
// Group by tag
|
|
149
|
-
lines.push("## By Tag");
|
|
150
|
-
lines.push("");
|
|
151
|
-
const byTag = new Map();
|
|
152
|
-
for (const concept of concepts) {
|
|
153
|
-
for (const tag of concept.tags) {
|
|
154
|
-
const existing = byTag.get(tag) || [];
|
|
155
|
-
existing.push(concept);
|
|
156
|
-
byTag.set(tag, existing);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
for (const [tag, tagConcepts] of byTag) {
|
|
160
|
-
lines.push(`### #${tag}`);
|
|
161
|
-
for (const concept of tagConcepts) {
|
|
162
|
-
const title = concept.title || concept.id;
|
|
163
|
-
lines.push(`- [[${title}]]`);
|
|
164
|
-
}
|
|
165
|
-
lines.push("");
|
|
166
|
-
}
|
|
167
|
-
return lines.join("\n");
|
|
168
|
-
}
|
|
169
|
-
// --- Search ------------------------------------------------------------------
|
|
170
|
-
function calculateRelevance(concept, query) {
|
|
171
|
-
let score = 0;
|
|
172
|
-
const matchedOn = [];
|
|
173
|
-
// Title match
|
|
174
|
-
if (query.text &&
|
|
175
|
-
concept.title?.toLowerCase().includes(query.text.toLowerCase())) {
|
|
176
|
-
score += 30;
|
|
177
|
-
matchedOn.push("title");
|
|
178
|
-
}
|
|
179
|
-
// Tag match
|
|
180
|
-
if (query.tags) {
|
|
181
|
-
for (const tag of query.tags) {
|
|
182
|
-
if (concept.tags.includes(tag)) {
|
|
183
|
-
score += 20;
|
|
184
|
-
matchedOn.push("tags");
|
|
185
|
-
break;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
// Type match
|
|
190
|
-
if (query.types && query.types.includes(concept.type)) {
|
|
191
|
-
score += 15;
|
|
192
|
-
matchedOn.push("type");
|
|
193
|
-
}
|
|
194
|
-
// Authority match
|
|
195
|
-
const authority = concept.metadata?.authority || "unverified";
|
|
196
|
-
if (query.authority && query.authority.includes(authority)) {
|
|
197
|
-
score += 10 * AUTHORITY_PRIORITY[authority];
|
|
198
|
-
matchedOn.push("authority");
|
|
199
|
-
}
|
|
200
|
-
// Body match
|
|
201
|
-
if (query.text &&
|
|
202
|
-
concept.body.toLowerCase().includes(query.text.toLowerCase())) {
|
|
203
|
-
score += 5;
|
|
204
|
-
}
|
|
205
|
-
return score;
|
|
206
|
-
}
|
|
207
|
-
// --- Main Engine -------------------------------------------------------------
|
|
208
|
-
export class MemoryEngine {
|
|
209
|
-
bundle = null;
|
|
210
|
-
ensureBundle() {
|
|
211
|
-
if (!this.bundle) {
|
|
212
|
-
this.bundle = {
|
|
213
|
-
path: "memory://in-memory",
|
|
214
|
-
concepts: [],
|
|
215
|
-
index: "",
|
|
216
|
-
log: "",
|
|
217
|
-
directories: [],
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
return this.bundle;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Load an OKF bundle from a directory path.
|
|
224
|
-
* Recursively reads all .md files, parses frontmatter + body,
|
|
225
|
-
* and builds the in-memory concept index.
|
|
226
|
-
*/
|
|
227
|
-
async loadBundle(bundlePath) {
|
|
228
|
-
const concepts = [];
|
|
229
|
-
const directories = [];
|
|
230
|
-
// Walk directory recursively for .md files
|
|
231
|
-
async function walkDir(dir) {
|
|
232
|
-
let entries;
|
|
233
|
-
try {
|
|
234
|
-
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
235
|
-
}
|
|
236
|
-
catch {
|
|
237
|
-
return; // Ignore missing directories
|
|
238
|
-
}
|
|
239
|
-
for (const entry of entries) {
|
|
240
|
-
const fullPath = path.join(dir, entry.name);
|
|
241
|
-
if (entry.isDirectory()) {
|
|
242
|
-
if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
243
|
-
directories.push({
|
|
244
|
-
path: path.relative(bundlePath, fullPath),
|
|
245
|
-
name: entry.name,
|
|
246
|
-
files: [],
|
|
247
|
-
subdirectories: [],
|
|
248
|
-
});
|
|
249
|
-
await walkDir(fullPath);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
else if (entry.name.endsWith(".md") &&
|
|
253
|
-
!RESERVED_FILES.includes(entry.name)) {
|
|
254
|
-
const content = await fs.readFile(fullPath, "utf-8").catch(() => "");
|
|
255
|
-
const parsed = parseFrontmatter(content);
|
|
256
|
-
if (parsed && parsed.frontmatter.type) {
|
|
257
|
-
const id = path.relative(bundlePath, fullPath).replace(/\.md$/, "");
|
|
258
|
-
concepts.push({
|
|
259
|
-
id,
|
|
260
|
-
type: parsed.frontmatter.type,
|
|
261
|
-
title: parsed.frontmatter.title,
|
|
262
|
-
description: parsed.frontmatter.description,
|
|
263
|
-
resource: parsed.frontmatter.resource,
|
|
264
|
-
tags: parsed.frontmatter.tags || [],
|
|
265
|
-
timestamp: parsed.frontmatter.timestamp,
|
|
266
|
-
metadata: { ...parsed.frontmatter },
|
|
267
|
-
body: parsed.body,
|
|
268
|
-
links: extractLinks(parsed.body),
|
|
269
|
-
});
|
|
270
|
-
// Prune OKF-reserved fields from metadata (they are top-level on OkfConcept)
|
|
271
|
-
delete concepts[concepts.length - 1].metadata["type"];
|
|
272
|
-
delete concepts[concepts.length - 1].metadata["title"];
|
|
273
|
-
delete concepts[concepts.length - 1].metadata["tags"];
|
|
274
|
-
delete concepts[concepts.length - 1].metadata["timestamp"];
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
await walkDir(bundlePath);
|
|
280
|
-
const bundle = {
|
|
281
|
-
path: bundlePath,
|
|
282
|
-
concepts,
|
|
283
|
-
index: "",
|
|
284
|
-
log: "",
|
|
285
|
-
directories,
|
|
286
|
-
};
|
|
287
|
-
// Load reserved files: index.md and log.md
|
|
288
|
-
const [indexPath, logPath] = ["index.md", "log.md"].map((f) => path.join(bundlePath, f));
|
|
289
|
-
try {
|
|
290
|
-
bundle.index = await fs.readFile(indexPath, "utf-8");
|
|
291
|
-
}
|
|
292
|
-
catch {
|
|
293
|
-
// No index yet — generate fresh
|
|
294
|
-
bundle.index = generateIndex(concepts);
|
|
295
|
-
}
|
|
296
|
-
try {
|
|
297
|
-
bundle.log = await fs.readFile(logPath, "utf-8");
|
|
298
|
-
}
|
|
299
|
-
catch {
|
|
300
|
-
// No log yet — that's fine
|
|
301
|
-
}
|
|
302
|
-
this.bundle = bundle;
|
|
303
|
-
return bundle;
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Validate a bundle
|
|
307
|
-
*/
|
|
308
|
-
validateBundle(bundle) {
|
|
309
|
-
const errors = [];
|
|
310
|
-
const warnings = [];
|
|
311
|
-
for (const concept of bundle.concepts) {
|
|
312
|
-
if (!concept.type) {
|
|
313
|
-
errors.push({
|
|
314
|
-
path: concept.id,
|
|
315
|
-
message: "Concept missing required 'type' field",
|
|
316
|
-
severity: "error",
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return {
|
|
321
|
-
valid: errors.length === 0,
|
|
322
|
-
errors,
|
|
323
|
-
warnings,
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Search for concepts matching a query
|
|
328
|
-
*/
|
|
329
|
-
search(query) {
|
|
330
|
-
const bundle = this.ensureBundle();
|
|
331
|
-
const results = [];
|
|
332
|
-
for (const concept of bundle.concepts) {
|
|
333
|
-
// Filter by authority
|
|
334
|
-
if (query.authority) {
|
|
335
|
-
const authority = concept.metadata?.authority || "unverified";
|
|
336
|
-
if (!query.authority.includes(authority)) {
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
// Filter by type
|
|
341
|
-
if (query.types && !query.types.includes(concept.type)) {
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
// Filter by tags
|
|
345
|
-
if (query.tags) {
|
|
346
|
-
const hasTag = query.tags.some((t) => concept.tags.includes(t));
|
|
347
|
-
if (!hasTag) {
|
|
348
|
-
continue;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
const relevance = calculateRelevance(concept, query);
|
|
352
|
-
if (relevance > 0 || !query.text) {
|
|
353
|
-
results.push({
|
|
354
|
-
concept,
|
|
355
|
-
relevance,
|
|
356
|
-
matchedOn: [],
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
// Sort by relevance
|
|
361
|
-
results.sort((a, b) => b.relevance - a.relevance);
|
|
362
|
-
// Apply limit
|
|
363
|
-
if (query.limit) {
|
|
364
|
-
return results.slice(0, query.limit);
|
|
365
|
-
}
|
|
366
|
-
return results;
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Write a new concept (RFC-0060)
|
|
370
|
-
*
|
|
371
|
-
* Persists to disk at {bundle.path}/{id}.md and updates bundle index.
|
|
372
|
-
* File name is derived from the title slug (first 64 chars).
|
|
373
|
-
*/
|
|
374
|
-
async writeConcept(request) {
|
|
375
|
-
const id = request.title
|
|
376
|
-
? request.title
|
|
377
|
-
.toLowerCase()
|
|
378
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
379
|
-
.replace(/^-|-$/g, "")
|
|
380
|
-
.slice(0, 64) +
|
|
381
|
-
"-" +
|
|
382
|
-
randomUUID().slice(0, 8)
|
|
383
|
-
: randomUUID();
|
|
384
|
-
const timestamp = new Date().toISOString();
|
|
385
|
-
const concept = {
|
|
386
|
-
id,
|
|
387
|
-
type: request.type,
|
|
388
|
-
title: request.title,
|
|
389
|
-
tags: request.tags || [],
|
|
390
|
-
timestamp,
|
|
391
|
-
metadata: {
|
|
392
|
-
...request.metadata,
|
|
393
|
-
authority: request.authority || "generated",
|
|
394
|
-
},
|
|
395
|
-
body: request.body,
|
|
396
|
-
links: request.links || [],
|
|
397
|
-
};
|
|
398
|
-
const bundle = this.ensureBundle();
|
|
399
|
-
// Persist to disk
|
|
400
|
-
if (bundle.path && bundle.path !== "memory://in-memory") {
|
|
401
|
-
const filePath = path.join(bundle.path, `${id}.md`);
|
|
402
|
-
const okfContent = this.exportToOkf(concept);
|
|
403
|
-
await fs.mkdir(bundle.path, { recursive: true });
|
|
404
|
-
await fs.writeFile(filePath, okfContent, "utf-8");
|
|
405
|
-
}
|
|
406
|
-
bundle.concepts.push(concept);
|
|
407
|
-
bundle.index = generateIndex(bundle.concepts);
|
|
408
|
-
return concept;
|
|
409
|
-
}
|
|
410
|
-
/**
|
|
411
|
-
* Promote a concept from the blackboard
|
|
412
|
-
*/
|
|
413
|
-
promoteFromBlackboard(blackboardContent, metadata) {
|
|
414
|
-
// Filter secrets before promoting
|
|
415
|
-
const filteredContent = filterSecrets(blackboardContent);
|
|
416
|
-
return {
|
|
417
|
-
type: metadata.type || "knowledge",
|
|
418
|
-
title: metadata.title,
|
|
419
|
-
body: filteredContent,
|
|
420
|
-
tags: metadata.tags || [],
|
|
421
|
-
authority: metadata.authority || "unverified",
|
|
422
|
-
metadata,
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Rebuild the index from disk and persist index.md to bundle directory.
|
|
427
|
-
*/
|
|
428
|
-
async rebuildIndex(bundlePath) {
|
|
429
|
-
const targetPath = bundlePath ?? this.bundle?.path;
|
|
430
|
-
if (!targetPath || targetPath === "memory://in-memory") {
|
|
431
|
-
if (this.bundle) {
|
|
432
|
-
this.bundle.index = generateIndex(this.bundle.concepts);
|
|
433
|
-
}
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
if (!this.bundle || this.bundle.path !== targetPath) {
|
|
437
|
-
await this.loadBundle(targetPath);
|
|
438
|
-
}
|
|
439
|
-
if (this.bundle) {
|
|
440
|
-
this.bundle.index = generateIndex(this.bundle.concepts);
|
|
441
|
-
const indexPath = path.join(targetPath, "index.md");
|
|
442
|
-
await fs.writeFile(indexPath, this.bundle.index, "utf-8");
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* Get the current bundle
|
|
447
|
-
*/
|
|
448
|
-
getBundle() {
|
|
449
|
-
return this.bundle;
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Export a concept to OKF format (RFC-0060)
|
|
453
|
-
*
|
|
454
|
-
* Excludes bundle-level metadata fields (index, log, directories)
|
|
455
|
-
* from the concept frontmatter to ensure clean round-trip parsing.
|
|
456
|
-
*/
|
|
457
|
-
exportToOkf(concept) {
|
|
458
|
-
// Collect concept-level metadata only, excluding top-level OKF fields
|
|
459
|
-
// and any bundle-level reserved fields that might be present.
|
|
460
|
-
const reserved = new Set([
|
|
461
|
-
"id",
|
|
462
|
-
"type",
|
|
463
|
-
"title",
|
|
464
|
-
"description",
|
|
465
|
-
"resource",
|
|
466
|
-
"tags",
|
|
467
|
-
"timestamp",
|
|
468
|
-
"body",
|
|
469
|
-
"links",
|
|
470
|
-
"index",
|
|
471
|
-
"log",
|
|
472
|
-
"directories",
|
|
473
|
-
]);
|
|
474
|
-
const conceptMetadata = {};
|
|
475
|
-
const meta = concept.metadata ?? {};
|
|
476
|
-
for (const [k, v] of Object.entries(meta)) {
|
|
477
|
-
if (!reserved.has(k)) {
|
|
478
|
-
conceptMetadata[k] = v;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
const lines = [
|
|
482
|
-
"---",
|
|
483
|
-
serializeFrontmatter({
|
|
484
|
-
type: concept.type,
|
|
485
|
-
title: concept.title,
|
|
486
|
-
tags: concept.tags,
|
|
487
|
-
timestamp: concept.timestamp,
|
|
488
|
-
...conceptMetadata,
|
|
489
|
-
}),
|
|
490
|
-
"---",
|
|
491
|
-
"",
|
|
492
|
-
concept.body,
|
|
493
|
-
];
|
|
494
|
-
if (concept.links.length > 0) {
|
|
495
|
-
lines.push("");
|
|
496
|
-
lines.push("## Links");
|
|
497
|
-
for (const link of concept.links) {
|
|
498
|
-
lines.push(`- [${link.title || link.href}](${link.href})`);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
return lines.join("\n");
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
// --- Factory -----------------------------------------------------------------
|
|
505
|
-
export function createMemoryEngine() {
|
|
506
|
-
return new MemoryEngine();
|
|
507
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory Engine Types (RFC-0060)
|
|
3
|
-
*
|
|
4
|
-
* Interfaces for OKF-based knowledge management.
|
|
5
|
-
*/
|
|
6
|
-
// --- Reserved Files -----------------------------------------------------------
|
|
7
|
-
export const RESERVED_FILES = ["index.md", "log.md"];
|
|
8
|
-
export function isReservedFile(filename) {
|
|
9
|
-
return RESERVED_FILES.includes(filename);
|
|
10
|
-
}
|
|
11
|
-
// --- Secret Detection ---------------------------------------------------------
|
|
12
|
-
export const SECRET_PATTERNS = [
|
|
13
|
-
/api[_-]?key/i,
|
|
14
|
-
/password/i,
|
|
15
|
-
/secret/i,
|
|
16
|
-
/token/i,
|
|
17
|
-
/bearer/i,
|
|
18
|
-
/auth/i,
|
|
19
|
-
/credential/i,
|
|
20
|
-
/private[_-]?key/i,
|
|
21
|
-
/access[_-]?token/i,
|
|
22
|
-
/refresh[_-]?token/i,
|
|
23
|
-
];
|
|
24
|
-
export function containsSecret(value) {
|
|
25
|
-
return SECRET_PATTERNS.some((pattern) => pattern.test(value));
|
|
26
|
-
}
|
|
27
|
-
export function filterSecrets(content) {
|
|
28
|
-
return content.replace(/([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([^\n]+)/g, (match, key) => {
|
|
29
|
-
if (containsSecret(key)) {
|
|
30
|
-
return `${key}=[REDACTED]`;
|
|
31
|
-
}
|
|
32
|
-
return match;
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
export const AUTHORITY_PRIORITY = {
|
|
36
|
-
approved: 3,
|
|
37
|
-
generated: 2,
|
|
38
|
-
unverified: 1,
|
|
39
|
-
};
|