openwolf-enhanced 1.21.0 → 1.23.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/CHANGELOG.md +173 -0
- package/README.md +117 -32
- package/dist/dashboard/assets/{AISuggestions-C9y4DXN7.js → AISuggestions-CxUalxra.js} +1 -1
- package/dist/dashboard/assets/{ActivityLog-BwI3hu53.js → ActivityLog-DYmmCNbe.js} +1 -1
- package/dist/dashboard/assets/{ActivityTimeline-CxfgPqU7.js → ActivityTimeline-ROR1OtgM.js} +1 -1
- package/dist/dashboard/assets/{AnatomyBrowser-6xUbPIvg.js → AnatomyBrowser-ia3I9Nxd.js} +1 -1
- package/dist/dashboard/assets/{BugLog-D-bqCcdb.js → BugLog-ChMhMpQ_.js} +1 -1
- package/dist/dashboard/assets/{CerebrumViewer-Di01VvRz.js → CerebrumViewer-CNi1v636.js} +1 -1
- package/dist/dashboard/assets/{CronStatus-0LpbP4Gk.js → CronStatus-CLg98w_6.js} +1 -1
- package/dist/dashboard/assets/{DesignQC-x5mYKkE4.js → DesignQC-XRxnlPnF.js} +1 -1
- package/dist/dashboard/assets/LinkGraph-_m7MFyVJ.js +1 -0
- package/dist/dashboard/assets/{MemoryViewer-Bc4Vw64G.js → MemoryViewer-eP7iW-aq.js} +1 -1
- package/dist/dashboard/assets/{NativeMemory-Dkk5zZei.js → NativeMemory-CnEsnOyW.js} +1 -1
- package/dist/dashboard/assets/{ProjectOverview-C7G8tn04.js → ProjectOverview-CMZSTm1v.js} +1 -1
- package/dist/dashboard/assets/{ProjectsAggregate-Vu_cGEVx.js → ProjectsAggregate-DJZyw66S.js} +1 -1
- package/dist/dashboard/assets/{StatusBadge-D6b5MXQJ.js → StatusBadge-Du4IbMH3.js} +1 -1
- package/dist/dashboard/assets/{TokenUsage-DFc8Ut2I.js → TokenUsage-6jPFUeu_.js} +1 -1
- package/dist/dashboard/assets/index-CVfvTpSA.css +2 -0
- package/dist/dashboard/assets/{index-BxheJgWu.js → index-DQE5H-ct.js} +3 -3
- package/dist/dashboard/index.html +2 -2
- package/dist/hooks/shared.js +43 -2
- package/dist/src/cli/distill-cmd.js +57 -0
- package/dist/src/cli/distill-cmd.js.map +1 -0
- package/dist/src/cli/index.js +20 -0
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/cli/lint-cmd.js +83 -0
- package/dist/src/cli/lint-cmd.js.map +1 -0
- package/dist/src/cli/recall-cmd.js +45 -12
- package/dist/src/cli/recall-cmd.js.map +1 -1
- package/dist/src/daemon/cron-engine.js +9 -1
- package/dist/src/daemon/cron-engine.js.map +1 -1
- package/dist/src/daemon/wolf-daemon.js +53 -0
- package/dist/src/daemon/wolf-daemon.js.map +1 -1
- package/dist/src/hooks/shared.js +43 -2
- package/dist/src/hooks/shared.js.map +1 -1
- package/dist/src/utils/distill.js +104 -0
- package/dist/src/utils/distill.js.map +1 -0
- package/dist/src/utils/kb-lint.js +289 -0
- package/dist/src/utils/kb-lint.js.map +1 -0
- package/dist/src/utils/link-graph.js +193 -0
- package/dist/src/utils/link-graph.js.map +1 -0
- package/dist/src/utils/maintenance.js +20 -0
- package/dist/src/utils/maintenance.js.map +1 -1
- package/dist/src/utils/semantic-recall.js +63 -0
- package/dist/src/utils/semantic-recall.js.map +1 -1
- package/package.json +1 -1
- package/src/templates/cron-manifest.json +32 -10
- package/dist/dashboard/assets/index-CbZ7BrXr.css +0 -2
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { blocksFor, entryId } from "./recall.js";
|
|
4
|
+
import { readJSON, writeJSON } from "./fs-safe.js";
|
|
5
|
+
import { buildLinkGraph, orphans } from "./link-graph.js";
|
|
6
|
+
export const CANONICAL_SECTIONS = ["User Preferences", "Key Learnings", "Do-Not-Repeat", "Decision Log"];
|
|
7
|
+
/** A date in any of the spellings that occur in practice: 2026-08-19, 19.08.2026, 12.07. */
|
|
8
|
+
const DATE_RE = /\b(20\d\d-\d\d-\d\d|\d\d\.\d\d\.(?:20\d\d|\d\d)?|\d\d\.\d\d\b)/;
|
|
9
|
+
export function headings(content) {
|
|
10
|
+
const out = [];
|
|
11
|
+
const lines = content.split(/\r?\n/);
|
|
12
|
+
let inFence = false;
|
|
13
|
+
for (let i = 0; i < lines.length; i++) {
|
|
14
|
+
// A `## heading` inside a fenced code block is sample text, not structure.
|
|
15
|
+
if (/^\s*```/.test(lines[i])) {
|
|
16
|
+
inFence = !inFence;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (inFence)
|
|
20
|
+
continue;
|
|
21
|
+
const m = lines[i].match(/^(#{1,6})\s+(.*\S)\s*$/);
|
|
22
|
+
if (m)
|
|
23
|
+
out.push({ line: i + 1, level: m[1].length, text: m[2] });
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
/** The canonical section a heading belongs to, by prefix — `Do-Not-Repeat (Forts.)` is still one. */
|
|
28
|
+
export function canonicalOf(text) {
|
|
29
|
+
const t = text.replace(/^[^\w]*/, "");
|
|
30
|
+
return CANONICAL_SECTIONS.find((c) => t.toLowerCase().startsWith(c.toLowerCase())) ?? null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Structure of cerebrum.md: exactly the four sections, each appearing once. Sub-headings below
|
|
34
|
+
* them are fine — that is how a section carries dated detail without the file going flat.
|
|
35
|
+
*/
|
|
36
|
+
export function lintCerebrumStructure(content, file = "cerebrum.md") {
|
|
37
|
+
const findings = [];
|
|
38
|
+
const tops = headings(content).filter((h) => h.level === 2);
|
|
39
|
+
const seen = new Map();
|
|
40
|
+
for (const h of tops) {
|
|
41
|
+
const canon = canonicalOf(h.text);
|
|
42
|
+
if (!canon) {
|
|
43
|
+
findings.push({
|
|
44
|
+
rule: "cerebrum/section-unknown", severity: "warn", file, line: h.line,
|
|
45
|
+
message: `"${h.text}" is not one of the four sections — its content is filed where nothing looks for it`,
|
|
46
|
+
});
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const prev = seen.get(canon);
|
|
50
|
+
if (prev !== undefined) {
|
|
51
|
+
findings.push({
|
|
52
|
+
rule: "cerebrum/section-duplicate", severity: "warn", file, line: h.line,
|
|
53
|
+
message: `second "${canon}" section (first at line ${prev}) — appended alongside instead of into it`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
seen.set(canon, h.line);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const c of CANONICAL_SECTIONS) {
|
|
61
|
+
if (!seen.has(c)) {
|
|
62
|
+
findings.push({
|
|
63
|
+
rule: "cerebrum/section-missing", severity: "warn", file, line: 1,
|
|
64
|
+
message: `no "## ${c}" section — the protocol writes into it, so entries land at the end of the file`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const checked = tops.length + CANONICAL_SECTIONS.length;
|
|
69
|
+
return { findings, score: { rule: "cerebrum/structure", checked, conforming: Math.max(0, checked - findings.length) } };
|
|
70
|
+
}
|
|
71
|
+
/** Do-Not-Repeat entries carry a date — OPENWOLF.md says so explicitly, and undated ones cannot age out. */
|
|
72
|
+
export function lintDoNotRepeatDates(content, file = "cerebrum.md") {
|
|
73
|
+
const findings = [];
|
|
74
|
+
const hs = headings(content);
|
|
75
|
+
const lines = content.split(/\r?\n/);
|
|
76
|
+
let checked = 0, conforming = 0;
|
|
77
|
+
for (let i = 0; i < hs.length; i++) {
|
|
78
|
+
if (hs[i].level !== 2 || canonicalOf(hs[i].text) !== "Do-Not-Repeat")
|
|
79
|
+
continue;
|
|
80
|
+
const end = hs.slice(i + 1).find((h) => h.level <= 2)?.line ?? lines.length + 1;
|
|
81
|
+
const section = lines.slice(hs[i].line, end - 1).join("\n");
|
|
82
|
+
// A dated sub-heading dates everything under it; only then are the bullets allowed to be bare.
|
|
83
|
+
let currentSubIsDated = DATE_RE.test(hs[i].text);
|
|
84
|
+
let subLine = hs[i].line;
|
|
85
|
+
for (const b of blocksFor(file, section)) {
|
|
86
|
+
const absolute = hs[i].line + b.start;
|
|
87
|
+
const head = b.text.split("\n")[0];
|
|
88
|
+
if (/^#{3,6}\s/.test(head)) {
|
|
89
|
+
currentSubIsDated = DATE_RE.test(head);
|
|
90
|
+
subLine = absolute;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (!/^\s*[-*]\s/.test(head))
|
|
94
|
+
continue;
|
|
95
|
+
checked++;
|
|
96
|
+
if (DATE_RE.test(b.text) || currentSubIsDated) {
|
|
97
|
+
conforming++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
findings.push({
|
|
101
|
+
rule: "cerebrum/dnr-undated", severity: "warn", file, line: absolute,
|
|
102
|
+
message: `undated Do-Not-Repeat entry — cannot be aged out or trusted as current (nearest heading line ${subLine})`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { findings, score: { rule: "cerebrum/dnr-undated", checked, conforming } };
|
|
107
|
+
}
|
|
108
|
+
/** buglog entries: the protocol names error_message, root_cause and fix as the point of the record. */
|
|
109
|
+
export function lintBuglog(entries, file = "buglog.json") {
|
|
110
|
+
const findings = [];
|
|
111
|
+
const ids = new Map();
|
|
112
|
+
let conforming = 0;
|
|
113
|
+
entries.forEach((b, i) => {
|
|
114
|
+
const missing = ["error_message", "root_cause", "fix"].filter((k) => !String(b[k] ?? "").trim());
|
|
115
|
+
if (missing.length === 0)
|
|
116
|
+
conforming++;
|
|
117
|
+
else {
|
|
118
|
+
findings.push({
|
|
119
|
+
rule: "buglog/incomplete", severity: "warn", file, line: i + 1,
|
|
120
|
+
message: `${b.id ?? `entry #${i + 1}`} has no ${missing.join(", ")} — a bug record without them cannot prevent a repeat`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const id = String(b.id ?? "").trim();
|
|
124
|
+
if (!id)
|
|
125
|
+
return;
|
|
126
|
+
const prev = ids.get(id);
|
|
127
|
+
// Duplicate ids are an error, not a warning: `openwolf bug <id>` resolves to whichever comes
|
|
128
|
+
// first, so the second record is unreachable through the tool that exists to find it.
|
|
129
|
+
if (prev !== undefined) {
|
|
130
|
+
findings.push({
|
|
131
|
+
rule: "buglog/duplicate-id", severity: "error", file, line: i + 1,
|
|
132
|
+
message: `id ${id} already used by entry #${prev} — one of the two is unreachable`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
else
|
|
136
|
+
ids.set(id, i + 1);
|
|
137
|
+
});
|
|
138
|
+
return { findings, score: { rule: "buglog/complete", checked: entries.length, conforming } };
|
|
139
|
+
}
|
|
140
|
+
/** Session summaries whose scaffold was never replaced — the handoff note that was never written. */
|
|
141
|
+
export function lintMemorySummaries(content, file = "memory.md") {
|
|
142
|
+
const findings = [];
|
|
143
|
+
const lines = content.split(/\r?\n/);
|
|
144
|
+
let checked = 0, conforming = 0;
|
|
145
|
+
lines.forEach((l, i) => {
|
|
146
|
+
if (!/<!--\s*session summary/i.test(l))
|
|
147
|
+
return;
|
|
148
|
+
checked++;
|
|
149
|
+
findings.push({
|
|
150
|
+
rule: "memory/empty-summary", severity: "warn", file, line: i + 1,
|
|
151
|
+
message: "session summary scaffold was never filled in — this session is invisible to recall",
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
// Every filled-in summary counts on the other side of the ratio, so the score reflects the file.
|
|
155
|
+
const filled = (content.match(/^\s*\*\*Did:\*\*/gm) ?? []).length;
|
|
156
|
+
checked += filled;
|
|
157
|
+
conforming += filled;
|
|
158
|
+
return { findings, score: { rule: "memory/summaries", checked, conforming } };
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Entry maturity, from content-addressed ids.
|
|
162
|
+
*
|
|
163
|
+
* [2026-08-20] `entryId` is stable across reordering and changes only when the entry's own text
|
|
164
|
+
* changes — which makes "how long has this been true?" answerable without any new bookkeeping in
|
|
165
|
+
* the files themselves. An entry that has survived unchanged for weeks is settled knowledge; one
|
|
166
|
+
* that appeared yesterday is not, and treating them alike is what makes an old cerebrum feel
|
|
167
|
+
* uniformly stale.
|
|
168
|
+
*
|
|
169
|
+
* Ids no longer present are pruned on every run. A previous ledger in this project grew to 44 MB
|
|
170
|
+
* because nothing ever removed anything; that lesson is cheap to apply here.
|
|
171
|
+
*/
|
|
172
|
+
export function stampMaturity(wolfDir, sources, stableDays = 30, now = new Date()) {
|
|
173
|
+
const statePath = path.join(wolfDir, "kb-state.json");
|
|
174
|
+
const firstRun = !fs.existsSync(statePath);
|
|
175
|
+
const state = readJSON(statePath, { version: 1, first_seen: {} });
|
|
176
|
+
if (!state.first_seen || typeof state.first_seen !== "object")
|
|
177
|
+
state.first_seen = {};
|
|
178
|
+
const today = now.toISOString().slice(0, 10);
|
|
179
|
+
const live = new Set();
|
|
180
|
+
for (const src of sources) {
|
|
181
|
+
let content;
|
|
182
|
+
try {
|
|
183
|
+
content = fs.readFileSync(path.join(wolfDir, src), "utf-8");
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
for (const b of blocksFor(src, content)) {
|
|
189
|
+
// Headings are structure, not knowledge. Counting them would also mean `distill` — which
|
|
190
|
+
// re-levels headings and nothing else — appeared to reset the age of half the corpus.
|
|
191
|
+
if (/^#{1,6}\s/.test(b.text.split("\n")[0]) && b.text.split("\n").length === 1)
|
|
192
|
+
continue;
|
|
193
|
+
live.add(entryId(src, b.text));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (live.size === 0)
|
|
197
|
+
return null;
|
|
198
|
+
for (const id of live)
|
|
199
|
+
if (!state.first_seen[id])
|
|
200
|
+
state.first_seen[id] = today;
|
|
201
|
+
for (const id of Object.keys(state.first_seen))
|
|
202
|
+
if (!live.has(id))
|
|
203
|
+
delete state.first_seen[id];
|
|
204
|
+
const cutoff = new Date(now.getTime() - stableDays * 86400_000).toISOString().slice(0, 10);
|
|
205
|
+
let stable = 0;
|
|
206
|
+
for (const id of live)
|
|
207
|
+
if ((state.first_seen[id] ?? today) <= cutoff)
|
|
208
|
+
stable++;
|
|
209
|
+
writeJSON(statePath, state);
|
|
210
|
+
return { stable, fresh: live.size - stable, stableDays, firstRun };
|
|
211
|
+
}
|
|
212
|
+
export function lintKnowledgeBase(wolfDir, opts = {}) {
|
|
213
|
+
const findings = [];
|
|
214
|
+
const scores = [];
|
|
215
|
+
const read = (f) => {
|
|
216
|
+
try {
|
|
217
|
+
return fs.readFileSync(path.join(wolfDir, f), "utf-8");
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const cerebrum = read("cerebrum.md");
|
|
224
|
+
if (cerebrum !== null) {
|
|
225
|
+
for (const r of [lintCerebrumStructure(cerebrum), lintDoNotRepeatDates(cerebrum)]) {
|
|
226
|
+
findings.push(...r.findings);
|
|
227
|
+
scores.push(r.score);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const memory = read("memory.md");
|
|
231
|
+
if (memory !== null) {
|
|
232
|
+
const r = lintMemorySummaries(memory);
|
|
233
|
+
findings.push(...r.findings);
|
|
234
|
+
scores.push(r.score);
|
|
235
|
+
}
|
|
236
|
+
const bugs = readJSON(path.join(wolfDir, "buglog.json"), null);
|
|
237
|
+
const bugList = Array.isArray(bugs)
|
|
238
|
+
? bugs
|
|
239
|
+
: Array.isArray(bugs?.bugs)
|
|
240
|
+
? bugs.bugs
|
|
241
|
+
: null;
|
|
242
|
+
if (bugList) {
|
|
243
|
+
const r = lintBuglog(bugList);
|
|
244
|
+
findings.push(...r.findings);
|
|
245
|
+
scores.push(r.score);
|
|
246
|
+
}
|
|
247
|
+
if (!opts.skipLinks) {
|
|
248
|
+
try {
|
|
249
|
+
const g = buildLinkGraph(wolfDir);
|
|
250
|
+
// Dead links are reported per target, not per occurrence: one mistyped slug cited from
|
|
251
|
+
// fifteen entries is one thing to fix, and fifteen lines of output would bury the rest.
|
|
252
|
+
const byTarget = new Map();
|
|
253
|
+
for (const d of g.dangling) {
|
|
254
|
+
const e = byTarget.get(d.target);
|
|
255
|
+
if (e)
|
|
256
|
+
e.count++;
|
|
257
|
+
else
|
|
258
|
+
byTarget.set(d.target, { count: 1, from: d.fromSrc, line: 1 });
|
|
259
|
+
}
|
|
260
|
+
for (const [target, e] of byTarget) {
|
|
261
|
+
findings.push({
|
|
262
|
+
rule: "links/dangling", severity: "warn", file: e.from, line: e.line,
|
|
263
|
+
message: `"${target}" is referenced ${e.count}× but does not exist — a note meant to be written, or a typo`,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
const refs = g.edges.length;
|
|
267
|
+
scores.push({ rule: "links/resolve", checked: refs + g.dangling.length, conforming: refs });
|
|
268
|
+
const orphaned = orphans(g, "file");
|
|
269
|
+
if (orphaned.length > 0) {
|
|
270
|
+
findings.push({
|
|
271
|
+
rule: "links/orphans", severity: "warn", file: "native memory", line: 1,
|
|
272
|
+
message: `${orphaned.length} topic files nothing points at and that point nowhere — reachable only by full-text search`,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
const fileNodes = g.nodes.filter((n) => n.kind === "file").length;
|
|
276
|
+
scores.push({ rule: "links/wired", checked: fileNodes, conforming: Math.max(0, fileNodes - orphaned.length) });
|
|
277
|
+
}
|
|
278
|
+
catch { /* no graph: not a lint failure */ }
|
|
279
|
+
}
|
|
280
|
+
const checked = scores.reduce((s, r) => s + r.checked, 0);
|
|
281
|
+
const conforming = scores.reduce((s, r) => s + r.conforming, 0);
|
|
282
|
+
return {
|
|
283
|
+
findings,
|
|
284
|
+
scores,
|
|
285
|
+
compliance: checked > 0 ? conforming / checked : null,
|
|
286
|
+
maturity: stampMaturity(wolfDir, ["cerebrum.md"], opts.stableDays ?? 30, opts.now),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=kb-lint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kb-lint.js","sourceRoot":"","sources":["../../../src/utils/kb-lint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { blocksFor, entryId } from "./recall.js";
|
|
4
|
+
import { nativeMemoryDir } from "../hooks/shared.js";
|
|
5
|
+
const ID_RE = /\[([scmbnx]-[0-9a-f]{6})\]/g;
|
|
6
|
+
const WIKI_RE = /\[\[([^\]|#]{1,120})(?:[|#][^\]]*)?\]\]/g;
|
|
7
|
+
/**
|
|
8
|
+
* Markdown link to a local .md file — how MEMORY.md wires its index.
|
|
9
|
+
*
|
|
10
|
+
* [2026-08-20] The link text bound was 160 and silently missed everything: this project's index
|
|
11
|
+
* uses whole descriptive sentences as link text, measured at 473 characters. A cap picked from
|
|
12
|
+
* imagination rather than from the data reported the entire index as unlinked. Kept on one line
|
|
13
|
+
* so a stray bracket cannot swallow half the file.
|
|
14
|
+
*/
|
|
15
|
+
const MDLINK_RE = /\[[^\]\n]{1,900}\]\(([^)\s]+\.md)\)/g;
|
|
16
|
+
/** Code spans and fences: `[[6,0,[taxId]]]` in an Odoo snippet is an array literal, not a link. */
|
|
17
|
+
const CODE_RE = /```[\s\S]*?```|`[^`\n]*`/g;
|
|
18
|
+
/** A slug is words, digits, dashes, underscores, dots — nothing with commas, quotes or brackets. */
|
|
19
|
+
const SLUG_OK = /^[\w][\w.\-/]{1,119}$/;
|
|
20
|
+
/**
|
|
21
|
+
* Normalize a slug so `-` and `_` are interchangeable and case does not matter.
|
|
22
|
+
*
|
|
23
|
+
* Needed because the two conventions drifted in practice: a topic file called
|
|
24
|
+
* `orderflow_oi_fenster_2026_08_20.md` carries `name: orderflow-oi-fenster-2026-08-20` in its
|
|
25
|
+
* frontmatter, and links are written both ways. Matching literally would report perfectly good
|
|
26
|
+
* links as dead.
|
|
27
|
+
*/
|
|
28
|
+
export function normalizeSlug(s) {
|
|
29
|
+
return s.trim().toLowerCase().replace(/\.md$/, "").replace(/[\s_-]+/g, "-");
|
|
30
|
+
}
|
|
31
|
+
/** Citation ids and wikilinks referenced by a piece of text. Deduplicated, order preserved. */
|
|
32
|
+
export function extractRefs(text) {
|
|
33
|
+
// [2026-08-20] Blank out code first. Measured on this project's own knowledge base, 10 of 17
|
|
34
|
+
// reported dead links were nested array literals from Odoo snippets (`[[6,0,[taxId]]]`) that the
|
|
35
|
+
// wikilink pattern happily matched. A dead-link report full of false positives gets ignored,
|
|
36
|
+
// which is worse than no report.
|
|
37
|
+
const prose = text.replace(CODE_RE, (m) => " ".repeat(m.length));
|
|
38
|
+
const ids = [];
|
|
39
|
+
const wikis = [];
|
|
40
|
+
ID_RE.lastIndex = 0;
|
|
41
|
+
for (let m = ID_RE.exec(prose); m; m = ID_RE.exec(prose))
|
|
42
|
+
ids.push(m[1]);
|
|
43
|
+
WIKI_RE.lastIndex = 0;
|
|
44
|
+
for (let m = WIKI_RE.exec(prose); m; m = WIKI_RE.exec(prose)) {
|
|
45
|
+
const t = m[1].trim();
|
|
46
|
+
if (SLUG_OK.test(t))
|
|
47
|
+
wikis.push(t);
|
|
48
|
+
}
|
|
49
|
+
// Markdown links into .md files count too — that is how MEMORY.md wires its whole index, and
|
|
50
|
+
// reading only wikilinks reported the index itself as an orphan.
|
|
51
|
+
MDLINK_RE.lastIndex = 0;
|
|
52
|
+
for (let m = MDLINK_RE.exec(prose); m; m = MDLINK_RE.exec(prose)) {
|
|
53
|
+
const t = m[1].split("/").pop();
|
|
54
|
+
if (SLUG_OK.test(t))
|
|
55
|
+
wikis.push(t);
|
|
56
|
+
}
|
|
57
|
+
return { ids: [...new Set(ids)], wikis: [...new Set(wikis)] };
|
|
58
|
+
}
|
|
59
|
+
function title(blockText, max = 90) {
|
|
60
|
+
const first = blockText.split("\n")[0].replace(/^[#>\s*-]+/, "").trim();
|
|
61
|
+
return first.length > max ? first.slice(0, max - 1) + "…" : first || "(ohne Titel)";
|
|
62
|
+
}
|
|
63
|
+
const DEFAULT_SOURCES = ["STATUS.md", "cerebrum.md", "memory.md", "buglog.json"];
|
|
64
|
+
/**
|
|
65
|
+
* Build the graph from the same files `recall` searches, so an id in the graph is the same id
|
|
66
|
+
* `recall --id` resolves. One index, not two — two indexes over one corpus drift apart.
|
|
67
|
+
*/
|
|
68
|
+
export function buildLinkGraph(wolfDir, opts = {}) {
|
|
69
|
+
const nodes = new Map();
|
|
70
|
+
const edges = [];
|
|
71
|
+
const dangling = [];
|
|
72
|
+
const bySlug = new Map();
|
|
73
|
+
// The .wolf sources get a file node too, so a link like `[…](cerebrum.md)` from a topic file
|
|
74
|
+
// resolves instead of being reported dead. They are real files; only their kind differs.
|
|
75
|
+
const files = (opts.sources ?? DEFAULT_SOURCES)
|
|
76
|
+
.map((src) => ({ label: src, abspath: path.join(wolfDir, src), slug: normalizeSlug(src) }));
|
|
77
|
+
if (opts.includeNative !== false) {
|
|
78
|
+
const nd = opts.nativeDir !== undefined ? opts.nativeDir : nativeMemoryDir(path.dirname(wolfDir));
|
|
79
|
+
if (nd) {
|
|
80
|
+
let entries = [];
|
|
81
|
+
try {
|
|
82
|
+
entries = fs.readdirSync(nd);
|
|
83
|
+
}
|
|
84
|
+
catch { /* unreadable */ }
|
|
85
|
+
for (const name of entries) {
|
|
86
|
+
if (!name.endsWith(".md") || name.includes(".bak"))
|
|
87
|
+
continue;
|
|
88
|
+
files.push({ label: `native/${name}`, abspath: path.join(nd, name), slug: normalizeSlug(name) });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Pass 1: every block becomes a node; every topic file additionally becomes a file node so a
|
|
93
|
+
// wikilink has something to land on.
|
|
94
|
+
const blocksByFile = [];
|
|
95
|
+
for (const f of files) {
|
|
96
|
+
let content;
|
|
97
|
+
try {
|
|
98
|
+
content = fs.readFileSync(f.abspath, "utf-8");
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const blocks = blocksFor(f.label, content).map((b) => ({ start: b.start, text: b.text }));
|
|
104
|
+
blocksByFile.push({ label: f.label, blocks });
|
|
105
|
+
if (f.slug) {
|
|
106
|
+
// [2026-08-20] The key is the FILE LABEL, not the slug. Keying by slug collided:
|
|
107
|
+
// `.wolf/memory.md` and `native/MEMORY.md` both normalize to "memory", so one silently
|
|
108
|
+
// replaced the other and MEMORY.md lost every one of its ~200 outgoing index links.
|
|
109
|
+
const key = `file:${f.label}`;
|
|
110
|
+
// The frontmatter `name:` wins when present — that is what links are written against.
|
|
111
|
+
const fm = content.match(/^---[\s\S]*?\bname:\s*([^\n]+)/);
|
|
112
|
+
const slug = normalizeSlug(fm ? fm[1] : f.slug);
|
|
113
|
+
nodes.set(key, { key, kind: "file", src: f.label, line: 1, title: f.label.replace("native/", ""), slug, inbound: 0, outbound: 0 });
|
|
114
|
+
// Wikilinks target topic files, so a native file wins a slug it shares with a .wolf source.
|
|
115
|
+
const isNative = f.label.startsWith("native/");
|
|
116
|
+
for (const alias of new Set([slug, f.slug])) {
|
|
117
|
+
if (isNative || !bySlug.has(alias))
|
|
118
|
+
bySlug.set(alias, key);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const b of blocks) {
|
|
122
|
+
const key = entryId(f.label, b.text);
|
|
123
|
+
if (!nodes.has(key)) {
|
|
124
|
+
nodes.set(key, { key, kind: "block", src: f.label, line: b.start, title: title(b.text), inbound: 0, outbound: 0 });
|
|
125
|
+
}
|
|
126
|
+
// [2026-08-20] Bug records are referenced by id from cerebrum and memory (`[[bug-209]]`), so
|
|
127
|
+
// they need an address of their own. Without one, `lint` reported live bug ids as dead links
|
|
128
|
+
// on its very first run — 10 of them, all present in buglog.json. A checker whose false
|
|
129
|
+
// positives point at the checker is worse than no checker.
|
|
130
|
+
const idm = f.label.endsWith(".json") ? b.text.match(/^([a-z]+-\d{2,})[\s—:-]/i) : null;
|
|
131
|
+
if (idm) {
|
|
132
|
+
const s = normalizeSlug(idm[1]);
|
|
133
|
+
if (!bySlug.has(s))
|
|
134
|
+
bySlug.set(s, key);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Pass 2: edges. Only now, because a reference may point forward to a file read later.
|
|
139
|
+
for (const { label, blocks } of blocksByFile) {
|
|
140
|
+
for (const b of blocks) {
|
|
141
|
+
const from = entryId(label, b.text);
|
|
142
|
+
const { ids, wikis } = extractRefs(b.text);
|
|
143
|
+
for (const id of ids) {
|
|
144
|
+
if (id === from)
|
|
145
|
+
continue; // a block citing itself is noise, not a link
|
|
146
|
+
if (nodes.has(id))
|
|
147
|
+
edges.push({ from, to: id, kind: "id" });
|
|
148
|
+
else
|
|
149
|
+
dangling.push({ from, fromSrc: label, target: id, kind: "id" });
|
|
150
|
+
}
|
|
151
|
+
for (const w of wikis) {
|
|
152
|
+
const target = bySlug.get(normalizeSlug(w));
|
|
153
|
+
if (target && target !== from)
|
|
154
|
+
edges.push({ from, to: target, kind: "wiki" });
|
|
155
|
+
else if (!target)
|
|
156
|
+
dangling.push({ from, fromSrc: label, target: w, kind: "wiki" });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Degrees, after deduplicating parallel edges of the same kind.
|
|
161
|
+
const seen = new Set();
|
|
162
|
+
const unique = [];
|
|
163
|
+
for (const e of edges) {
|
|
164
|
+
const k = `${e.from}>${e.to}:${e.kind}`;
|
|
165
|
+
if (seen.has(k))
|
|
166
|
+
continue;
|
|
167
|
+
seen.add(k);
|
|
168
|
+
unique.push(e);
|
|
169
|
+
const f = nodes.get(e.from);
|
|
170
|
+
if (f)
|
|
171
|
+
f.outbound++;
|
|
172
|
+
const t = nodes.get(e.to);
|
|
173
|
+
if (t)
|
|
174
|
+
t.inbound++;
|
|
175
|
+
}
|
|
176
|
+
return { nodes: [...nodes.values()], edges: unique, dangling };
|
|
177
|
+
}
|
|
178
|
+
/** Who points at this node. The question citations could not answer before. */
|
|
179
|
+
export function backlinksFor(graph, key) {
|
|
180
|
+
const byKey = new Map(graph.nodes.map((n) => [n.key, n]));
|
|
181
|
+
return graph.edges
|
|
182
|
+
.filter((e) => e.to === key)
|
|
183
|
+
.map((e) => byKey.get(e.from))
|
|
184
|
+
.filter((n) => Boolean(n));
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Nodes nothing points at and that point nowhere. Not a defect by itself — most blocks are
|
|
188
|
+
* plain notes — but among *topic files* an orphan is usually one that was never wired in.
|
|
189
|
+
*/
|
|
190
|
+
export function orphans(graph, kind) {
|
|
191
|
+
return graph.nodes.filter((n) => n.inbound === 0 && n.outbound === 0 && (!kind || n.kind === kind));
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=link-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-graph.js","sourceRoot":"","sources":["../../../src/utils/link-graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -416,6 +416,26 @@ export function footprint(wolfDir, ret) {
|
|
|
416
416
|
`— semantic recall misses them. Refresh with 'openwolf recall <query> --semantic'`);
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
|
+
// [2026-08-20] AI cron tasks send .wolf files to an LLM API. They now ship disabled, but
|
|
420
|
+
// cron-manifest.json is user data and `update` never rewrites it — so a project created before
|
|
421
|
+
// this change keeps them on and would never hear about it. This warning is how existing installs
|
|
422
|
+
// find out. It fires only when a key is actually available, so it is a real exposure, not a lecture.
|
|
423
|
+
try {
|
|
424
|
+
const manifest = readJSON(path.join(wolfDir, "cron-manifest.json"), {});
|
|
425
|
+
const filesOf = (t) => t.action?.params?.context_files ?? [];
|
|
426
|
+
const sending = (manifest.tasks ?? []).filter((t) => t.enabled && filesOf(t).length > 0);
|
|
427
|
+
const cfg = readJSON(path.join(wolfDir, "config.json"), {});
|
|
428
|
+
const cron = cfg.openwolf?.cron ?? {};
|
|
429
|
+
const keyEnv = cron.api_key_env || "ANTHROPIC_API_KEY";
|
|
430
|
+
if (sending.length > 0 && cron.enabled !== false && process.env[keyEnv]) {
|
|
431
|
+
const names = sending.map((t) => t.id).join(", ");
|
|
432
|
+
const files = [...new Set(sending.flatMap(filesOf))].join(", ");
|
|
433
|
+
warnings.push(`cron: ${names} send ${files} to an LLM API on schedule, and ${keyEnv} is set. ` +
|
|
434
|
+
`<private> blocks are stripped, everything else is not. Disable in .wolf/cron-manifest.json ` +
|
|
435
|
+
`(enabled: false) if that is not what you want.`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
catch { /* no manifest, nothing to warn about */ }
|
|
419
439
|
const logBytes = fileSize(path.join(wolfDir, "daemon.log"));
|
|
420
440
|
if (logBytes > 0) {
|
|
421
441
|
items.push({ name: "daemon.log", bytes: logBytes });
|