pi-para 0.5.2 → 0.6.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/ARCHITECTURE.md +160 -0
- package/CHANGELOG.md +51 -0
- package/CONFIGURATION.md +243 -0
- package/README.md +140 -237
- package/SETUP.md +196 -112
- package/STRUCTURE.md +140 -0
- package/package.json +86 -18
- package/scripts/build-assets.mjs +29 -0
- package/scripts/install.ps1 +55 -0
- package/scripts/install.sh +63 -0
- package/scripts/smoke-install.mjs +63 -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 +209 -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/capture.d.ts +0 -34
- package/dist/capture.d.ts.map +0 -1
- package/dist/capture.js +0 -461
- package/dist/capture.js.map +0 -1
- package/dist/cli.d.ts +0 -15
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -405
- package/dist/cli.js.map +0 -1
- package/dist/commands.d.ts +0 -15
- package/dist/commands.d.ts.map +0 -1
- package/dist/commands.js +0 -786
- package/dist/commands.js.map +0 -1
- package/dist/context.d.ts +0 -61
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -289
- package/dist/context.js.map +0 -1
- package/dist/daemon.d.ts +0 -49
- package/dist/daemon.d.ts.map +0 -1
- package/dist/daemon.js +0 -226
- package/dist/daemon.js.map +0 -1
- package/dist/frontmatter.d.ts +0 -59
- package/dist/frontmatter.d.ts.map +0 -1
- package/dist/frontmatter.js +0 -170
- package/dist/frontmatter.js.map +0 -1
- package/dist/gepa/dataset.d.ts +0 -36
- package/dist/gepa/dataset.d.ts.map +0 -1
- package/dist/gepa/dataset.js +0 -148
- package/dist/gepa/dataset.js.map +0 -1
- package/dist/gepa/evaluator.d.ts +0 -51
- package/dist/gepa/evaluator.d.ts.map +0 -1
- package/dist/gepa/evaluator.js +0 -317
- package/dist/gepa/evaluator.js.map +0 -1
- package/dist/gepa/index.d.ts +0 -66
- package/dist/gepa/index.d.ts.map +0 -1
- package/dist/gepa/index.js +0 -240
- package/dist/gepa/index.js.map +0 -1
- package/dist/gepa/llm.d.ts +0 -35
- package/dist/gepa/llm.d.ts.map +0 -1
- package/dist/gepa/llm.js +0 -268
- package/dist/gepa/llm.js.map +0 -1
- package/dist/gepa/optimized/capture-prompt.txt +0 -65
- package/dist/gepa/optimizer.d.ts +0 -29
- package/dist/gepa/optimizer.d.ts.map +0 -1
- package/dist/gepa/optimizer.js +0 -249
- package/dist/gepa/optimizer.js.map +0 -1
- package/dist/gepa/targets.d.ts +0 -39
- package/dist/gepa/targets.d.ts.map +0 -1
- package/dist/gepa/targets.js +0 -326
- package/dist/gepa/targets.js.map +0 -1
- package/dist/gepa/types.d.ts +0 -168
- package/dist/gepa/types.d.ts.map +0 -1
- package/dist/gepa/types.js +0 -124
- package/dist/gepa/types.js.map +0 -1
- package/dist/index.d.ts +0 -21
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -294
- package/dist/index.js.map +0 -1
- package/dist/ingest.d.ts +0 -88
- package/dist/ingest.d.ts.map +0 -1
- package/dist/ingest.js +0 -105
- package/dist/ingest.js.map +0 -1
- package/dist/link-discovery.d.ts +0 -40
- package/dist/link-discovery.d.ts.map +0 -1
- package/dist/link-discovery.js +0 -122
- package/dist/link-discovery.js.map +0 -1
- package/dist/link-utils.d.ts +0 -30
- package/dist/link-utils.d.ts.map +0 -1
- package/dist/link-utils.js +0 -145
- package/dist/link-utils.js.map +0 -1
- package/dist/lint.d.ts +0 -39
- package/dist/lint.d.ts.map +0 -1
- package/dist/lint.js +0 -827
- package/dist/lint.js.map +0 -1
- package/dist/maintainer.d.ts +0 -24
- package/dist/maintainer.d.ts.map +0 -1
- package/dist/maintainer.js +0 -358
- package/dist/maintainer.js.map +0 -1
- package/dist/processor.d.ts +0 -14
- package/dist/processor.d.ts.map +0 -1
- package/dist/processor.js +0 -361
- package/dist/processor.js.map +0 -1
- package/dist/query.d.ts +0 -69
- package/dist/query.d.ts.map +0 -1
- package/dist/query.js +0 -107
- package/dist/query.js.map +0 -1
- package/dist/raw.d.ts +0 -57
- package/dist/raw.d.ts.map +0 -1
- package/dist/raw.js +0 -219
- package/dist/raw.js.map +0 -1
- package/dist/redact.d.ts +0 -21
- package/dist/redact.d.ts.map +0 -1
- package/dist/redact.js +0 -143
- package/dist/redact.js.map +0 -1
- package/dist/scope.d.ts +0 -54
- package/dist/scope.d.ts.map +0 -1
- package/dist/scope.js +0 -161
- package/dist/scope.js.map +0 -1
- package/dist/scripts/bulk-link-fix.d.ts +0 -9
- package/dist/scripts/bulk-link-fix.d.ts.map +0 -1
- package/dist/scripts/bulk-link-fix.js +0 -106
- package/dist/scripts/bulk-link-fix.js.map +0 -1
- package/dist/session-tools.d.ts +0 -33
- package/dist/session-tools.d.ts.map +0 -1
- package/dist/session-tools.js +0 -211
- package/dist/session-tools.js.map +0 -1
- package/dist/state.d.ts +0 -54
- package/dist/state.d.ts.map +0 -1
- package/dist/state.js +0 -176
- package/dist/state.js.map +0 -1
- package/dist/store.d.ts +0 -56
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js +0 -227
- package/dist/store.js.map +0 -1
- package/dist/summarize.d.ts +0 -56
- package/dist/summarize.d.ts.map +0 -1
- package/dist/summarize.js +0 -231
- package/dist/summarize.js.map +0 -1
- package/dist/tag-registry.d.ts +0 -56
- package/dist/tag-registry.d.ts.map +0 -1
- package/dist/tag-registry.js +0 -146
- package/dist/tag-registry.js.map +0 -1
- package/dist/templates/prompts.d.ts +0 -63
- package/dist/templates/prompts.d.ts.map +0 -1
- package/dist/templates/prompts.js +0 -260
- package/dist/templates/prompts.js.map +0 -1
- package/dist/tools.d.ts +0 -26
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -1033
- package/dist/tools.js.map +0 -1
- package/dist/watcher.d.ts +0 -25
- package/dist/watcher.d.ts.map +0 -1
- package/dist/watcher.js +0 -84
- package/dist/watcher.js.map +0 -1
- package/dist/webui/client/dist/index.html +0 -1885
- package/dist/webui/server.d.ts +0 -31
- package/dist/webui/server.d.ts.map +0 -1
- package/dist/webui/server.js +0 -566
- package/dist/webui/server.js.map +0 -1
- package/dist/wiki.d.ts +0 -103
- package/dist/wiki.d.ts.map +0 -1
- package/dist/wiki.js +0 -428
- package/dist/wiki.js.map +0 -1
- package/scripts/gepa/__pycache__/dataset.cpython-313.pyc +0 -0
- package/scripts/gepa/__pycache__/lm_providers.cpython-313.pyc +0 -0
- package/scripts/gepa/__pycache__/metric.cpython-313.pyc +0 -0
- package/scripts/gepa/__pycache__/program.cpython-313.pyc +0 -0
- package/scripts/gepa/__pycache__/wiki_reader.cpython-313.pyc +0 -0
- package/scripts/gepa/uv.lock +0 -2241
package/src/tools.ts
CHANGED
|
@@ -7,22 +7,22 @@
|
|
|
7
7
|
* Factory functions (createXxxExecute) close over wikiDir and store — no ctx dependency.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { StringEnum } from "@
|
|
11
|
-
import type { AgentTool, AgentToolResult } from "@
|
|
12
|
-
import type { ExtensionAPI } from "@
|
|
13
|
-
import { Text } from "@
|
|
10
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
11
|
+
import type { AgentTool, AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
12
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
14
14
|
import type { QMDStore } from "qmd-engine";
|
|
15
15
|
import { Type } from "typebox";
|
|
16
16
|
|
|
17
17
|
import type { ProjectScope } from "./scope.js";
|
|
18
18
|
import { resolveSource, truncateSource, detectSourceType } from "./ingest.js";
|
|
19
19
|
import { queryWiki as queryWikiLib, formatQueryResults, formatFreshness } from "./query.js";
|
|
20
|
-
import { reindex } from "./store.js";
|
|
21
20
|
import {
|
|
22
21
|
readPage,
|
|
23
22
|
writePage,
|
|
24
23
|
movePage,
|
|
25
24
|
listPages,
|
|
25
|
+
listPageSlugs,
|
|
26
26
|
readIndex,
|
|
27
27
|
writeIndex,
|
|
28
28
|
readSchema,
|
|
@@ -53,6 +53,12 @@ import {
|
|
|
53
53
|
QUERY_PROMPT,
|
|
54
54
|
LINT_PROMPT,
|
|
55
55
|
} from "./templates/prompts.js";
|
|
56
|
+
import { enqueueWikiMaintenance } from "./scheduler/index.js";
|
|
57
|
+
import {
|
|
58
|
+
WIKI_TOOL_DESCRIPTIONS,
|
|
59
|
+
WIKI_TOOL_SNIPPETS,
|
|
60
|
+
getWikiToolGuidelines,
|
|
61
|
+
} from "./wiki-tool-guidance.js";
|
|
56
62
|
|
|
57
63
|
// -- Parameter schemas -------------------------------------------------------
|
|
58
64
|
|
|
@@ -124,6 +130,17 @@ const WikiWriteParams = Type.Object({
|
|
|
124
130
|
),
|
|
125
131
|
});
|
|
126
132
|
|
|
133
|
+
const WikiEditParams = Type.Object({
|
|
134
|
+
path: Type.String({ description: "Existing page path (e.g. 'resources/ssl-cert-gotchas')" }),
|
|
135
|
+
edits: Type.Array(WikiEditSchema, {
|
|
136
|
+
description: "Atomic exact text replacements. Every oldText must appear exactly once or no changes are written.",
|
|
137
|
+
}),
|
|
138
|
+
title: Type.Optional(Type.String({ description: "Optional replacement page title" })),
|
|
139
|
+
scope: Type.Optional(Type.Array(Type.String(), { description: "Optional replacement scope tags" })),
|
|
140
|
+
tags: Type.Optional(Type.Array(Type.String(), { description: "Optional replacement topic tags" })),
|
|
141
|
+
logSummary: Type.Optional(Type.String({ description: "One-line summary for log.md" })),
|
|
142
|
+
});
|
|
143
|
+
|
|
127
144
|
const WikiReadParams = Type.Object({
|
|
128
145
|
path: Type.String({
|
|
129
146
|
description: "Page path (e.g. 'projects/auth-refactor') or page title",
|
|
@@ -173,6 +190,7 @@ interface WikiWriteDetails {
|
|
|
173
190
|
pagesWritten: string[];
|
|
174
191
|
indexUpdated: boolean;
|
|
175
192
|
logAppended: boolean;
|
|
193
|
+
pagesSkipped?: string[];
|
|
176
194
|
}
|
|
177
195
|
|
|
178
196
|
interface WikiReadDetails {
|
|
@@ -325,6 +343,21 @@ function createQueryExecute(
|
|
|
325
343
|
};
|
|
326
344
|
}
|
|
327
345
|
|
|
346
|
+
// -- Background maintenance --------------------------------------------------
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Schedule index rebuild + QMD reindex after latency-sensitive edits.
|
|
350
|
+
* Debounced per wiki directory and coordinated through the scheduler queue so
|
|
351
|
+
* multiple Pi processes do not run the same maintenance task concurrently.
|
|
352
|
+
*/
|
|
353
|
+
function scheduleWikiMaintenance(
|
|
354
|
+
wikiDir: string,
|
|
355
|
+
store: QMDStore,
|
|
356
|
+
markDirty: () => void,
|
|
357
|
+
): void {
|
|
358
|
+
enqueueWikiMaintenance(wikiDir, store, markDirty);
|
|
359
|
+
}
|
|
360
|
+
|
|
328
361
|
// -- Factory: wiki_write execute ---------------------------------------------
|
|
329
362
|
|
|
330
363
|
function createWriteExecute(
|
|
@@ -350,12 +383,13 @@ function createWriteExecute(
|
|
|
350
383
|
},
|
|
351
384
|
): Promise<AgentToolResult<WikiWriteDetails>> => {
|
|
352
385
|
const pagesWritten: string[] = [];
|
|
386
|
+
const pagesSkipped: string[] = [];
|
|
353
387
|
const now = new Date().toISOString();
|
|
354
388
|
const scope = getScope();
|
|
355
389
|
|
|
356
|
-
// Gather all existing slugs for auto-linking
|
|
357
|
-
|
|
358
|
-
const allSlugs =
|
|
390
|
+
// Gather all existing slugs for auto-linking (slug-only readdir scan;
|
|
391
|
+
// reading/parsing every page here made wiki_write scale poorly)
|
|
392
|
+
const allSlugs = await listPageSlugs(wikiDir);
|
|
359
393
|
|
|
360
394
|
for (const pageSpec of params.pages) {
|
|
361
395
|
// Sanitize slug
|
|
@@ -375,21 +409,39 @@ function createWriteExecute(
|
|
|
375
409
|
// Track this slug for auto-linking other pages in this batch
|
|
376
410
|
allSlugs.add(slug);
|
|
377
411
|
|
|
412
|
+
if (pageSpec.mode === "create" && existing) {
|
|
413
|
+
pagesSkipped.push(`${pagePath} (already exists; use mode=replace, mode=append, or wiki_edit)`);
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (pageSpec.mode === "edit" && !existing) {
|
|
418
|
+
pagesSkipped.push(`${pagePath} (missing; wiki_write mode=edit requires an existing page)`);
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
|
|
378
422
|
if (pageSpec.mode === "edit" && existing) {
|
|
379
|
-
//
|
|
423
|
+
// Legacy edit mode: keep for compatibility, but make it atomic like wiki_edit.
|
|
380
424
|
const edits = pageSpec.edits ?? [];
|
|
381
425
|
if (edits.length === 0) {
|
|
382
426
|
continue; // no edits to apply
|
|
383
427
|
}
|
|
384
|
-
let editedBody = existing.body;
|
|
385
428
|
const errors: string[] = [];
|
|
386
429
|
for (const edit of edits) {
|
|
387
|
-
const
|
|
388
|
-
|
|
430
|
+
const first = existing.body.indexOf(edit.oldText);
|
|
431
|
+
const last = existing.body.lastIndexOf(edit.oldText);
|
|
432
|
+
if (first === -1) {
|
|
389
433
|
errors.push(`oldText not found: "${edit.oldText.slice(0, 60)}..."`);
|
|
390
|
-
|
|
434
|
+
} else if (first !== last) {
|
|
435
|
+
errors.push(`oldText is not unique: "${edit.oldText.slice(0, 60)}..."`);
|
|
391
436
|
}
|
|
392
|
-
|
|
437
|
+
}
|
|
438
|
+
if (errors.length > 0) {
|
|
439
|
+
pagesSkipped.push(`${pagePath} (${errors.join("; ")})`);
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
let editedBody = existing.body;
|
|
443
|
+
for (const edit of edits) {
|
|
444
|
+
editedBody = editedBody.replace(edit.oldText, edit.newText);
|
|
393
445
|
}
|
|
394
446
|
const linkedBody = redactSecrets(autoLinkSlugs(editedBody, allSlugs, slug)).text;
|
|
395
447
|
const rawScope = pageSpec.scope.length > 0 ? pageSpec.scope : existing.frontmatter.scope;
|
|
@@ -500,8 +552,12 @@ function createWriteExecute(
|
|
|
500
552
|
logAppended = true;
|
|
501
553
|
}
|
|
502
554
|
|
|
503
|
-
//
|
|
504
|
-
|
|
555
|
+
// Defer QMD BM25 reindex to the debounced background maintenance queue
|
|
556
|
+
// (same as wiki_edit). Immediate post-write queries are safe: queryWiki
|
|
557
|
+
// retries with a fresh store.update() before returning zero results.
|
|
558
|
+
if (pagesWritten.length > 0) {
|
|
559
|
+
scheduleWikiMaintenance(wikiDir, store, markDirty);
|
|
560
|
+
}
|
|
505
561
|
|
|
506
562
|
// Mark context cache dirty so before_agent_start rebuilds
|
|
507
563
|
markDirty();
|
|
@@ -514,9 +570,12 @@ function createWriteExecute(
|
|
|
514
570
|
await gitCommit(wikiDir, commitMsg);
|
|
515
571
|
}
|
|
516
572
|
|
|
517
|
-
const summary =
|
|
518
|
-
|
|
519
|
-
|
|
573
|
+
const summary = [
|
|
574
|
+
pagesWritten.length > 0
|
|
575
|
+
? `Wrote ${pagesWritten.length} page(s): ${pagesWritten.join(", ")}`
|
|
576
|
+
: "No pages written.",
|
|
577
|
+
pagesSkipped.length > 0 ? `Skipped ${pagesSkipped.length} page(s): ${pagesSkipped.join(", ")}` : "",
|
|
578
|
+
].filter(Boolean).join("\n");
|
|
520
579
|
|
|
521
580
|
return {
|
|
522
581
|
content: [
|
|
@@ -535,6 +594,7 @@ function createWriteExecute(
|
|
|
535
594
|
pagesWritten,
|
|
536
595
|
indexUpdated,
|
|
537
596
|
logAppended,
|
|
597
|
+
pagesSkipped,
|
|
538
598
|
},
|
|
539
599
|
};
|
|
540
600
|
};
|
|
@@ -542,11 +602,129 @@ function createWriteExecute(
|
|
|
542
602
|
|
|
543
603
|
// -- Factory: wiki_read execute ----------------------------------------------
|
|
544
604
|
|
|
605
|
+
function createEditExecute(
|
|
606
|
+
wikiDir: string,
|
|
607
|
+
store: QMDStore,
|
|
608
|
+
markDirty: () => void,
|
|
609
|
+
) {
|
|
610
|
+
return async (
|
|
611
|
+
params: {
|
|
612
|
+
path: string;
|
|
613
|
+
edits: Array<{ oldText: string; newText: string }>;
|
|
614
|
+
title?: string;
|
|
615
|
+
scope?: string[];
|
|
616
|
+
tags?: string[];
|
|
617
|
+
logSummary?: string;
|
|
618
|
+
},
|
|
619
|
+
): Promise<AgentToolResult<WikiWriteDetails>> => {
|
|
620
|
+
const parsed = parsePagePath(params.path);
|
|
621
|
+
if (!parsed) {
|
|
622
|
+
return {
|
|
623
|
+
content: [{ type: "text", text: `Invalid page path: ${params.path}` }],
|
|
624
|
+
details: { pagesWritten: [], pagesSkipped: [params.path], indexUpdated: false, logAppended: false },
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const existing = await readPage(wikiDir, parsed.category, parsed.slug);
|
|
629
|
+
const pagePath = `${parsed.category}/${parsed.slug}`;
|
|
630
|
+
if (!existing) {
|
|
631
|
+
return {
|
|
632
|
+
content: [{ type: "text", text: `Page not found: ${pagePath}` }],
|
|
633
|
+
details: { pagesWritten: [], pagesSkipped: [pagePath], indexUpdated: false, logAppended: false },
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (params.edits.length === 0) {
|
|
638
|
+
return {
|
|
639
|
+
content: [{ type: "text", text: "No edits supplied." }],
|
|
640
|
+
details: { pagesWritten: [], pagesSkipped: [pagePath], indexUpdated: false, logAppended: false },
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Atomic validation: all oldText strings must exist exactly once before writing.
|
|
645
|
+
const errors: string[] = [];
|
|
646
|
+
for (const edit of params.edits) {
|
|
647
|
+
const first = existing.body.indexOf(edit.oldText);
|
|
648
|
+
const last = existing.body.lastIndexOf(edit.oldText);
|
|
649
|
+
if (first === -1) {
|
|
650
|
+
errors.push(`oldText not found: "${edit.oldText.slice(0, 60)}..."`);
|
|
651
|
+
} else if (first !== last) {
|
|
652
|
+
errors.push(`oldText is not unique: "${edit.oldText.slice(0, 60)}..."`);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (errors.length > 0) {
|
|
656
|
+
return {
|
|
657
|
+
content: [{ type: "text", text: `No changes written.\n${errors.join("\n")}` }],
|
|
658
|
+
details: { pagesWritten: [], pagesSkipped: [pagePath], indexUpdated: false, logAppended: false },
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
let editedBody = existing.body;
|
|
663
|
+
for (const edit of params.edits) {
|
|
664
|
+
editedBody = editedBody.replace(edit.oldText, edit.newText);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Surgical edit path: do not scan/autolink/rebuild/reindex the whole wiki.
|
|
668
|
+
// wiki_edit is often called mid-task and must stay fast; broader maintenance
|
|
669
|
+
// can run via wiki_lint/rebuildIndex or on startup.
|
|
670
|
+
const linkedBody = redactSecrets(editedBody).text;
|
|
671
|
+
const now = new Date().toISOString();
|
|
672
|
+
const rawScope = params.scope ?? existing.frontmatter.scope;
|
|
673
|
+
const newScope = normalizeScopes(rawScope);
|
|
674
|
+
const rawTags = params.tags ?? existing.frontmatter.tags;
|
|
675
|
+
const updatedPage: WikiPage = {
|
|
676
|
+
category: parsed.category,
|
|
677
|
+
slug: parsed.slug,
|
|
678
|
+
frontmatter: {
|
|
679
|
+
...existing.frontmatter,
|
|
680
|
+
title: params.title ?? existing.frontmatter.title,
|
|
681
|
+
scope: newScope,
|
|
682
|
+
tags: normalizeTags(rawTags, newScope),
|
|
683
|
+
updated: now,
|
|
684
|
+
links: syncFrontmatterLinks(linkedBody),
|
|
685
|
+
},
|
|
686
|
+
body: linkedBody,
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
await writePage(wikiDir, updatedPage);
|
|
690
|
+
if (params.logSummary) {
|
|
691
|
+
await appendLog(wikiDir, {
|
|
692
|
+
date: now.split("T")[0],
|
|
693
|
+
operation: "edit",
|
|
694
|
+
summary: params.logSummary,
|
|
695
|
+
pages: [pagePath],
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
markDirty();
|
|
699
|
+
await gitCommit(wikiDir, params.logSummary ?? `wiki_edit: ${pagePath}`);
|
|
700
|
+
scheduleWikiMaintenance(wikiDir, store, markDirty);
|
|
701
|
+
|
|
702
|
+
return {
|
|
703
|
+
content: [{ type: "text", text: `Edited ${pagePath}. Scheduled background index/search refresh.` }],
|
|
704
|
+
details: {
|
|
705
|
+
pagesWritten: [pagePath],
|
|
706
|
+
pagesSkipped: [],
|
|
707
|
+
indexUpdated: false,
|
|
708
|
+
logAppended: !!params.logSummary,
|
|
709
|
+
},
|
|
710
|
+
};
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
545
714
|
function createReadExecute(wikiDir: string) {
|
|
546
715
|
return async (
|
|
547
716
|
params: { path: string },
|
|
548
717
|
): Promise<AgentToolResult<WikiReadDetails>> => {
|
|
549
718
|
const now = Date.now();
|
|
719
|
+
const normalizedPath = params.path.trim().toLowerCase().replace(/^\/+/, "");
|
|
720
|
+
|
|
721
|
+
if (normalizedPath === "index" || normalizedPath === "index.md" || normalizedPath === "wiki-index") {
|
|
722
|
+
const index = await readIndex(wikiDir);
|
|
723
|
+
return {
|
|
724
|
+
content: [{ type: "text", text: index }],
|
|
725
|
+
details: { found: true, path: "index.md" },
|
|
726
|
+
};
|
|
727
|
+
}
|
|
550
728
|
|
|
551
729
|
// Try parsing as category/slug path
|
|
552
730
|
const parsed = parsePagePath(params.path);
|
|
@@ -647,9 +825,10 @@ function createMoveExecute(
|
|
|
647
825
|
|
|
648
826
|
await movePage(wikiDir, ref, params.to);
|
|
649
827
|
|
|
650
|
-
//
|
|
651
|
-
|
|
828
|
+
// Rebuild index.md inline (fast, single-pass); defer QMD reindex to the
|
|
829
|
+
// debounced background maintenance queue like wiki_write/wiki_edit.
|
|
652
830
|
await rebuildIndex(wikiDir);
|
|
831
|
+
scheduleWikiMaintenance(wikiDir, store, markDirty);
|
|
653
832
|
markDirty();
|
|
654
833
|
|
|
655
834
|
// Git commit
|
|
@@ -794,10 +973,12 @@ function createSummarizeExecute(
|
|
|
794
973
|
};
|
|
795
974
|
}
|
|
796
975
|
|
|
797
|
-
// Generate overview prompt for the LLM
|
|
798
|
-
|
|
976
|
+
// Generate a bounded overview prompt for the LLM. For large targets like
|
|
977
|
+
// `all`, brief mode must not dump every full page body into model context.
|
|
978
|
+
const depth = params.depth ?? "brief";
|
|
979
|
+
const overviewPrompt = generateOverviewPrompt(pages, scope, { depth });
|
|
799
980
|
const depthNote =
|
|
800
|
-
|
|
981
|
+
depth === "detailed"
|
|
801
982
|
? "\nProvide a DETAILED summary with full Key Facts, Insights, and Connections for each theme."
|
|
802
983
|
: "\nProvide a BRIEF overview — one paragraph per theme, focus on high-level patterns.";
|
|
803
984
|
|
|
@@ -866,7 +1047,7 @@ function createMigrateExecute(
|
|
|
866
1047
|
/**
|
|
867
1048
|
* Register all wiki tools with the pi extension API.
|
|
868
1049
|
*
|
|
869
|
-
* Tools: wiki_ingest, wiki_query, wiki_write, wiki_read,
|
|
1050
|
+
* Tools: wiki_ingest, wiki_query, wiki_write, wiki_edit, wiki_read,
|
|
870
1051
|
* wiki_move, wiki_lint, wiki_summarize
|
|
871
1052
|
*/
|
|
872
1053
|
export function registerTools(
|
|
@@ -880,6 +1061,7 @@ export function registerTools(
|
|
|
880
1061
|
const ingestExec = createIngestExecute(wikiDir, store, getScope);
|
|
881
1062
|
const queryExec = createQueryExecute(wikiDir, store, getScope, getGraphBoost);
|
|
882
1063
|
const writeExec = createWriteExecute(wikiDir, store, getScope, markDirty);
|
|
1064
|
+
const editExec = createEditExecute(wikiDir, store, markDirty);
|
|
883
1065
|
const readExec = createReadExecute(wikiDir);
|
|
884
1066
|
const moveExec = createMoveExecute(wikiDir, store, markDirty);
|
|
885
1067
|
const lintExec = createLintExecute(wikiDir);
|
|
@@ -890,14 +1072,9 @@ export function registerTools(
|
|
|
890
1072
|
pi.registerTool({
|
|
891
1073
|
name: "wiki_ingest",
|
|
892
1074
|
label: "Wiki Ingest",
|
|
893
|
-
description:
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
"so you can synthesize wiki pages via wiki_write.",
|
|
897
|
-
promptSnippet: "Ingest a URL, file, or text into the PARA wiki knowledge base",
|
|
898
|
-
promptGuidelines: [
|
|
899
|
-
"Use wiki_ingest when the user provides a URL, file path, or text to add to the knowledge base.",
|
|
900
|
-
],
|
|
1075
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_ingest,
|
|
1076
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_ingest,
|
|
1077
|
+
promptGuidelines: getWikiToolGuidelines("wiki_ingest"),
|
|
901
1078
|
parameters: WikiIngestParams,
|
|
902
1079
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
903
1080
|
ctx.ui.setStatus("pi-para", "wiki: ingesting...");
|
|
@@ -938,16 +1115,9 @@ export function registerTools(
|
|
|
938
1115
|
pi.registerTool({
|
|
939
1116
|
name: "wiki_query",
|
|
940
1117
|
label: "Wiki Query",
|
|
941
|
-
description:
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
"Use global=true to search across all project scopes.",
|
|
945
|
-
promptSnippet: "Search the PARA wiki for relevant knowledge pages",
|
|
946
|
-
promptGuidelines: [
|
|
947
|
-
"Use wiki_query BEFORE answering questions that might have relevant context in the wiki — architecture decisions, past debugging solutions, project conventions, or domain knowledge.",
|
|
948
|
-
"Use wiki_query when the user asks about something you previously discussed or captured in the wiki.",
|
|
949
|
-
"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').",
|
|
950
|
-
],
|
|
1118
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_query,
|
|
1119
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_query,
|
|
1120
|
+
promptGuidelines: getWikiToolGuidelines("wiki_query"),
|
|
951
1121
|
parameters: WikiQueryParams,
|
|
952
1122
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
953
1123
|
ctx.ui.setStatus("pi-para", "wiki: searching...");
|
|
@@ -977,21 +1147,44 @@ export function registerTools(
|
|
|
977
1147
|
},
|
|
978
1148
|
});
|
|
979
1149
|
|
|
1150
|
+
// -- wiki_edit --
|
|
1151
|
+
pi.registerTool({
|
|
1152
|
+
name: "wiki_edit",
|
|
1153
|
+
label: "Wiki Edit",
|
|
1154
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_edit,
|
|
1155
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_edit,
|
|
1156
|
+
promptGuidelines: getWikiToolGuidelines("wiki_edit"),
|
|
1157
|
+
parameters: WikiEditParams,
|
|
1158
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
1159
|
+
ctx.ui.setStatus("pi-para", "wiki: editing...");
|
|
1160
|
+
try { return await editExec(params); } finally { ctx.ui.setStatus("pi-para", undefined); }
|
|
1161
|
+
},
|
|
1162
|
+
renderCall(args, theme) {
|
|
1163
|
+
return new Text(
|
|
1164
|
+
theme.fg("toolTitle", theme.bold("wiki_edit ")) +
|
|
1165
|
+
theme.fg("muted", args.path ?? "") +
|
|
1166
|
+
theme.fg("dim", ` (${args.edits?.length ?? 0} edit(s))`),
|
|
1167
|
+
0,
|
|
1168
|
+
0,
|
|
1169
|
+
);
|
|
1170
|
+
},
|
|
1171
|
+
renderResult(result, _options, theme) {
|
|
1172
|
+
const details = result.details as WikiWriteDetails | undefined;
|
|
1173
|
+
if (details?.pagesWritten?.length) {
|
|
1174
|
+
return new Text(theme.fg("success", `Edited ${details.pagesWritten.join(", ")}`), 0, 0);
|
|
1175
|
+
}
|
|
1176
|
+
const t = result.content[0];
|
|
1177
|
+
return new Text(theme.fg("warning", t?.type === "text" ? t.text : "No changes"), 0, 0);
|
|
1178
|
+
},
|
|
1179
|
+
});
|
|
1180
|
+
|
|
980
1181
|
// -- wiki_write --
|
|
981
1182
|
pi.registerTool({
|
|
982
1183
|
name: "wiki_write",
|
|
983
1184
|
label: "Wiki Write",
|
|
984
|
-
description:
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
"Pages are automatically re-indexed for search after writing.",
|
|
988
|
-
promptSnippet: "Write/update PARA wiki pages with frontmatter and markdown body. Use mode=edit with edits[{oldText,newText}] to surgically update sections.",
|
|
989
|
-
promptGuidelines: [
|
|
990
|
-
"Use wiki_write after wiki_ingest to create/update pages based on the ingested content.",
|
|
991
|
-
"Use wiki_write to save valuable answers or synthesis back into the wiki when they add new knowledge.",
|
|
992
|
-
"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.",
|
|
993
|
-
"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.",
|
|
994
|
-
],
|
|
1185
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_write,
|
|
1186
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_write,
|
|
1187
|
+
promptGuidelines: getWikiToolGuidelines("wiki_write"),
|
|
995
1188
|
parameters: WikiWriteParams,
|
|
996
1189
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
997
1190
|
ctx.ui.setStatus("pi-para", "wiki: writing...");
|
|
@@ -1023,6 +1216,9 @@ export function registerTools(
|
|
|
1023
1216
|
theme.fg("muted", details.pagesWritten.join(", ")),
|
|
1024
1217
|
);
|
|
1025
1218
|
}
|
|
1219
|
+
if (details.pagesSkipped?.length) {
|
|
1220
|
+
parts.push(theme.fg("warning", `Skipped ${details.pagesSkipped.length} page(s): `) + theme.fg("muted", details.pagesSkipped.join(", ")));
|
|
1221
|
+
}
|
|
1026
1222
|
if (details.indexUpdated) parts.push(theme.fg("dim", "Updated index.md"));
|
|
1027
1223
|
if (details.logAppended) parts.push(theme.fg("dim", "Logged operation"));
|
|
1028
1224
|
return new Text(parts.join("\n") || theme.fg("dim", "No changes"), 0, 0);
|
|
@@ -1033,13 +1229,9 @@ export function registerTools(
|
|
|
1033
1229
|
pi.registerTool({
|
|
1034
1230
|
name: "wiki_read",
|
|
1035
1231
|
label: "Wiki Read",
|
|
1036
|
-
description:
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
promptSnippet: "Read a wiki page by path or title. Includes freshness indicator — verify STALE pages against actual code before trusting.",
|
|
1040
|
-
promptGuidelines: [
|
|
1041
|
-
"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').",
|
|
1042
|
-
],
|
|
1232
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_read,
|
|
1233
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_read,
|
|
1234
|
+
promptGuidelines: getWikiToolGuidelines("wiki_read"),
|
|
1043
1235
|
parameters: WikiReadParams,
|
|
1044
1236
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
1045
1237
|
ctx.ui.setStatus("pi-para", "wiki: reading...");
|
|
@@ -1070,10 +1262,9 @@ export function registerTools(
|
|
|
1070
1262
|
pi.registerTool({
|
|
1071
1263
|
name: "wiki_move",
|
|
1072
1264
|
label: "Wiki Move",
|
|
1073
|
-
description:
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
promptSnippet: "Move a wiki page between PARA categories",
|
|
1265
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_move,
|
|
1266
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_move,
|
|
1267
|
+
promptGuidelines: getWikiToolGuidelines("wiki_move"),
|
|
1077
1268
|
parameters: WikiMoveParams,
|
|
1078
1269
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
1079
1270
|
ctx.ui.setStatus("pi-para", "wiki: moving...");
|
|
@@ -1106,11 +1297,9 @@ export function registerTools(
|
|
|
1106
1297
|
pi.registerTool({
|
|
1107
1298
|
name: "wiki_lint",
|
|
1108
1299
|
label: "Wiki Lint",
|
|
1109
|
-
description:
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
"index drift, and duplicate slugs. Auto-fixes simple issues by default.",
|
|
1113
|
-
promptSnippet: "Run wiki health checks and auto-fix issues",
|
|
1300
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_lint,
|
|
1301
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_lint,
|
|
1302
|
+
promptGuidelines: getWikiToolGuidelines("wiki_lint"),
|
|
1114
1303
|
parameters: WikiLintParams,
|
|
1115
1304
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
1116
1305
|
ctx.ui.setStatus("pi-para", "wiki: linting...");
|
|
@@ -1149,10 +1338,9 @@ export function registerTools(
|
|
|
1149
1338
|
pi.registerTool({
|
|
1150
1339
|
name: "wiki_migrate",
|
|
1151
1340
|
label: "Wiki Migrate",
|
|
1152
|
-
description:
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
promptSnippet: "Batch-migrate all wiki pages to the current schema version",
|
|
1341
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_migrate,
|
|
1342
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_migrate,
|
|
1343
|
+
promptGuidelines: getWikiToolGuidelines("wiki_migrate"),
|
|
1156
1344
|
parameters: WikiMigrateParams,
|
|
1157
1345
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
1158
1346
|
ctx.ui.setStatus("pi-para", "wiki: migrating...");
|
|
@@ -1190,12 +1378,9 @@ export function registerTools(
|
|
|
1190
1378
|
pi.registerTool({
|
|
1191
1379
|
name: "wiki_summarize",
|
|
1192
1380
|
label: "Wiki Summarize",
|
|
1193
|
-
description:
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
"a category name (e.g. 'projects'), or 'all'. " +
|
|
1197
|
-
"Returns content with a summary prompt for you to synthesize.",
|
|
1198
|
-
promptSnippet: "Summarize wiki pages, categories, or the entire wiki",
|
|
1381
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_summarize,
|
|
1382
|
+
promptSnippet: WIKI_TOOL_SNIPPETS.wiki_summarize,
|
|
1383
|
+
promptGuidelines: getWikiToolGuidelines("wiki_summarize"),
|
|
1199
1384
|
parameters: WikiSummarizeParams,
|
|
1200
1385
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
1201
1386
|
ctx.ui.setStatus("pi-para", "wiki: summarizing...");
|
|
@@ -1231,7 +1416,7 @@ export function registerTools(
|
|
|
1231
1416
|
/**
|
|
1232
1417
|
* Create standalone AgentTool instances for the mini-agent (capture/lint/summarize).
|
|
1233
1418
|
* Returns only the tools needed for autonomous wiki operations:
|
|
1234
|
-
* wiki_write, wiki_read, wiki_query, wiki_move.
|
|
1419
|
+
* wiki_write, wiki_edit, wiki_read, wiki_query, wiki_move.
|
|
1235
1420
|
*/
|
|
1236
1421
|
export function createStandaloneTools(
|
|
1237
1422
|
wikiDir: string,
|
|
@@ -1241,6 +1426,7 @@ export function createStandaloneTools(
|
|
|
1241
1426
|
): AgentTool[] {
|
|
1242
1427
|
const noopDirty = markDirty ?? (() => {});
|
|
1243
1428
|
const writeExec = createWriteExecute(wikiDir, store, getScope, noopDirty);
|
|
1429
|
+
const editExec = createEditExecute(wikiDir, store, noopDirty);
|
|
1244
1430
|
const readExec = createReadExecute(wikiDir);
|
|
1245
1431
|
const queryExec = createQueryExecute(wikiDir, store, getScope);
|
|
1246
1432
|
const moveExec = createMoveExecute(wikiDir, store, noopDirty);
|
|
@@ -1248,18 +1434,23 @@ export function createStandaloneTools(
|
|
|
1248
1434
|
const wikiWrite: AgentTool<typeof WikiWriteParams> = {
|
|
1249
1435
|
name: "wiki_write",
|
|
1250
1436
|
label: "Wiki Write",
|
|
1251
|
-
description:
|
|
1252
|
-
"Write or update wiki pages. Supports create, replace, and append modes. " +
|
|
1253
|
-
"Optionally update index.md and append to log.md.",
|
|
1437
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_write,
|
|
1254
1438
|
parameters: WikiWriteParams,
|
|
1255
1439
|
execute: (_toolCallId, params) => writeExec(params),
|
|
1256
1440
|
};
|
|
1257
1441
|
|
|
1442
|
+
const wikiEdit: AgentTool<typeof WikiEditParams> = {
|
|
1443
|
+
name: "wiki_edit",
|
|
1444
|
+
label: "Wiki Edit",
|
|
1445
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_edit,
|
|
1446
|
+
parameters: WikiEditParams,
|
|
1447
|
+
execute: (_toolCallId, params) => editExec(params),
|
|
1448
|
+
};
|
|
1449
|
+
|
|
1258
1450
|
const wikiRead: AgentTool<typeof WikiReadParams> = {
|
|
1259
1451
|
name: "wiki_read",
|
|
1260
1452
|
label: "Wiki Read",
|
|
1261
|
-
description:
|
|
1262
|
-
"Read a specific wiki page by path (e.g. 'projects/auth-refactor') or by title.",
|
|
1453
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_read,
|
|
1263
1454
|
parameters: WikiReadParams,
|
|
1264
1455
|
execute: (_toolCallId, params) => readExec(params),
|
|
1265
1456
|
};
|
|
@@ -1267,8 +1458,7 @@ export function createStandaloneTools(
|
|
|
1267
1458
|
const wikiQuery: AgentTool<typeof WikiQueryParams> = {
|
|
1268
1459
|
name: "wiki_query",
|
|
1269
1460
|
label: "Wiki Query",
|
|
1270
|
-
description:
|
|
1271
|
-
"Search the wiki knowledge base with a natural language query. Returns relevant pages.",
|
|
1461
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_query,
|
|
1272
1462
|
parameters: WikiQueryParams,
|
|
1273
1463
|
execute: (_toolCallId, params) => queryExec(params),
|
|
1274
1464
|
};
|
|
@@ -1276,12 +1466,12 @@ export function createStandaloneTools(
|
|
|
1276
1466
|
const wikiMove: AgentTool<typeof WikiMoveParams> = {
|
|
1277
1467
|
name: "wiki_move",
|
|
1278
1468
|
label: "Wiki Move",
|
|
1279
|
-
description:
|
|
1469
|
+
description: WIKI_TOOL_DESCRIPTIONS.wiki_move,
|
|
1280
1470
|
parameters: WikiMoveParams,
|
|
1281
1471
|
execute: (_toolCallId, params) => moveExec(params),
|
|
1282
1472
|
};
|
|
1283
1473
|
|
|
1284
|
-
return [wikiWrite, wikiRead, wikiQuery, wikiMove];
|
|
1474
|
+
return [wikiWrite, wikiEdit, wikiRead, wikiQuery, wikiMove];
|
|
1285
1475
|
}
|
|
1286
1476
|
|
|
1287
1477
|
// -- Utilities ---------------------------------------------------------------
|