@a-company/paradigm 3.17.2 → 3.19.0
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/chunk-3DYYXGDC.js +403 -0
- package/dist/{chunk-ZRPEI35Q.js → chunk-BKMNLROM.js} +533 -5
- package/dist/{chunk-IWDLTLZH.js → chunk-BRILIG7Z.js} +155 -32
- package/dist/{chunk-D4VBBKGV.js → chunk-EZ6XW6FB.js} +150 -409
- package/dist/{delete-KBKPTIMU.js → delete-YTASL4SM.js} +3 -3
- package/dist/{dist-YHDSIZQD.js → dist-IKBGY7FQ.js} +3 -1
- package/dist/{edit-Y6SKWVHI.js → edit-S7NZD7H7.js} +1 -1
- package/dist/index.js +18 -10
- package/dist/{list-SLAH7X7N.js → list-CAL7KS7B.js} +3 -3
- package/dist/lore-loader-S5BXMH27.js +21 -0
- package/dist/{lore-server-WFU4H5DI.js → lore-server-2NYDLGCJ.js} +102 -17
- package/dist/mcp.js +503 -147
- package/dist/migrate-assessments-FPR6C35Z.js +97 -0
- package/dist/{record-PLZ2OQAN.js → record-UGN75GTB.js} +10 -7
- package/dist/{reindex-T4N3NG73.js → reindex-CMZARW5K.js} +2 -1
- package/dist/retag-URLJLMSK.js +62 -0
- package/dist/{review-Z4Z37PD5.js → review-725ZKA7U.js} +1 -1
- package/dist/{serve-BU43NO7D.js → serve-GUJ3L3IG.js} +1 -1
- package/dist/{show-WGDIK56Q.js → show-GEVVQWWG.js} +54 -5
- package/dist/{timeline-X3CZM7MW.js → timeline-B6TMGWRU.js} +9 -8
- package/dist/university-content/courses/para-501.json +30 -30
- package/dist/university-content/plsat/v3.0.json +26 -26
- package/lore-ui/dist/assets/{index-CB4RregB.js → index-CDr7Tr1E.js} +10 -10
- package/lore-ui/dist/index.html +1 -1
- package/package.json +1 -1
- package/dist/assessment-loader-C5EOUM47.js +0 -23
- package/dist/chunk-DSXS42FY.js +0 -283
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../paradigm-mcp/src/utils/lore-loader.ts
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import * as yaml from "js-yaml";
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import * as os from "os";
|
|
9
|
+
var LORE_DIR = ".paradigm/lore";
|
|
10
|
+
var ENTRIES_DIR = "entries";
|
|
11
|
+
var TIMELINE_FILE = "timeline.yaml";
|
|
12
|
+
function inferProvider(model) {
|
|
13
|
+
const lower = model.toLowerCase();
|
|
14
|
+
if (lower.includes("claude") || lower.includes("anthropic")) return "anthropic";
|
|
15
|
+
if (lower.includes("gpt") || lower.includes("openai") || lower.includes("o1") || lower.includes("o3")) return "openai";
|
|
16
|
+
if (lower.includes("gemini") || lower.includes("google") || lower.includes("palm")) return "google";
|
|
17
|
+
if (lower.includes("llama") || lower.includes("meta")) return "meta";
|
|
18
|
+
if (lower.includes("mistral") || lower.includes("mixtral")) return "mistral";
|
|
19
|
+
if (lower.includes("deepseek")) return "deepseek";
|
|
20
|
+
if (lower.includes("cohere") || lower.includes("command")) return "cohere";
|
|
21
|
+
return "unknown";
|
|
22
|
+
}
|
|
23
|
+
function normalizeLoreEntry(raw) {
|
|
24
|
+
const entry = raw;
|
|
25
|
+
const author = entry.author;
|
|
26
|
+
if (!entry.type) {
|
|
27
|
+
entry.type = "agent-session";
|
|
28
|
+
}
|
|
29
|
+
if (typeof author === "string") {
|
|
30
|
+
return raw;
|
|
31
|
+
}
|
|
32
|
+
if (author && typeof author === "object" && !Array.isArray(author)) {
|
|
33
|
+
const old = author;
|
|
34
|
+
if (old.type === "agent") {
|
|
35
|
+
entry.author = "unknown";
|
|
36
|
+
entry.agent = {
|
|
37
|
+
provider: old.model ? inferProvider(old.model) : inferProvider(old.id),
|
|
38
|
+
model: old.model || old.id
|
|
39
|
+
};
|
|
40
|
+
} else {
|
|
41
|
+
entry.author = old.id || "unknown";
|
|
42
|
+
}
|
|
43
|
+
delete entry.assistedBy;
|
|
44
|
+
}
|
|
45
|
+
return entry;
|
|
46
|
+
}
|
|
47
|
+
function sanitizeAuthor(name) {
|
|
48
|
+
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 20) || "unknown";
|
|
49
|
+
}
|
|
50
|
+
function resolveAuthor() {
|
|
51
|
+
const envAuthor = process.env.PARADIGM_AUTHOR;
|
|
52
|
+
if (envAuthor) return sanitizeAuthor(envAuthor);
|
|
53
|
+
try {
|
|
54
|
+
const gitName = execSync("git config user.name", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
55
|
+
if (gitName) return sanitizeAuthor(gitName);
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const username = os.userInfo().username;
|
|
60
|
+
if (username) return sanitizeAuthor(username);
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
return "unknown";
|
|
64
|
+
}
|
|
65
|
+
function isLoreFile(filename) {
|
|
66
|
+
return filename.endsWith(".yaml") || filename.endsWith(".lore");
|
|
67
|
+
}
|
|
68
|
+
function resolveEntryPath(rootDir, dateStr, entryId) {
|
|
69
|
+
const dirPath = path.join(rootDir, LORE_DIR, ENTRIES_DIR, dateStr);
|
|
70
|
+
const lorePath = path.join(dirPath, `${entryId}.lore`);
|
|
71
|
+
if (fs.existsSync(lorePath)) return lorePath;
|
|
72
|
+
const yamlPath = path.join(dirPath, `${entryId}.yaml`);
|
|
73
|
+
if (fs.existsSync(yamlPath)) return yamlPath;
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
async function loadLoreEntries(rootDir, filter) {
|
|
77
|
+
const entriesPath = path.join(rootDir, LORE_DIR, ENTRIES_DIR);
|
|
78
|
+
if (!fs.existsSync(entriesPath)) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
migrateLegacyEntries(rootDir);
|
|
82
|
+
const entries = [];
|
|
83
|
+
const dateDirs = fs.readdirSync(entriesPath).filter((d) => /^\d{4}-\d{2}-\d{2}$/.test(d)).sort().reverse();
|
|
84
|
+
for (const dateDir of dateDirs) {
|
|
85
|
+
if (filter?.dateFrom && dateDir < filter.dateFrom.slice(0, 10)) continue;
|
|
86
|
+
if (filter?.dateTo && dateDir > filter.dateTo.slice(0, 10)) continue;
|
|
87
|
+
const dirPath = path.join(entriesPath, dateDir);
|
|
88
|
+
const files = fs.readdirSync(dirPath).filter(isLoreFile).sort();
|
|
89
|
+
for (const file of files) {
|
|
90
|
+
try {
|
|
91
|
+
const content = fs.readFileSync(path.join(dirPath, file), "utf8");
|
|
92
|
+
const raw = yaml.load(content);
|
|
93
|
+
entries.push(normalizeLoreEntry(raw));
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return filter ? applyFilter(entries, filter) : entries;
|
|
99
|
+
}
|
|
100
|
+
async function loadLoreEntry(rootDir, entryId) {
|
|
101
|
+
const dateMatch = entryId.match(/^L-(\d{4}-\d{2}-\d{2})-/);
|
|
102
|
+
if (dateMatch) {
|
|
103
|
+
const dateStr = dateMatch[1];
|
|
104
|
+
const entryPath = resolveEntryPath(rootDir, dateStr, entryId);
|
|
105
|
+
if (entryPath) {
|
|
106
|
+
try {
|
|
107
|
+
const content = fs.readFileSync(entryPath, "utf8");
|
|
108
|
+
const raw = yaml.load(content);
|
|
109
|
+
return normalizeLoreEntry(raw);
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const entries = await loadLoreEntries(rootDir);
|
|
116
|
+
return entries.find((e) => e.id === entryId) || null;
|
|
117
|
+
}
|
|
118
|
+
async function loadLoreTimeline(rootDir) {
|
|
119
|
+
const timelinePath = path.join(rootDir, LORE_DIR, TIMELINE_FILE);
|
|
120
|
+
if (!fs.existsSync(timelinePath)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const content = fs.readFileSync(timelinePath, "utf8");
|
|
125
|
+
return yaml.load(content);
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function captureGitContext(cwd) {
|
|
131
|
+
try {
|
|
132
|
+
const ref = execSync("git rev-parse HEAD", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
133
|
+
const branch = execSync("git rev-parse --abbrev-ref HEAD", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
134
|
+
const status = execSync("git status --porcelain", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
135
|
+
return { ref, branch, dirty: status.length > 0 };
|
|
136
|
+
} catch {
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function recordLoreEntry(rootDir, entry) {
|
|
141
|
+
const lorePath = path.join(rootDir, LORE_DIR);
|
|
142
|
+
const dateStr = entry.timestamp.slice(0, 10);
|
|
143
|
+
const datePath = path.join(lorePath, ENTRIES_DIR, dateStr);
|
|
144
|
+
if (!fs.existsSync(datePath)) {
|
|
145
|
+
fs.mkdirSync(datePath, { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
if (!entry.author) {
|
|
148
|
+
entry.author = resolveAuthor();
|
|
149
|
+
}
|
|
150
|
+
if (!entry.git_context) {
|
|
151
|
+
entry.git_context = captureGitContext(rootDir);
|
|
152
|
+
}
|
|
153
|
+
if (!entry.id) {
|
|
154
|
+
entry.id = generateLoreId(rootDir, dateStr, entry.author, entry.timestamp);
|
|
155
|
+
}
|
|
156
|
+
const entryPath = path.join(datePath, `${entry.id}.lore`);
|
|
157
|
+
fs.writeFileSync(entryPath, yaml.dump(entry, { lineWidth: -1, noRefs: true }));
|
|
158
|
+
await rebuildTimeline(rootDir);
|
|
159
|
+
return entry.id;
|
|
160
|
+
}
|
|
161
|
+
async function addLoreReview(rootDir, entryId, review) {
|
|
162
|
+
const entry = await loadLoreEntry(rootDir, entryId);
|
|
163
|
+
if (!entry) return false;
|
|
164
|
+
const dateStr = entry.timestamp.slice(0, 10);
|
|
165
|
+
const entryPath = resolveEntryPath(rootDir, dateStr, entryId);
|
|
166
|
+
if (!entryPath) return false;
|
|
167
|
+
entry.review = review;
|
|
168
|
+
fs.writeFileSync(entryPath, yaml.dump(entry, { lineWidth: -1, noRefs: true }));
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
async function rebuildTimeline(rootDir) {
|
|
172
|
+
const lorePath = path.join(rootDir, LORE_DIR);
|
|
173
|
+
const entriesPath = path.join(lorePath, ENTRIES_DIR);
|
|
174
|
+
if (!fs.existsSync(entriesPath)) return;
|
|
175
|
+
migrateLegacyEntries(rootDir);
|
|
176
|
+
const authors = /* @__PURE__ */ new Set();
|
|
177
|
+
let entryCount = 0;
|
|
178
|
+
let lastUpdated = "";
|
|
179
|
+
const dateDirs = fs.readdirSync(entriesPath).filter((d) => /^\d{4}-\d{2}-\d{2}$/.test(d));
|
|
180
|
+
for (const dateDir of dateDirs) {
|
|
181
|
+
const dirPath = path.join(entriesPath, dateDir);
|
|
182
|
+
const files = fs.readdirSync(dirPath).filter(isLoreFile);
|
|
183
|
+
for (const file of files) {
|
|
184
|
+
try {
|
|
185
|
+
const content = fs.readFileSync(path.join(dirPath, file), "utf8");
|
|
186
|
+
const raw = yaml.load(content);
|
|
187
|
+
const entry = normalizeLoreEntry(raw);
|
|
188
|
+
authors.add(entry.author);
|
|
189
|
+
entryCount++;
|
|
190
|
+
if (!lastUpdated || entry.timestamp > lastUpdated) {
|
|
191
|
+
lastUpdated = entry.timestamp;
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
let project = "unknown";
|
|
198
|
+
const configPath = path.join(rootDir, ".paradigm", "config.yaml");
|
|
199
|
+
if (fs.existsSync(configPath)) {
|
|
200
|
+
try {
|
|
201
|
+
const config = yaml.load(fs.readFileSync(configPath, "utf8"));
|
|
202
|
+
project = config.project || config.name || "unknown";
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const timeline = {
|
|
207
|
+
version: "1.0",
|
|
208
|
+
project,
|
|
209
|
+
entries: entryCount,
|
|
210
|
+
last_updated: lastUpdated || (/* @__PURE__ */ new Date()).toISOString(),
|
|
211
|
+
authors: Array.from(authors)
|
|
212
|
+
};
|
|
213
|
+
if (!fs.existsSync(lorePath)) {
|
|
214
|
+
fs.mkdirSync(lorePath, { recursive: true });
|
|
215
|
+
}
|
|
216
|
+
fs.writeFileSync(
|
|
217
|
+
path.join(lorePath, TIMELINE_FILE),
|
|
218
|
+
yaml.dump(timeline, { lineWidth: -1, noRefs: true })
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
function migrateLegacyEntries(rootDir) {
|
|
222
|
+
const entriesPath = path.join(rootDir, LORE_DIR, ENTRIES_DIR);
|
|
223
|
+
if (!fs.existsSync(entriesPath)) return 0;
|
|
224
|
+
const rootFiles = fs.readdirSync(entriesPath).filter((f) => isLoreFile(f) && !f.startsWith("."));
|
|
225
|
+
let migrated = 0;
|
|
226
|
+
for (const file of rootFiles) {
|
|
227
|
+
const filePath = path.join(entriesPath, file);
|
|
228
|
+
const stat = fs.statSync(filePath);
|
|
229
|
+
if (!stat.isFile()) continue;
|
|
230
|
+
try {
|
|
231
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
232
|
+
const raw = yaml.load(content);
|
|
233
|
+
if (raw.author && typeof raw.author === "object") continue;
|
|
234
|
+
if (typeof raw.author === "string" && raw.timestamp) continue;
|
|
235
|
+
const dateStr = typeof raw.date === "string" ? raw.date.slice(0, 10) : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
236
|
+
const datePath = path.join(entriesPath, dateStr);
|
|
237
|
+
if (!fs.existsSync(datePath)) {
|
|
238
|
+
fs.mkdirSync(datePath, { recursive: true });
|
|
239
|
+
}
|
|
240
|
+
const author = resolveAuthor();
|
|
241
|
+
const timestamp = `${dateStr}T00:00:00.000Z`;
|
|
242
|
+
const id = generateLoreId(rootDir, dateStr, author, timestamp);
|
|
243
|
+
const oldType = String(raw.type || "agent-session");
|
|
244
|
+
const v2Type = ["agent-session", "human-note", "decision", "review", "incident", "milestone"].includes(oldType) ? oldType : "agent-session";
|
|
245
|
+
let verification;
|
|
246
|
+
if (raw.test_results && typeof raw.test_results === "object") {
|
|
247
|
+
const tr = raw.test_results;
|
|
248
|
+
verification = {
|
|
249
|
+
status: tr.total === tr.passed ? "pass" : "partial",
|
|
250
|
+
details: { tests: tr.total === tr.passed ? "pass" : "fail" }
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const v2Entry = {
|
|
254
|
+
id,
|
|
255
|
+
type: v2Type,
|
|
256
|
+
timestamp,
|
|
257
|
+
author: "unknown",
|
|
258
|
+
agent: { provider: "unknown", model: "unknown" },
|
|
259
|
+
title: String(raw.title || file.replace(/\.(yaml|lore)$/, "")),
|
|
260
|
+
summary: String(raw.summary || ""),
|
|
261
|
+
symbols_touched: Array.isArray(raw.symbols_touched) ? raw.symbols_touched : [],
|
|
262
|
+
files_modified: Array.isArray(raw.files_modified) ? raw.files_modified : void 0,
|
|
263
|
+
...verification ? { verification } : {},
|
|
264
|
+
tags: ["migrated", oldType]
|
|
265
|
+
};
|
|
266
|
+
fs.writeFileSync(
|
|
267
|
+
path.join(datePath, `${id}.lore`),
|
|
268
|
+
yaml.dump(v2Entry, { lineWidth: -1, noRefs: true })
|
|
269
|
+
);
|
|
270
|
+
fs.unlinkSync(filePath);
|
|
271
|
+
migrated++;
|
|
272
|
+
} catch {
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return migrated;
|
|
276
|
+
}
|
|
277
|
+
async function updateLoreEntry(rootDir, entryId, partial) {
|
|
278
|
+
const entry = await loadLoreEntry(rootDir, entryId);
|
|
279
|
+
if (!entry) return false;
|
|
280
|
+
const dateStr = entry.timestamp.slice(0, 10);
|
|
281
|
+
const entryPath = resolveEntryPath(rootDir, dateStr, entryId);
|
|
282
|
+
if (!entryPath) return false;
|
|
283
|
+
if (partial.title !== void 0) entry.title = partial.title;
|
|
284
|
+
if (partial.summary !== void 0) entry.summary = partial.summary;
|
|
285
|
+
if (partial.type !== void 0) entry.type = partial.type;
|
|
286
|
+
if (partial.duration_minutes !== void 0) entry.duration_minutes = partial.duration_minutes;
|
|
287
|
+
if (partial.symbols_touched !== void 0) entry.symbols_touched = partial.symbols_touched;
|
|
288
|
+
if (partial.symbols_created !== void 0) entry.symbols_created = partial.symbols_created;
|
|
289
|
+
if (partial.files_created !== void 0) entry.files_created = partial.files_created;
|
|
290
|
+
if (partial.files_modified !== void 0) entry.files_modified = partial.files_modified;
|
|
291
|
+
if (partial.lines_added !== void 0) entry.lines_added = partial.lines_added;
|
|
292
|
+
if (partial.lines_removed !== void 0) entry.lines_removed = partial.lines_removed;
|
|
293
|
+
if (partial.commit !== void 0) entry.commit = partial.commit;
|
|
294
|
+
if (partial.decisions !== void 0) entry.decisions = partial.decisions;
|
|
295
|
+
if (partial.errors_encountered !== void 0) entry.errors_encountered = partial.errors_encountered;
|
|
296
|
+
if (partial.learnings !== void 0) entry.learnings = partial.learnings;
|
|
297
|
+
if (partial.verification !== void 0) entry.verification = partial.verification;
|
|
298
|
+
if (partial.tags !== void 0) entry.tags = partial.tags;
|
|
299
|
+
if (partial.body !== void 0) entry.body = partial.body;
|
|
300
|
+
if (partial.linked_lore !== void 0) entry.linked_lore = partial.linked_lore;
|
|
301
|
+
if (partial.linked_tasks !== void 0) entry.linked_tasks = partial.linked_tasks;
|
|
302
|
+
if (partial.linked_commits !== void 0) entry.linked_commits = partial.linked_commits;
|
|
303
|
+
fs.writeFileSync(entryPath, yaml.dump(entry, { lineWidth: -1, noRefs: true }));
|
|
304
|
+
await rebuildTimeline(rootDir);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
async function deleteLoreEntry(rootDir, entryId) {
|
|
308
|
+
const entry = await loadLoreEntry(rootDir, entryId);
|
|
309
|
+
if (!entry) return false;
|
|
310
|
+
const dateStr = entry.timestamp.slice(0, 10);
|
|
311
|
+
const entryPath = resolveEntryPath(rootDir, dateStr, entryId);
|
|
312
|
+
if (!entryPath) return false;
|
|
313
|
+
fs.unlinkSync(entryPath);
|
|
314
|
+
const dateDir = path.dirname(entryPath);
|
|
315
|
+
const remaining = fs.readdirSync(dateDir).filter(isLoreFile);
|
|
316
|
+
if (remaining.length === 0) {
|
|
317
|
+
fs.rmdirSync(dateDir);
|
|
318
|
+
}
|
|
319
|
+
await rebuildTimeline(rootDir);
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
function applyFilter(entries, filter) {
|
|
323
|
+
let result = entries;
|
|
324
|
+
if (filter.author) {
|
|
325
|
+
result = result.filter((e) => e.author === filter.author);
|
|
326
|
+
}
|
|
327
|
+
if (filter.hasAgent !== void 0) {
|
|
328
|
+
result = result.filter(
|
|
329
|
+
(e) => filter.hasAgent ? e.agent != null : e.agent == null
|
|
330
|
+
);
|
|
331
|
+
} else if (filter.authorType) {
|
|
332
|
+
result = result.filter(
|
|
333
|
+
(e) => filter.authorType === "agent" ? e.agent != null : e.agent == null
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
if (filter.symbol) {
|
|
337
|
+
result = result.filter(
|
|
338
|
+
(e) => e.symbols_touched.includes(filter.symbol) || e.symbols_created?.includes(filter.symbol)
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
if (filter.dateFrom) {
|
|
342
|
+
const from = new Date(filter.dateFrom).getTime();
|
|
343
|
+
result = result.filter((e) => new Date(e.timestamp).getTime() >= from);
|
|
344
|
+
}
|
|
345
|
+
if (filter.dateTo) {
|
|
346
|
+
const to = new Date(filter.dateTo).getTime();
|
|
347
|
+
result = result.filter((e) => new Date(e.timestamp).getTime() <= to);
|
|
348
|
+
}
|
|
349
|
+
if (filter.type) {
|
|
350
|
+
result = result.filter((e) => e.type === filter.type);
|
|
351
|
+
}
|
|
352
|
+
if (filter.tag) {
|
|
353
|
+
const prefix = filter.tag;
|
|
354
|
+
result = result.filter(
|
|
355
|
+
(e) => e.tags?.some((t) => t === prefix || t.startsWith(prefix + ":") || prefix.includes(":") && t === prefix)
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
359
|
+
result = result.filter((e) => filter.tags.some((tag) => e.tags?.includes(tag)));
|
|
360
|
+
}
|
|
361
|
+
if (filter.hasBody !== void 0) {
|
|
362
|
+
result = result.filter(
|
|
363
|
+
(e) => filter.hasBody ? e.body != null && e.body.length > 0 : !e.body || e.body.length === 0
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
if (filter.hasReview !== void 0) {
|
|
367
|
+
result = result.filter((e) => filter.hasReview ? e.review != null : e.review == null);
|
|
368
|
+
}
|
|
369
|
+
result.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
370
|
+
if (filter.offset) result = result.slice(filter.offset);
|
|
371
|
+
if (filter.limit) result = result.slice(0, filter.limit);
|
|
372
|
+
return result;
|
|
373
|
+
}
|
|
374
|
+
function generateLoreId(rootDir, dateStr, author, timestamp) {
|
|
375
|
+
const sanitized = sanitizeAuthor(author);
|
|
376
|
+
const ts = new Date(timestamp);
|
|
377
|
+
const hh = String(ts.getUTCHours()).padStart(2, "0");
|
|
378
|
+
const mm = String(ts.getUTCMinutes()).padStart(2, "0");
|
|
379
|
+
const ss = String(ts.getUTCSeconds()).padStart(2, "0");
|
|
380
|
+
const timeStr = `${hh}${mm}${ss}`;
|
|
381
|
+
const prefix = `L-${dateStr}-${sanitized}-${timeStr}`;
|
|
382
|
+
const datePath = path.join(rootDir, LORE_DIR, ENTRIES_DIR, dateStr);
|
|
383
|
+
if (!fs.existsSync(datePath)) {
|
|
384
|
+
return `${prefix}-001`;
|
|
385
|
+
}
|
|
386
|
+
const existing = fs.readdirSync(datePath).filter((f) => f.startsWith(prefix) && isLoreFile(f)).map((f) => {
|
|
387
|
+
const match = f.match(/-(\d{3})\.(yaml|lore)$/);
|
|
388
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
389
|
+
});
|
|
390
|
+
const next = existing.length > 0 ? Math.max(...existing) + 1 : 1;
|
|
391
|
+
return `${prefix}-${String(next).padStart(3, "0")}`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export {
|
|
395
|
+
loadLoreEntries,
|
|
396
|
+
loadLoreEntry,
|
|
397
|
+
loadLoreTimeline,
|
|
398
|
+
recordLoreEntry,
|
|
399
|
+
addLoreReview,
|
|
400
|
+
rebuildTimeline,
|
|
401
|
+
updateLoreEntry,
|
|
402
|
+
deleteLoreEntry
|
|
403
|
+
};
|