pi-para 0.5.2 → 0.5.5
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/ARCHITECTURE.md +160 -0
- package/CHANGELOG.md +29 -0
- package/CONFIGURATION.md +243 -0
- package/README.md +140 -237
- package/SETUP.md +196 -112
- package/STRUCTURE.md +140 -0
- package/dist/atomic-write.d.ts +2 -0
- package/dist/atomic-write.d.ts.map +1 -0
- package/dist/atomic-write.js +18 -0
- package/dist/atomic-write.js.map +1 -0
- package/dist/capture.d.ts +4 -4
- package/dist/capture.d.ts.map +1 -1
- package/dist/capture.js +1 -1
- package/dist/capture.js.map +1 -1
- package/dist/cli-legacy.d.ts +4 -0
- package/dist/cli-legacy.d.ts.map +1 -0
- package/dist/cli-legacy.js +41 -0
- package/dist/cli-legacy.js.map +1 -0
- package/dist/cli.d.ts +2 -9
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +135 -29
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts +3 -3
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +189 -256
- package/dist/commands.js.map +1 -1
- package/dist/config.d.ts +104 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +242 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +4 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +29 -7
- package/dist/context.js.map +1 -1
- package/dist/credentials.d.ts +27 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +85 -0
- package/dist/credentials.js.map +1 -0
- package/dist/daemon.d.ts +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/doctor.d.ts +20 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +171 -0
- package/dist/doctor.js.map +1 -0
- package/dist/gepa/index.js +1 -1
- package/dist/gepa/index.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +102 -37
- package/dist/index.js.map +1 -1
- package/dist/maintainer.d.ts +2 -2
- package/dist/maintainer.d.ts.map +1 -1
- package/dist/maintainer.js +2 -2
- package/dist/maintainer.js.map +1 -1
- package/dist/model-resolver.d.ts +30 -0
- package/dist/model-resolver.d.ts.map +1 -0
- package/dist/model-resolver.js +71 -0
- package/dist/model-resolver.js.map +1 -0
- package/dist/paths.d.ts +20 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +33 -0
- package/dist/paths.js.map +1 -0
- package/dist/processor.d.ts +2 -2
- package/dist/processor.d.ts.map +1 -1
- package/dist/processor.js +2 -2
- package/dist/processor.js.map +1 -1
- package/dist/qmd-providers.d.ts +12 -0
- package/dist/qmd-providers.d.ts.map +1 -0
- package/dist/qmd-providers.js +45 -0
- package/dist/qmd-providers.js.map +1 -0
- package/dist/query.d.ts +1 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +14 -1
- package/dist/query.js.map +1 -1
- package/dist/repair.d.ts +11 -0
- package/dist/repair.d.ts.map +1 -0
- package/dist/repair.js +41 -0
- package/dist/repair.js.map +1 -0
- package/dist/scheduler/controls.d.ts +35 -0
- package/dist/scheduler/controls.d.ts.map +1 -0
- package/dist/scheduler/controls.js +98 -0
- package/dist/scheduler/controls.js.map +1 -0
- package/dist/scheduler/index.d.ts +48 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +193 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/leases.d.ts +22 -0
- package/dist/scheduler/leases.d.ts.map +1 -0
- package/dist/scheduler/leases.js +74 -0
- package/dist/scheduler/leases.js.map +1 -0
- package/dist/scheduler/session-capture.d.ts +33 -0
- package/dist/scheduler/session-capture.d.ts.map +1 -0
- package/dist/scheduler/session-capture.js +108 -0
- package/dist/scheduler/session-capture.js.map +1 -0
- package/dist/scheduler/state.d.ts +56 -0
- package/dist/scheduler/state.d.ts.map +1 -0
- package/dist/scheduler/state.js +210 -0
- package/dist/scheduler/state.js.map +1 -0
- package/dist/session-tools.d.ts +1 -1
- package/dist/session-tools.d.ts.map +1 -1
- package/dist/settings.d.ts +19 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +106 -0
- package/dist/settings.js.map +1 -0
- package/dist/setup.d.ts +29 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +90 -0
- package/dist/setup.js.map +1 -0
- package/dist/status.d.ts +22 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +93 -0
- package/dist/status.js.map +1 -0
- package/dist/store.d.ts +23 -8
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +89 -34
- package/dist/store.js.map +1 -1
- package/dist/summarize.d.ts +6 -2
- package/dist/summarize.d.ts.map +1 -1
- package/dist/summarize.js +60 -25
- package/dist/summarize.js.map +1 -1
- package/dist/templates/prompts.d.ts +1 -1
- package/dist/templates/prompts.d.ts.map +1 -1
- package/dist/templates/prompts.js +1 -1
- package/dist/tools.d.ts +5 -5
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +243 -78
- package/dist/tools.js.map +1 -1
- package/dist/wiki-tool-guidance.d.ts +6 -0
- package/dist/wiki-tool-guidance.d.ts.map +1 -0
- package/dist/wiki-tool-guidance.js +70 -0
- package/dist/wiki-tool-guidance.js.map +1 -0
- package/dist/wiki.d.ts +6 -1
- package/dist/wiki.d.ts.map +1 -1
- package/dist/wiki.js +88 -19
- package/dist/wiki.js.map +1 -1
- package/package.json +80 -15
- package/scripts/install.ps1 +44 -0
- package/scripts/install.sh +62 -0
- package/scripts/smoke-install.mjs +61 -0
- package/skills/para/SKILL.md +1 -1
- package/skills/setup/SKILL.md +1 -1
- package/src/atomic-write.ts +17 -0
- package/src/capture.ts +4 -4
- package/src/cli-legacy.ts +43 -0
- package/src/cli.ts +133 -28
- package/src/commands.ts +203 -243
- package/src/config.ts +355 -0
- package/src/context.ts +29 -14
- package/src/credentials.ts +100 -0
- package/src/daemon.ts +1 -1
- package/src/doctor.ts +204 -0
- package/src/gepa/index.ts +1 -1
- package/src/index.ts +119 -40
- package/src/maintainer.ts +4 -4
- package/src/model-resolver.ts +96 -0
- package/src/paths.ts +48 -0
- package/src/processor.ts +4 -4
- package/src/qmd-providers.ts +63 -0
- package/src/query.ts +15 -1
- package/src/repair.ts +49 -0
- package/src/scheduler/controls.ts +125 -0
- package/src/scheduler/index.ts +210 -0
- package/src/scheduler/leases.ts +98 -0
- package/src/scheduler/session-capture.ts +146 -0
- package/src/scheduler/state.ts +304 -0
- package/src/session-tools.ts +1 -1
- package/src/settings.ts +126 -0
- package/src/setup.ts +116 -0
- package/src/status.ts +115 -0
- package/src/store.ts +116 -31
- package/src/summarize.ts +72 -16
- package/src/templates/prompts.ts +1 -1
- package/src/tools.ts +280 -90
- package/src/wiki-tool-guidance.ts +84 -0
- package/src/wiki.ts +98 -26
package/dist/tools.js
CHANGED
|
@@ -6,13 +6,12 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Factory functions (createXxxExecute) close over wikiDir and store — no ctx dependency.
|
|
8
8
|
*/
|
|
9
|
-
import { StringEnum } from "@
|
|
10
|
-
import { Text } from "@
|
|
9
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
10
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
11
11
|
import { Type } from "typebox";
|
|
12
12
|
import { resolveSource, truncateSource, detectSourceType } from "./ingest.js";
|
|
13
13
|
import { queryWiki as queryWikiLib, formatQueryResults, formatFreshness } from "./query.js";
|
|
14
|
-
import {
|
|
15
|
-
import { readPage, writePage, movePage, listPages, readIndex, writeIndex, readSchema, appendLog, gitCommit, rebuildIndex, PARA_CATEGORIES, } from "./wiki.js";
|
|
14
|
+
import { readPage, writePage, movePage, listPages, listPageSlugs, readIndex, writeIndex, readSchema, appendLog, gitCommit, rebuildIndex, PARA_CATEGORIES, } from "./wiki.js";
|
|
16
15
|
import { validateFrontmatter, parseFrontmatter, CURRENT_SCHEMA_VERSION, migrateToLatest, } from "./frontmatter.js";
|
|
17
16
|
import { extractWikilinks, autoLinkSlugs, syncFrontmatterLinks } from "./link-utils.js";
|
|
18
17
|
import { normalizeTags, normalizeScopes } from "./tag-registry.js";
|
|
@@ -20,6 +19,8 @@ import { redactSecrets } from "./redact.js";
|
|
|
20
19
|
import { lintWiki } from "./lint.js";
|
|
21
20
|
import { generateOverviewPrompt } from "./summarize.js";
|
|
22
21
|
import { INGEST_PROMPT, QUERY_PROMPT, LINT_PROMPT, } from "./templates/prompts.js";
|
|
22
|
+
import { enqueueWikiMaintenance } from "./scheduler/index.js";
|
|
23
|
+
import { WIKI_TOOL_DESCRIPTIONS, WIKI_TOOL_SNIPPETS, getWikiToolGuidelines, } from "./wiki-tool-guidance.js";
|
|
23
24
|
// -- Parameter schemas -------------------------------------------------------
|
|
24
25
|
const PARA_ENUM = StringEnum(["projects", "areas", "resources", "archives"], { description: "PARA category" });
|
|
25
26
|
const PARA_ENUM_NO_ARCHIVES = StringEnum(["projects", "areas", "resources"], { description: "PARA category (archives excluded for new content)" });
|
|
@@ -64,6 +65,16 @@ const WikiWriteParams = Type.Object({
|
|
|
64
65
|
indexContent: Type.Optional(Type.String({ description: "Updated index.md content (full replacement)" })),
|
|
65
66
|
logSummary: Type.Optional(Type.String({ description: "One-line summary for log.md" })),
|
|
66
67
|
});
|
|
68
|
+
const WikiEditParams = Type.Object({
|
|
69
|
+
path: Type.String({ description: "Existing page path (e.g. 'resources/ssl-cert-gotchas')" }),
|
|
70
|
+
edits: Type.Array(WikiEditSchema, {
|
|
71
|
+
description: "Atomic exact text replacements. Every oldText must appear exactly once or no changes are written.",
|
|
72
|
+
}),
|
|
73
|
+
title: Type.Optional(Type.String({ description: "Optional replacement page title" })),
|
|
74
|
+
scope: Type.Optional(Type.Array(Type.String(), { description: "Optional replacement scope tags" })),
|
|
75
|
+
tags: Type.Optional(Type.Array(Type.String(), { description: "Optional replacement topic tags" })),
|
|
76
|
+
logSummary: Type.Optional(Type.String({ description: "One-line summary for log.md" })),
|
|
77
|
+
});
|
|
67
78
|
const WikiReadParams = Type.Object({
|
|
68
79
|
path: Type.String({
|
|
69
80
|
description: "Page path (e.g. 'projects/auth-refactor') or page title",
|
|
@@ -189,15 +200,25 @@ function createQueryExecute(_wikiDir, store, getScope, getGraphBoost) {
|
|
|
189
200
|
};
|
|
190
201
|
};
|
|
191
202
|
}
|
|
203
|
+
// -- Background maintenance --------------------------------------------------
|
|
204
|
+
/**
|
|
205
|
+
* Schedule index rebuild + QMD reindex after latency-sensitive edits.
|
|
206
|
+
* Debounced per wiki directory and coordinated through the scheduler queue so
|
|
207
|
+
* multiple Pi processes do not run the same maintenance task concurrently.
|
|
208
|
+
*/
|
|
209
|
+
function scheduleWikiMaintenance(wikiDir, store, markDirty) {
|
|
210
|
+
enqueueWikiMaintenance(wikiDir, store, markDirty);
|
|
211
|
+
}
|
|
192
212
|
// -- Factory: wiki_write execute ---------------------------------------------
|
|
193
213
|
function createWriteExecute(wikiDir, store, getScope, markDirty) {
|
|
194
214
|
return async (params) => {
|
|
195
215
|
const pagesWritten = [];
|
|
216
|
+
const pagesSkipped = [];
|
|
196
217
|
const now = new Date().toISOString();
|
|
197
218
|
const scope = getScope();
|
|
198
|
-
// Gather all existing slugs for auto-linking
|
|
199
|
-
|
|
200
|
-
const allSlugs =
|
|
219
|
+
// Gather all existing slugs for auto-linking (slug-only readdir scan;
|
|
220
|
+
// reading/parsing every page here made wiki_write scale poorly)
|
|
221
|
+
const allSlugs = await listPageSlugs(wikiDir);
|
|
201
222
|
for (const pageSpec of params.pages) {
|
|
202
223
|
// Sanitize slug
|
|
203
224
|
const slug = pageSpec.slug
|
|
@@ -212,21 +233,38 @@ function createWriteExecute(wikiDir, store, getScope, markDirty) {
|
|
|
212
233
|
const pagePath = `${pageSpec.category}/${slug}`;
|
|
213
234
|
// Track this slug for auto-linking other pages in this batch
|
|
214
235
|
allSlugs.add(slug);
|
|
236
|
+
if (pageSpec.mode === "create" && existing) {
|
|
237
|
+
pagesSkipped.push(`${pagePath} (already exists; use mode=replace, mode=append, or wiki_edit)`);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (pageSpec.mode === "edit" && !existing) {
|
|
241
|
+
pagesSkipped.push(`${pagePath} (missing; wiki_write mode=edit requires an existing page)`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
215
244
|
if (pageSpec.mode === "edit" && existing) {
|
|
216
|
-
//
|
|
245
|
+
// Legacy edit mode: keep for compatibility, but make it atomic like wiki_edit.
|
|
217
246
|
const edits = pageSpec.edits ?? [];
|
|
218
247
|
if (edits.length === 0) {
|
|
219
248
|
continue; // no edits to apply
|
|
220
249
|
}
|
|
221
|
-
let editedBody = existing.body;
|
|
222
250
|
const errors = [];
|
|
223
251
|
for (const edit of edits) {
|
|
224
|
-
const
|
|
225
|
-
|
|
252
|
+
const first = existing.body.indexOf(edit.oldText);
|
|
253
|
+
const last = existing.body.lastIndexOf(edit.oldText);
|
|
254
|
+
if (first === -1) {
|
|
226
255
|
errors.push(`oldText not found: "${edit.oldText.slice(0, 60)}..."`);
|
|
227
|
-
continue;
|
|
228
256
|
}
|
|
229
|
-
|
|
257
|
+
else if (first !== last) {
|
|
258
|
+
errors.push(`oldText is not unique: "${edit.oldText.slice(0, 60)}..."`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (errors.length > 0) {
|
|
262
|
+
pagesSkipped.push(`${pagePath} (${errors.join("; ")})`);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
let editedBody = existing.body;
|
|
266
|
+
for (const edit of edits) {
|
|
267
|
+
editedBody = editedBody.replace(edit.oldText, edit.newText);
|
|
230
268
|
}
|
|
231
269
|
const linkedBody = redactSecrets(autoLinkSlugs(editedBody, allSlugs, slug)).text;
|
|
232
270
|
const rawScope = pageSpec.scope.length > 0 ? pageSpec.scope : existing.frontmatter.scope;
|
|
@@ -336,8 +374,12 @@ function createWriteExecute(wikiDir, store, getScope, markDirty) {
|
|
|
336
374
|
});
|
|
337
375
|
logAppended = true;
|
|
338
376
|
}
|
|
339
|
-
//
|
|
340
|
-
|
|
377
|
+
// Defer QMD BM25 reindex to the debounced background maintenance queue
|
|
378
|
+
// (same as wiki_edit). Immediate post-write queries are safe: queryWiki
|
|
379
|
+
// retries with a fresh store.update() before returning zero results.
|
|
380
|
+
if (pagesWritten.length > 0) {
|
|
381
|
+
scheduleWikiMaintenance(wikiDir, store, markDirty);
|
|
382
|
+
}
|
|
341
383
|
// Mark context cache dirty so before_agent_start rebuilds
|
|
342
384
|
markDirty();
|
|
343
385
|
// Git auto-commit
|
|
@@ -347,9 +389,12 @@ function createWriteExecute(wikiDir, store, getScope, markDirty) {
|
|
|
347
389
|
: `wiki: ${pagesWritten.join(", ")}`;
|
|
348
390
|
await gitCommit(wikiDir, commitMsg);
|
|
349
391
|
}
|
|
350
|
-
const summary =
|
|
351
|
-
|
|
352
|
-
|
|
392
|
+
const summary = [
|
|
393
|
+
pagesWritten.length > 0
|
|
394
|
+
? `Wrote ${pagesWritten.length} page(s): ${pagesWritten.join(", ")}`
|
|
395
|
+
: "No pages written.",
|
|
396
|
+
pagesSkipped.length > 0 ? `Skipped ${pagesSkipped.length} page(s): ${pagesSkipped.join(", ")}` : "",
|
|
397
|
+
].filter(Boolean).join("\n");
|
|
353
398
|
return {
|
|
354
399
|
content: [
|
|
355
400
|
{
|
|
@@ -367,14 +412,112 @@ function createWriteExecute(wikiDir, store, getScope, markDirty) {
|
|
|
367
412
|
pagesWritten,
|
|
368
413
|
indexUpdated,
|
|
369
414
|
logAppended,
|
|
415
|
+
pagesSkipped,
|
|
370
416
|
},
|
|
371
417
|
};
|
|
372
418
|
};
|
|
373
419
|
}
|
|
374
420
|
// -- Factory: wiki_read execute ----------------------------------------------
|
|
421
|
+
function createEditExecute(wikiDir, store, markDirty) {
|
|
422
|
+
return async (params) => {
|
|
423
|
+
const parsed = parsePagePath(params.path);
|
|
424
|
+
if (!parsed) {
|
|
425
|
+
return {
|
|
426
|
+
content: [{ type: "text", text: `Invalid page path: ${params.path}` }],
|
|
427
|
+
details: { pagesWritten: [], pagesSkipped: [params.path], indexUpdated: false, logAppended: false },
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const existing = await readPage(wikiDir, parsed.category, parsed.slug);
|
|
431
|
+
const pagePath = `${parsed.category}/${parsed.slug}`;
|
|
432
|
+
if (!existing) {
|
|
433
|
+
return {
|
|
434
|
+
content: [{ type: "text", text: `Page not found: ${pagePath}` }],
|
|
435
|
+
details: { pagesWritten: [], pagesSkipped: [pagePath], indexUpdated: false, logAppended: false },
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
if (params.edits.length === 0) {
|
|
439
|
+
return {
|
|
440
|
+
content: [{ type: "text", text: "No edits supplied." }],
|
|
441
|
+
details: { pagesWritten: [], pagesSkipped: [pagePath], indexUpdated: false, logAppended: false },
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
// Atomic validation: all oldText strings must exist exactly once before writing.
|
|
445
|
+
const errors = [];
|
|
446
|
+
for (const edit of params.edits) {
|
|
447
|
+
const first = existing.body.indexOf(edit.oldText);
|
|
448
|
+
const last = existing.body.lastIndexOf(edit.oldText);
|
|
449
|
+
if (first === -1) {
|
|
450
|
+
errors.push(`oldText not found: "${edit.oldText.slice(0, 60)}..."`);
|
|
451
|
+
}
|
|
452
|
+
else if (first !== last) {
|
|
453
|
+
errors.push(`oldText is not unique: "${edit.oldText.slice(0, 60)}..."`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (errors.length > 0) {
|
|
457
|
+
return {
|
|
458
|
+
content: [{ type: "text", text: `No changes written.\n${errors.join("\n")}` }],
|
|
459
|
+
details: { pagesWritten: [], pagesSkipped: [pagePath], indexUpdated: false, logAppended: false },
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
let editedBody = existing.body;
|
|
463
|
+
for (const edit of params.edits) {
|
|
464
|
+
editedBody = editedBody.replace(edit.oldText, edit.newText);
|
|
465
|
+
}
|
|
466
|
+
// Surgical edit path: do not scan/autolink/rebuild/reindex the whole wiki.
|
|
467
|
+
// wiki_edit is often called mid-task and must stay fast; broader maintenance
|
|
468
|
+
// can run via wiki_lint/rebuildIndex or on startup.
|
|
469
|
+
const linkedBody = redactSecrets(editedBody).text;
|
|
470
|
+
const now = new Date().toISOString();
|
|
471
|
+
const rawScope = params.scope ?? existing.frontmatter.scope;
|
|
472
|
+
const newScope = normalizeScopes(rawScope);
|
|
473
|
+
const rawTags = params.tags ?? existing.frontmatter.tags;
|
|
474
|
+
const updatedPage = {
|
|
475
|
+
category: parsed.category,
|
|
476
|
+
slug: parsed.slug,
|
|
477
|
+
frontmatter: {
|
|
478
|
+
...existing.frontmatter,
|
|
479
|
+
title: params.title ?? existing.frontmatter.title,
|
|
480
|
+
scope: newScope,
|
|
481
|
+
tags: normalizeTags(rawTags, newScope),
|
|
482
|
+
updated: now,
|
|
483
|
+
links: syncFrontmatterLinks(linkedBody),
|
|
484
|
+
},
|
|
485
|
+
body: linkedBody,
|
|
486
|
+
};
|
|
487
|
+
await writePage(wikiDir, updatedPage);
|
|
488
|
+
if (params.logSummary) {
|
|
489
|
+
await appendLog(wikiDir, {
|
|
490
|
+
date: now.split("T")[0],
|
|
491
|
+
operation: "edit",
|
|
492
|
+
summary: params.logSummary,
|
|
493
|
+
pages: [pagePath],
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
markDirty();
|
|
497
|
+
await gitCommit(wikiDir, params.logSummary ?? `wiki_edit: ${pagePath}`);
|
|
498
|
+
scheduleWikiMaintenance(wikiDir, store, markDirty);
|
|
499
|
+
return {
|
|
500
|
+
content: [{ type: "text", text: `Edited ${pagePath}. Scheduled background index/search refresh.` }],
|
|
501
|
+
details: {
|
|
502
|
+
pagesWritten: [pagePath],
|
|
503
|
+
pagesSkipped: [],
|
|
504
|
+
indexUpdated: false,
|
|
505
|
+
logAppended: !!params.logSummary,
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
};
|
|
509
|
+
}
|
|
375
510
|
function createReadExecute(wikiDir) {
|
|
376
511
|
return async (params) => {
|
|
377
512
|
const now = Date.now();
|
|
513
|
+
const normalizedPath = params.path.trim().toLowerCase().replace(/^\/+/, "");
|
|
514
|
+
if (normalizedPath === "index" || normalizedPath === "index.md" || normalizedPath === "wiki-index") {
|
|
515
|
+
const index = await readIndex(wikiDir);
|
|
516
|
+
return {
|
|
517
|
+
content: [{ type: "text", text: index }],
|
|
518
|
+
details: { found: true, path: "index.md" },
|
|
519
|
+
};
|
|
520
|
+
}
|
|
378
521
|
// Try parsing as category/slug path
|
|
379
522
|
const parsed = parsePagePath(params.path);
|
|
380
523
|
if (parsed) {
|
|
@@ -457,9 +600,10 @@ function createMoveExecute(wikiDir, store, markDirty) {
|
|
|
457
600
|
path: `${parsed.category}/${parsed.slug}.md`,
|
|
458
601
|
};
|
|
459
602
|
await movePage(wikiDir, ref, params.to);
|
|
460
|
-
//
|
|
461
|
-
|
|
603
|
+
// Rebuild index.md inline (fast, single-pass); defer QMD reindex to the
|
|
604
|
+
// debounced background maintenance queue like wiki_write/wiki_edit.
|
|
462
605
|
await rebuildIndex(wikiDir);
|
|
606
|
+
scheduleWikiMaintenance(wikiDir, store, markDirty);
|
|
463
607
|
markDirty();
|
|
464
608
|
// Git commit
|
|
465
609
|
await gitCommit(wikiDir, `move: ${page.frontmatter.title} → ${params.to}`);
|
|
@@ -588,9 +732,11 @@ function createSummarizeExecute(wikiDir, getScope) {
|
|
|
588
732
|
details: { target: params.target, pageCount: 0 },
|
|
589
733
|
};
|
|
590
734
|
}
|
|
591
|
-
// Generate overview prompt for the LLM
|
|
592
|
-
|
|
593
|
-
const
|
|
735
|
+
// Generate a bounded overview prompt for the LLM. For large targets like
|
|
736
|
+
// `all`, brief mode must not dump every full page body into model context.
|
|
737
|
+
const depth = params.depth ?? "brief";
|
|
738
|
+
const overviewPrompt = generateOverviewPrompt(pages, scope, { depth });
|
|
739
|
+
const depthNote = depth === "detailed"
|
|
594
740
|
? "\nProvide a DETAILED summary with full Key Facts, Insights, and Connections for each theme."
|
|
595
741
|
: "\nProvide a BRIEF overview — one paragraph per theme, focus on high-level patterns.";
|
|
596
742
|
return {
|
|
@@ -647,13 +793,14 @@ function createMigrateExecute(wikiDir) {
|
|
|
647
793
|
/**
|
|
648
794
|
* Register all wiki tools with the pi extension API.
|
|
649
795
|
*
|
|
650
|
-
* Tools: wiki_ingest, wiki_query, wiki_write, wiki_read,
|
|
796
|
+
* Tools: wiki_ingest, wiki_query, wiki_write, wiki_edit, wiki_read,
|
|
651
797
|
* wiki_move, wiki_lint, wiki_summarize
|
|
652
798
|
*/
|
|
653
799
|
export function registerTools(pi, wikiDir, store, getScope, markDirty, getGraphBoost) {
|
|
654
800
|
const ingestExec = createIngestExecute(wikiDir, store, getScope);
|
|
655
801
|
const queryExec = createQueryExecute(wikiDir, store, getScope, getGraphBoost);
|
|
656
802
|
const writeExec = createWriteExecute(wikiDir, store, getScope, markDirty);
|
|
803
|
+
const editExec = createEditExecute(wikiDir, store, markDirty);
|
|
657
804
|
const readExec = createReadExecute(wikiDir);
|
|
658
805
|
const moveExec = createMoveExecute(wikiDir, store, markDirty);
|
|
659
806
|
const lintExec = createLintExecute(wikiDir);
|
|
@@ -663,13 +810,9 @@ export function registerTools(pi, wikiDir, store, getScope, markDirty, getGraphB
|
|
|
663
810
|
pi.registerTool({
|
|
664
811
|
name: "wiki_ingest",
|
|
665
812
|
label: "Wiki Ingest",
|
|
666
|
-
description:
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
promptSnippet: "Ingest a URL, file, or text into the PARA wiki knowledge base",
|
|
670
|
-
promptGuidelines: [
|
|
671
|
-
"Use wiki_ingest when the user provides a URL, file path, or text to add to the knowledge base.",
|
|
672
|
-
],
|
|
813
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_ingest,
|
|
814
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_ingest,
|
|
815
|
+
promptGuidelines: getWikiToolGuidelines("wiki_ingest"),
|
|
673
816
|
parameters: WikiIngestParams,
|
|
674
817
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
675
818
|
ctx.ui.setStatus("pi-para", "wiki: ingesting...");
|
|
@@ -709,15 +852,9 @@ export function registerTools(pi, wikiDir, store, getScope, markDirty, getGraphB
|
|
|
709
852
|
pi.registerTool({
|
|
710
853
|
name: "wiki_query",
|
|
711
854
|
label: "Wiki Query",
|
|
712
|
-
description:
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
promptSnippet: "Search the PARA wiki for relevant knowledge pages",
|
|
716
|
-
promptGuidelines: [
|
|
717
|
-
"Use wiki_query BEFORE answering questions that might have relevant context in the wiki — architecture decisions, past debugging solutions, project conventions, or domain knowledge.",
|
|
718
|
-
"Use wiki_query when the user asks about something you previously discussed or captured in the wiki.",
|
|
719
|
-
"Wiki results include freshness indicators (FRESH/AGING/STALE/VERY STALE). When a page is AGING or STALE and makes claims about code, files, configs, ports, or APIs — verify against the actual source before trusting. If you find the wiki is wrong, fix it immediately with wiki_write(mode: 'edit').",
|
|
720
|
-
],
|
|
855
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_query,
|
|
856
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_query,
|
|
857
|
+
promptGuidelines: getWikiToolGuidelines("wiki_query"),
|
|
721
858
|
parameters: WikiQueryParams,
|
|
722
859
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
723
860
|
ctx.ui.setStatus("pi-para", "wiki: searching...");
|
|
@@ -747,20 +884,44 @@ export function registerTools(pi, wikiDir, store, getScope, markDirty, getGraphB
|
|
|
747
884
|
theme.fg("dim", ` scope: ${details.scopeUsed}`), 0, 0);
|
|
748
885
|
},
|
|
749
886
|
});
|
|
887
|
+
// -- wiki_edit --
|
|
888
|
+
pi.registerTool({
|
|
889
|
+
name: "wiki_edit",
|
|
890
|
+
label: "Wiki Edit",
|
|
891
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_edit,
|
|
892
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_edit,
|
|
893
|
+
promptGuidelines: getWikiToolGuidelines("wiki_edit"),
|
|
894
|
+
parameters: WikiEditParams,
|
|
895
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
896
|
+
ctx.ui.setStatus("pi-para", "wiki: editing...");
|
|
897
|
+
try {
|
|
898
|
+
return await editExec(params);
|
|
899
|
+
}
|
|
900
|
+
finally {
|
|
901
|
+
ctx.ui.setStatus("pi-para", undefined);
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
renderCall(args, theme) {
|
|
905
|
+
return new Text(theme.fg("toolTitle", theme.bold("wiki_edit ")) +
|
|
906
|
+
theme.fg("muted", args.path ?? "") +
|
|
907
|
+
theme.fg("dim", ` (${args.edits?.length ?? 0} edit(s))`), 0, 0);
|
|
908
|
+
},
|
|
909
|
+
renderResult(result, _options, theme) {
|
|
910
|
+
const details = result.details;
|
|
911
|
+
if (details?.pagesWritten?.length) {
|
|
912
|
+
return new Text(theme.fg("success", `Edited ${details.pagesWritten.join(", ")}`), 0, 0);
|
|
913
|
+
}
|
|
914
|
+
const t = result.content[0];
|
|
915
|
+
return new Text(theme.fg("warning", t?.type === "text" ? t.text : "No changes"), 0, 0);
|
|
916
|
+
},
|
|
917
|
+
});
|
|
750
918
|
// -- wiki_write --
|
|
751
919
|
pi.registerTool({
|
|
752
920
|
name: "wiki_write",
|
|
753
921
|
label: "Wiki Write",
|
|
754
|
-
description:
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
promptSnippet: "Write/update PARA wiki pages with frontmatter and markdown body. Use mode=edit with edits[{oldText,newText}] to surgically update sections.",
|
|
758
|
-
promptGuidelines: [
|
|
759
|
-
"Use wiki_write after wiki_ingest to create/update pages based on the ingested content.",
|
|
760
|
-
"Use wiki_write to save valuable answers or synthesis back into the wiki when they add new knowledge.",
|
|
761
|
-
"After making significant architectural decisions, solving debugging problems, establishing project conventions, or completing substantial implementation work, use wiki_write to persist the knowledge without being asked.",
|
|
762
|
-
"Prefer mode=edit with edits[{oldText,newText}] to update specific sections of existing pages. Use mode=append to add new sections. Only use mode=replace when rewriting the entire page.",
|
|
763
|
-
],
|
|
922
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_write,
|
|
923
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_write,
|
|
924
|
+
promptGuidelines: getWikiToolGuidelines("wiki_write"),
|
|
764
925
|
parameters: WikiWriteParams,
|
|
765
926
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
766
927
|
ctx.ui.setStatus("pi-para", "wiki: writing...");
|
|
@@ -795,6 +956,9 @@ export function registerTools(pi, wikiDir, store, getScope, markDirty, getGraphB
|
|
|
795
956
|
parts.push(theme.fg("success", `Wrote ${details.pagesWritten.length} page(s): `) +
|
|
796
957
|
theme.fg("muted", details.pagesWritten.join(", ")));
|
|
797
958
|
}
|
|
959
|
+
if (details.pagesSkipped?.length) {
|
|
960
|
+
parts.push(theme.fg("warning", `Skipped ${details.pagesSkipped.length} page(s): `) + theme.fg("muted", details.pagesSkipped.join(", ")));
|
|
961
|
+
}
|
|
798
962
|
if (details.indexUpdated)
|
|
799
963
|
parts.push(theme.fg("dim", "Updated index.md"));
|
|
800
964
|
if (details.logAppended)
|
|
@@ -806,12 +970,9 @@ export function registerTools(pi, wikiDir, store, getScope, markDirty, getGraphB
|
|
|
806
970
|
pi.registerTool({
|
|
807
971
|
name: "wiki_read",
|
|
808
972
|
label: "Wiki Read",
|
|
809
|
-
description:
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
promptGuidelines: [
|
|
813
|
-
"wiki_read results include a freshness indicator (FRESH/AGING/STALE/VERY STALE). When a page is AGING or STALE, verify claims about code, configs, or APIs against the actual source before trusting. Fix incorrect wiki pages with wiki_write(mode: 'edit').",
|
|
814
|
-
],
|
|
973
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_read,
|
|
974
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_read,
|
|
975
|
+
promptGuidelines: getWikiToolGuidelines("wiki_read"),
|
|
815
976
|
parameters: WikiReadParams,
|
|
816
977
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
817
978
|
ctx.ui.setStatus("pi-para", "wiki: reading...");
|
|
@@ -838,9 +999,9 @@ export function registerTools(pi, wikiDir, store, getScope, markDirty, getGraphB
|
|
|
838
999
|
pi.registerTool({
|
|
839
1000
|
name: "wiki_move",
|
|
840
1001
|
label: "Wiki Move",
|
|
841
|
-
description:
|
|
842
|
-
|
|
843
|
-
|
|
1002
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_move,
|
|
1003
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_move,
|
|
1004
|
+
promptGuidelines: getWikiToolGuidelines("wiki_move"),
|
|
844
1005
|
parameters: WikiMoveParams,
|
|
845
1006
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
846
1007
|
ctx.ui.setStatus("pi-para", "wiki: moving...");
|
|
@@ -869,10 +1030,9 @@ export function registerTools(pi, wikiDir, store, getScope, markDirty, getGraphB
|
|
|
869
1030
|
pi.registerTool({
|
|
870
1031
|
name: "wiki_lint",
|
|
871
1032
|
label: "Wiki Lint",
|
|
872
|
-
description:
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
promptSnippet: "Run wiki health checks and auto-fix issues",
|
|
1033
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_lint,
|
|
1034
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_lint,
|
|
1035
|
+
promptGuidelines: getWikiToolGuidelines("wiki_lint"),
|
|
876
1036
|
parameters: WikiLintParams,
|
|
877
1037
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
878
1038
|
ctx.ui.setStatus("pi-para", "wiki: linting...");
|
|
@@ -912,9 +1072,9 @@ export function registerTools(pi, wikiDir, store, getScope, markDirty, getGraphB
|
|
|
912
1072
|
pi.registerTool({
|
|
913
1073
|
name: "wiki_migrate",
|
|
914
1074
|
label: "Wiki Migrate",
|
|
915
|
-
description:
|
|
916
|
-
|
|
917
|
-
|
|
1075
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_migrate,
|
|
1076
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_migrate,
|
|
1077
|
+
promptGuidelines: getWikiToolGuidelines("wiki_migrate"),
|
|
918
1078
|
parameters: WikiMigrateParams,
|
|
919
1079
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
920
1080
|
ctx.ui.setStatus("pi-para", "wiki: migrating...");
|
|
@@ -944,11 +1104,9 @@ export function registerTools(pi, wikiDir, store, getScope, markDirty, getGraphB
|
|
|
944
1104
|
pi.registerTool({
|
|
945
1105
|
name: "wiki_summarize",
|
|
946
1106
|
label: "Wiki Summarize",
|
|
947
|
-
description:
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
"Returns content with a summary prompt for you to synthesize.",
|
|
951
|
-
promptSnippet: "Summarize wiki pages, categories, or the entire wiki",
|
|
1107
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_summarize,
|
|
1108
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_summarize,
|
|
1109
|
+
promptGuidelines: getWikiToolGuidelines("wiki_summarize"),
|
|
952
1110
|
parameters: WikiSummarizeParams,
|
|
953
1111
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
954
1112
|
ctx.ui.setStatus("pi-para", "wiki: summarizing...");
|
|
@@ -983,44 +1141,51 @@ export function registerTools(pi, wikiDir, store, getScope, markDirty, getGraphB
|
|
|
983
1141
|
/**
|
|
984
1142
|
* Create standalone AgentTool instances for the mini-agent (capture/lint/summarize).
|
|
985
1143
|
* Returns only the tools needed for autonomous wiki operations:
|
|
986
|
-
* wiki_write, wiki_read, wiki_query, wiki_move.
|
|
1144
|
+
* wiki_write, wiki_edit, wiki_read, wiki_query, wiki_move.
|
|
987
1145
|
*/
|
|
988
1146
|
export function createStandaloneTools(wikiDir, store, getScope, markDirty) {
|
|
989
1147
|
const noopDirty = markDirty ?? (() => { });
|
|
990
1148
|
const writeExec = createWriteExecute(wikiDir, store, getScope, noopDirty);
|
|
1149
|
+
const editExec = createEditExecute(wikiDir, store, noopDirty);
|
|
991
1150
|
const readExec = createReadExecute(wikiDir);
|
|
992
1151
|
const queryExec = createQueryExecute(wikiDir, store, getScope);
|
|
993
1152
|
const moveExec = createMoveExecute(wikiDir, store, noopDirty);
|
|
994
1153
|
const wikiWrite = {
|
|
995
1154
|
name: "wiki_write",
|
|
996
1155
|
label: "Wiki Write",
|
|
997
|
-
description:
|
|
998
|
-
"Optionally update index.md and append to log.md.",
|
|
1156
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_write,
|
|
999
1157
|
parameters: WikiWriteParams,
|
|
1000
1158
|
execute: (_toolCallId, params) => writeExec(params),
|
|
1001
1159
|
};
|
|
1160
|
+
const wikiEdit = {
|
|
1161
|
+
name: "wiki_edit",
|
|
1162
|
+
label: "Wiki Edit",
|
|
1163
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_edit,
|
|
1164
|
+
parameters: WikiEditParams,
|
|
1165
|
+
execute: (_toolCallId, params) => editExec(params),
|
|
1166
|
+
};
|
|
1002
1167
|
const wikiRead = {
|
|
1003
1168
|
name: "wiki_read",
|
|
1004
1169
|
label: "Wiki Read",
|
|
1005
|
-
description:
|
|
1170
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_read,
|
|
1006
1171
|
parameters: WikiReadParams,
|
|
1007
1172
|
execute: (_toolCallId, params) => readExec(params),
|
|
1008
1173
|
};
|
|
1009
1174
|
const wikiQuery = {
|
|
1010
1175
|
name: "wiki_query",
|
|
1011
1176
|
label: "Wiki Query",
|
|
1012
|
-
description:
|
|
1177
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_query,
|
|
1013
1178
|
parameters: WikiQueryParams,
|
|
1014
1179
|
execute: (_toolCallId, params) => queryExec(params),
|
|
1015
1180
|
};
|
|
1016
1181
|
const wikiMove = {
|
|
1017
1182
|
name: "wiki_move",
|
|
1018
1183
|
label: "Wiki Move",
|
|
1019
|
-
description:
|
|
1184
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_move,
|
|
1020
1185
|
parameters: WikiMoveParams,
|
|
1021
1186
|
execute: (_toolCallId, params) => moveExec(params),
|
|
1022
1187
|
};
|
|
1023
|
-
return [wikiWrite, wikiRead, wikiQuery, wikiMove];
|
|
1188
|
+
return [wikiWrite, wikiEdit, wikiRead, wikiQuery, wikiMove];
|
|
1024
1189
|
}
|
|
1025
1190
|
// -- Utilities ---------------------------------------------------------------
|
|
1026
1191
|
function formatBytes(bytes) {
|