@zosmaai/pi-llm-wiki 0.10.7 → 0.11.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.de.md +35 -4
  3. package/README.es.md +260 -170
  4. package/README.fr.md +35 -4
  5. package/README.hi.md +35 -4
  6. package/README.ja.md +35 -4
  7. package/README.ko.md +35 -4
  8. package/README.md +38 -3
  9. package/README.pt.md +35 -4
  10. package/README.ru.md +35 -4
  11. package/README.zh.md +260 -170
  12. package/assets/demo.gif +0 -0
  13. package/dist/extensions/llm-wiki/lib/bootstrap.js +71 -0
  14. package/dist/extensions/llm-wiki/lib/embeddings.js +401 -0
  15. package/dist/extensions/llm-wiki/lib/guardrails.js +232 -0
  16. package/dist/extensions/llm-wiki/lib/indexing.js +78 -0
  17. package/dist/extensions/llm-wiki/lib/ingest-worker.js +310 -0
  18. package/dist/extensions/llm-wiki/lib/inject.js +65 -0
  19. package/dist/extensions/llm-wiki/lib/knowledge-document.js +442 -0
  20. package/dist/extensions/llm-wiki/lib/knowledge-links.js +206 -0
  21. package/dist/extensions/llm-wiki/lib/legacy-repair.js +443 -0
  22. package/dist/extensions/llm-wiki/lib/metadata.js +499 -0
  23. package/dist/extensions/llm-wiki/lib/model-command.js +86 -0
  24. package/dist/extensions/llm-wiki/lib/observation.js +283 -0
  25. package/dist/extensions/llm-wiki/lib/recall.js +875 -0
  26. package/dist/extensions/llm-wiki/lib/retro.js +158 -0
  27. package/dist/extensions/llm-wiki/lib/runtime.js +191 -0
  28. package/dist/extensions/llm-wiki/lib/source-extractors.js +426 -0
  29. package/dist/extensions/llm-wiki/lib/source-packet.js +229 -0
  30. package/dist/extensions/llm-wiki/lib/subagent.js +41 -0
  31. package/dist/extensions/llm-wiki/lib/task-config.js +172 -0
  32. package/dist/extensions/llm-wiki/lib/tools.js +1192 -0
  33. package/dist/extensions/llm-wiki/lib/trajectories-command.js +51 -0
  34. package/dist/extensions/llm-wiki/lib/trajectory.js +467 -0
  35. package/dist/extensions/llm-wiki/lib/utils.js +347 -0
  36. package/dist/extensions/llm-wiki/lib/vault-format.js +247 -0
  37. package/dist/extensions/llm-wiki/lib/visible-status.js +31 -0
  38. package/dist/extensions/llm-wiki/lib/wiki-service.js +128 -0
  39. package/dist/mcp/exec.js +121 -0
  40. package/dist/mcp/index.js +229 -0
  41. package/dist/mcp/operations.js +130 -0
  42. package/dist/package.json +1 -0
  43. package/docs/superpowers/plans/2026-08-02-okf-foundation.md +1579 -0
  44. package/docs/superpowers/plans/2026-08-03-okf-foundation-remediation.md +3005 -0
  45. package/docs/superpowers/plans/2026-08-06-okf-foundation-release-remediation.md +1174 -0
  46. package/docs/superpowers/specs/2026-08-02-okf-foundation-design.md +578 -0
  47. package/docs/superpowers/specs/2026-08-02-okf-v0.2-interoperability-design.md +538 -0
  48. package/extensions/llm-wiki/index.ts +22 -36
  49. package/extensions/llm-wiki/lib/bootstrap.ts +84 -0
  50. package/extensions/llm-wiki/lib/embeddings.ts +9 -3
  51. package/extensions/llm-wiki/lib/guardrails.ts +174 -29
  52. package/extensions/llm-wiki/lib/indexing.ts +2 -1
  53. package/extensions/llm-wiki/lib/ingest-worker.ts +170 -29
  54. package/extensions/llm-wiki/lib/knowledge-document.ts +661 -0
  55. package/extensions/llm-wiki/lib/knowledge-links.ts +282 -0
  56. package/extensions/llm-wiki/lib/legacy-repair.ts +572 -0
  57. package/extensions/llm-wiki/lib/metadata.ts +531 -116
  58. package/extensions/llm-wiki/lib/observation.ts +37 -43
  59. package/extensions/llm-wiki/lib/recall.ts +61 -33
  60. package/extensions/llm-wiki/lib/retro.ts +65 -41
  61. package/extensions/llm-wiki/lib/source-extractors.ts +12 -17
  62. package/extensions/llm-wiki/lib/source-packet.ts +44 -31
  63. package/extensions/llm-wiki/lib/tools.ts +406 -348
  64. package/extensions/llm-wiki/lib/trajectory.ts +15 -1
  65. package/extensions/llm-wiki/lib/utils.ts +121 -130
  66. package/extensions/llm-wiki/lib/vault-format.ts +363 -0
  67. package/extensions/llm-wiki/lib/wiki-service.ts +183 -0
  68. package/mcp/exec.ts +122 -0
  69. package/mcp/index.ts +60 -250
  70. package/mcp/operations.ts +176 -0
  71. package/package.json +8 -2
  72. package/scripts/migrate-llm-wiki.js +801 -0
  73. package/skills/llm-wiki/SKILL.md +8 -6
@@ -0,0 +1,283 @@
1
+ import { join } from "node:path";
2
+ import { Type } from "typebox";
3
+ import { scheduleReindex } from "./indexing.js";
4
+ import { createKnowledgeDocument, writeKnowledgeDocumentFile } from "./knowledge-document.js";
5
+ import { appendEvent, rebuildMetadataLight } from "./metadata.js";
6
+ import { fmtDate, resolveVaultPaths } from "./utils.js";
7
+ import { assertWritableVault, inspectWritableVault } from "./vault-format.js";
8
+ // ─── Save Observation ──────────────────────────────────
9
+ const RELEVANCE_EMOJIS = {
10
+ low: "📝",
11
+ medium: "🔍",
12
+ high: "⭐",
13
+ critical: "🔴",
14
+ };
15
+ /**
16
+ * Save an observation as a wiki source page.
17
+ *
18
+ * Unlike wiki_retro (which saves atomic insights at task end),
19
+ * wiki_observe records timestamped observations during a session
20
+ * that can later be distilled into durable wiki pages.
21
+ *
22
+ * Observations are stored in wiki/sources/ with type: source and
23
+ * status: observation. They are searchable via wiki_recail.
24
+ */
25
+ export function saveObservation(paths, input, opts) {
26
+ assertWritableVault(paths);
27
+ const today = fmtDate();
28
+ const timestamp = new Date().toISOString();
29
+ // Generate a slug from title
30
+ const slugBase = input.title
31
+ .toLowerCase()
32
+ .replace(/[^a-z0-9]+/g, "-")
33
+ .replace(/^-|-$/g, "")
34
+ .slice(0, 60);
35
+ const slug = `obs-${today}-${slugBase}`;
36
+ const pagePath = join(paths.wiki, "sources", `${slug}.md`);
37
+ const relevanceEmoji = RELEVANCE_EMOJIS[input.relevance] ?? "📝";
38
+ const tags = input.tags ?? "";
39
+ const sourceContext = input.source_context ?? "";
40
+ const body = `# ${relevanceEmoji} Observation: ${input.title}
41
+
42
+ ${input.content}
43
+
44
+ *Relevance: ${input.relevance}*${sourceContext ? `\n*Context: ${sourceContext}*` : ""}${tags ? `\n*Tags: ${tags}*` : ""}
45
+
46
+ ---
47
+ *Observed: ${timestamp}*`;
48
+ const doc = createKnowledgeDocument(`sources/${slug}.md`, {
49
+ type: "source",
50
+ title: `Observation: ${input.title}`,
51
+ slug,
52
+ status: "observation",
53
+ created: today,
54
+ updated: today,
55
+ relevance: input.relevance,
56
+ observed_at: timestamp,
57
+ ...(tags ? { tags: tags.split(/\s+/).filter(Boolean) } : {}),
58
+ ...(sourceContext ? { source_context: sourceContext } : {}),
59
+ }, body);
60
+ writeKnowledgeDocumentFile(pagePath, doc);
61
+ // Log event
62
+ appendEvent(paths, {
63
+ kind: "observe",
64
+ slug,
65
+ title: input.title,
66
+ relevance: input.relevance,
67
+ });
68
+ // Rebuild metadata so the observation is immediately searchable. Callers that
69
+ // background this (the wiki_observe tool) pass { rebuild: false } and schedule
70
+ // a non-blocking reindex instead.
71
+ if (opts?.rebuild !== false)
72
+ rebuildMetadataLight(paths);
73
+ return { slug, pagePath };
74
+ }
75
+ export function createReminderState() {
76
+ return { observeDoneThisSession: false };
77
+ }
78
+ // ─── Tool Registration ─────────────────────────────────
79
+ /**
80
+ * Register the `wiki_observe` tool.
81
+ * The model calls this to record observations during a session.
82
+ * Observations are saved to the wiki and become searchable.
83
+ */
84
+ export function registerWikiObserve(pi, runtime, reminderState) {
85
+ pi.registerTool({
86
+ name: "wiki_observe",
87
+ label: "Wiki Observe",
88
+ description: "Record an atomic observation from the current session into the wiki. " +
89
+ "Observations are timestamped, relevance-rated facts about decisions made, " +
90
+ "findings discovered, constraints established, or work completed. " +
91
+ "Saved observations are searchable via wiki_recall and can later be " +
92
+ "distilled into durable wiki pages via wiki_ensure_page. " +
93
+ "Call this proactively after non-trivial work — every observation " +
94
+ "compounds the wiki's knowledge across sessions.",
95
+ promptSnippet: "Record an observation about the current work",
96
+ promptGuidelines: [
97
+ "Call wiki_observe after non-trivial decisions, discoveries, or completions.",
98
+ "One observation per call. Use multiple calls for multiple observations.",
99
+ "Rate relevance honestly — most observations are medium or low, not critical.",
100
+ "Observations compound across sessions via wiki_recail.",
101
+ ],
102
+ parameters: Type.Object({
103
+ title: Type.String({
104
+ description: "Short descriptive title (≤80 chars). Noun phrase, not a sentence. " +
105
+ "Example: 'JWT auth middleware added' or 'Postgres migration constraint discovered'",
106
+ }),
107
+ content: Type.String({
108
+ description: "The observation in plain prose. What happened, was decided, or was learned. " +
109
+ "Preserve specific details: file paths, function names, error messages, " +
110
+ "quantitative results. Example: 'User decided to use JWT with refresh tokens. " +
111
+ "Implementation at src/auth/jwt.ts. Tests passing.'",
112
+ }),
113
+ relevance: Type.Union([
114
+ Type.Literal("low"),
115
+ Type.Literal("medium"),
116
+ Type.Literal("high"),
117
+ Type.Literal("critical"),
118
+ ], {
119
+ description: "Relevance level: low (routine), medium (task context), " +
120
+ "high (non-trivial decisions/constraints), critical (user identity, " +
121
+ "persistent preferences, completed work that must not be redone). " +
122
+ "Default: medium. Be honest — most observations are medium or low.",
123
+ }),
124
+ tags: Type.Optional(Type.String({
125
+ description: "Optional space-separated tags for categorization. " +
126
+ "Example: 'auth backend migration'",
127
+ })),
128
+ source_context: Type.Optional(Type.String({
129
+ description: "What was being worked on. Example: 'Adding authentication module' or 'Debugging login timeout'",
130
+ })),
131
+ }),
132
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
133
+ const paths = resolveVaultPaths(ctx.cwd ?? process.cwd());
134
+ const vaultCheck = inspectWritableVault(paths);
135
+ if (!vaultCheck.ok) {
136
+ return {
137
+ content: [
138
+ {
139
+ type: "text",
140
+ text: `Wiki vault error: ${vaultCheck.diagnostics[0].message}`,
141
+ },
142
+ ],
143
+ details: {
144
+ error: vaultCheck.diagnostics[0].code,
145
+ diagnostics: vaultCheck.diagnostics,
146
+ },
147
+ isError: true,
148
+ };
149
+ }
150
+ const result = saveObservation(paths, {
151
+ title: params.title,
152
+ content: params.content,
153
+ relevance: params.relevance,
154
+ tags: params.tags,
155
+ source_context: params.source_context,
156
+ },
157
+ // When a background runtime is available, write the page synchronously
158
+ // but defer the O(pages) metadata rebuild + embeddings off the tool's
159
+ // critical path. Without a runtime, fall back to the inline rebuild.
160
+ { rebuild: !runtime });
161
+ if (runtime) {
162
+ const launchCtx = { hasUI: ctx.hasUI, ui: ctx.ui };
163
+ scheduleReindex(runtime, launchCtx, paths);
164
+ }
165
+ // Signal the reminder to stop nagging this session
166
+ if (reminderState) {
167
+ reminderState.observeDoneThisSession = true;
168
+ }
169
+ const relevanceEmoji = RELEVANCE_EMOJIS[params.relevance] ?? "📝";
170
+ return {
171
+ content: [
172
+ {
173
+ type: "text",
174
+ text: [
175
+ `${relevanceEmoji} **Observation saved**: ${params.title}`,
176
+ "",
177
+ `- Page: \`${result.pagePath}\``,
178
+ `- Relevance: ${params.relevance}`,
179
+ params.tags ? `- Tags: ${params.tags}` : "",
180
+ "",
181
+ "This observation is now searchable via wiki_recall. " +
182
+ "It will compound with future observations across sessions.",
183
+ ]
184
+ .filter((l) => l !== "")
185
+ .join("\n"),
186
+ },
187
+ ],
188
+ details: {
189
+ slug: result.slug,
190
+ title: params.title,
191
+ relevance: params.relevance,
192
+ tags: params.tags || null,
193
+ },
194
+ };
195
+ },
196
+ });
197
+ }
198
+ // ─── Turn-End Reminder ─────────────────────────────────
199
+ /**
200
+ * Build the one-time, user-visible session notice (issue #77) that announces
201
+ * the full wiki loop so the user can SEE the wiki is active and what it offers:
202
+ *
203
+ * retrieval (sync, on the LLM's critical path): recall → search → read
204
+ * capture (background + reported): observe → retro
205
+ *
206
+ * Shown once per session when `notices` are enabled; silenced otherwise.
207
+ */
208
+ export function buildSessionNotice() {
209
+ return [
210
+ "\u{1F9E0} **LLM Wiki active.**",
211
+ "Retrieval (inline): recall runs automatically each turn — use `wiki_search` to query",
212
+ "and `read` to open pages.",
213
+ "Capture (background + reported): `wiki_observe` for timestamped notes,",
214
+ "`wiki_retro` for durable insights. All other wiki actions run in the background and",
215
+ "report when done. Silence these notices with `llm-wiki.notices: false`.",
216
+ ].join(" ");
217
+ }
218
+ /**
219
+ * Build the periodic observe/retro reminder text. Mentions BOTH capture tools
220
+ * (issue #77): `wiki_observe` for timestamped session observations and
221
+ * `wiki_retro` for distilled, durable insights at task end.
222
+ */
223
+ export function buildReminderText() {
224
+ return [
225
+ "**Wiki capture reminder:** If the work in this session produced non-trivial",
226
+ "decisions, findings, constraints, or completions worth preserving across sessions,",
227
+ "record them now: call `wiki_observe` for timestamped observations, or `wiki_retro`",
228
+ "to save a distilled insight. Both are searchable via `wiki_recall` and compound",
229
+ "your wiki's knowledge over time.",
230
+ "",
231
+ "One item per call. Separate distinct findings into multiple calls.",
232
+ ].join(" ");
233
+ }
234
+ /**
235
+ * Track observation cadence and send turn-end reminders.
236
+ * After every N significant turns, reminds the model to call wiki_observe
237
+ * for non-trivial findings (same pattern as memex-retro reminders).
238
+ *
239
+ * `options.display` (issue #77) controls whether the reminder is shown to the
240
+ * user (`true`, the default) or injected silently into model context only
241
+ * (`false`). Pass a resolver so the live `notices` config is read at send time.
242
+ */
243
+ export function registerObservationReminder(pi, reminderState, options) {
244
+ const REMINDER_INTERVAL = options?.turnsBetweenReminders ?? 5;
245
+ const resolveDisplay = () => {
246
+ const d = options?.display;
247
+ if (typeof d === "function")
248
+ return d();
249
+ if (typeof d === "boolean")
250
+ return d;
251
+ return true;
252
+ };
253
+ let turnsSinceLastReminder = 0;
254
+ pi.on("session_start", async () => {
255
+ turnsSinceLastReminder = 0;
256
+ reminderState.observeDoneThisSession = false;
257
+ });
258
+ // After compaction, reset turn counter so reminders resume
259
+ // BUT preserve observeDoneThisSession — if the model already called
260
+ // wiki_observe this session, compaction should not resurrect the nag.
261
+ pi.on("session_compact", async () => {
262
+ turnsSinceLastReminder = 0;
263
+ });
264
+ pi.on("agent_end", async (event, _ctx) => {
265
+ // Skip reminder on retries — willRetry means pi will re-run the agent,
266
+ // and queuing another reminder would duplicate them (issue: connection
267
+ // errors cause multiple retries, each firing agent_end).
268
+ if ("willRetry" in event && event.willRetry)
269
+ return;
270
+ turnsSinceLastReminder++;
271
+ if (turnsSinceLastReminder < REMINDER_INTERVAL)
272
+ return;
273
+ if (reminderState.observeDoneThisSession)
274
+ return;
275
+ pi.sendMessage({
276
+ customType: "wiki-observe-reminder",
277
+ content: buildReminderText(),
278
+ display: resolveDisplay(),
279
+ }, {
280
+ deliverAs: "nextTurn",
281
+ });
282
+ });
283
+ }