@zosmaai/pi-llm-wiki 0.9.1 → 0.9.3

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.
@@ -513,7 +513,8 @@ export function registerWikiEnsurePage(pi: ExtensionAPI, runtime?: Runtime): voi
513
513
  ],
514
514
  parameters: Type.Object({
515
515
  type: Type.String({
516
- description: "Page type: entity | concept | synthesis | analysis | requirement",
516
+ description:
517
+ "Page type: entity | concept | synthesis | analysis | requirement | skill | case",
517
518
  }),
518
519
  title: Type.String({ description: "Page title" }),
519
520
  content: Type.Optional(
@@ -531,7 +532,14 @@ export function registerWikiEnsurePage(pi: ExtensionAPI, runtime?: Runtime): voi
531
532
  };
532
533
  }
533
534
 
534
- const type = params.type as "entity" | "concept" | "synthesis" | "analysis";
535
+ const type = params.type as
536
+ | "entity"
537
+ | "concept"
538
+ | "synthesis"
539
+ | "analysis"
540
+ | "requirement"
541
+ | "skill"
542
+ | "case";
535
543
  const slug = params.title
536
544
  .toLowerCase()
537
545
  .replace(/[^a-z0-9\s-]/g, "")
@@ -545,6 +553,8 @@ export function registerWikiEnsurePage(pi: ExtensionAPI, runtime?: Runtime): voi
545
553
  synthesis: "syntheses",
546
554
  analysis: "analyses",
547
555
  requirement: "requirements",
556
+ skill: "skills",
557
+ case: "cases",
548
558
  };
549
559
  const folder = folderMap[type] || "concepts";
550
560
  const pagePath = join(paths.wiki, folder, `${slug}.md`);
@@ -622,6 +632,76 @@ function buildPageTemplate(
622
632
  "Durable answer from a query.\n\n## Question\n\n[Original question]",
623
633
  );
624
634
  }
635
+ if (type === "skill") {
636
+ return [
637
+ "---",
638
+ "type: skill",
639
+ `created: ${date}`,
640
+ `updated: ${date}`,
641
+ "status: draft",
642
+ "trajectories: []",
643
+ "tags: []",
644
+ "---",
645
+ "",
646
+ `# ${title}`,
647
+ "",
648
+ "_One-line summary of the reusable pattern this skill captures._",
649
+ "",
650
+ "## When to Use",
651
+ "",
652
+ "[Trigger conditions — when this pattern applies]",
653
+ "",
654
+ "## Procedure",
655
+ "",
656
+ "1. [Step 1]",
657
+ "2. [Step 2]",
658
+ "",
659
+ "## Pitfalls",
660
+ "",
661
+ "- [Known failure mode or caveat]",
662
+ "",
663
+ "## Distilled From",
664
+ "",
665
+ "_Trajectories this skill was generalized from._",
666
+ "",
667
+ "- [[trajectories/TRJ-...]]",
668
+ "",
669
+ ].join("\n");
670
+ }
671
+ if (type === "case") {
672
+ return [
673
+ "---",
674
+ "type: case",
675
+ `created: ${date}`,
676
+ `updated: ${date}`,
677
+ "status: draft",
678
+ "outcome: success",
679
+ "trajectory_id: ",
680
+ "tags: []",
681
+ "---",
682
+ "",
683
+ `# ${title}`,
684
+ "",
685
+ "_One-line summary of the specific task this case records._",
686
+ "",
687
+ "## Task",
688
+ "",
689
+ "[What was requested]",
690
+ "",
691
+ "## Approach",
692
+ "",
693
+ "[How the agent solved it — key steps and decisions]",
694
+ "",
695
+ "## Outcome",
696
+ "",
697
+ "[Result, and anything worth reusing or avoiding next time]",
698
+ "",
699
+ "## Trajectory",
700
+ "",
701
+ "- [[trajectories/TRJ-...]] — captured tool-call run",
702
+ "",
703
+ ].join("\n");
704
+ }
625
705
  if (type === "requirement") {
626
706
  return [
627
707
  "---",
@@ -1117,10 +1197,12 @@ export function registerWikiWatch(pi: ExtensionAPI): void {
1117
1197
  pi.registerTool({
1118
1198
  name: "wiki_watch",
1119
1199
  label: "Wiki Watch",
1120
- description: "Schedule automatic wiki updates (discover → ingest → lint) via pi's cron system.",
1200
+ description:
1201
+ "Print a ready-to-paste crontab line for scheduling automatic wiki updates (discover → ingest → lint). Does NOT schedule anything itself — it returns the command for the user to install.",
1121
1202
  promptSnippet: "Schedule auto-updates for the wiki",
1122
1203
  promptGuidelines: [
1123
1204
  "Use wiki_watch when the user wants the wiki to stay current automatically.",
1205
+ "wiki_watch only PRINTS a cron line — surface the output to the user verbatim so they can install it. Do not claim the schedule is active.",
1124
1206
  ],
1125
1207
  parameters: Type.Object({
1126
1208
  interval: Type.String({ description: "daily, weekly, hourly, or stop" }),
@@ -1132,15 +1214,16 @@ export function registerWikiWatch(pi: ExtensionAPI): void {
1132
1214
  {
1133
1215
  type: "text",
1134
1216
  text: [
1135
- "🛑 To stop wiki auto-updates:",
1217
+ "🛑 To stop wiki auto-updates, remove the cron line you installed earlier:",
1136
1218
  "",
1219
+ "```bash",
1220
+ "crontab -e # then delete the line tagged '# llm-wiki-autoupdate'",
1137
1221
  "```",
1138
- "schedule_prompt action=list",
1139
- "```",
1140
- "Find the wiki job IDs, then:",
1141
1222
  "",
1142
- "```",
1143
- "schedule_prompt action=remove jobId=<id>",
1223
+ "Or list current jobs to confirm:",
1224
+ "",
1225
+ "```bash",
1226
+ "crontab -l | grep llm-wiki-autoupdate",
1144
1227
  "```",
1145
1228
  ].join("\n"),
1146
1229
  },
@@ -1149,10 +1232,11 @@ export function registerWikiWatch(pi: ExtensionAPI): void {
1149
1232
  };
1150
1233
  }
1151
1234
 
1235
+ // 5-field POSIX crontab expressions (min hour dom month dow).
1152
1236
  const intervals: Record<string, { cron: string; label: string }> = {
1153
- daily: { cron: "0 0 8 * * *", label: "Daily at 8:00 AM" },
1154
- weekly: { cron: "0 0 9 * * 1", label: "Weekly on Monday at 9:00 AM" },
1155
- hourly: { cron: "0 0 * * * *", label: "Every hour" },
1237
+ daily: { cron: "0 8 * * *", label: "Daily at 8:00 AM" },
1238
+ weekly: { cron: "0 9 * * 1", label: "Weekly on Monday at 9:00 AM" },
1239
+ hourly: { cron: "0 * * * *", label: "Every hour" },
1156
1240
  };
1157
1241
 
1158
1242
  const config = intervals[params.interval];
@@ -1169,18 +1253,37 @@ export function registerWikiWatch(pi: ExtensionAPI): void {
1169
1253
  };
1170
1254
  }
1171
1255
 
1256
+ // Robustness for global crontab environments:
1257
+ // * `/bin/bash -lc` runs a LOGIN shell that sources /etc/profile +
1258
+ // ~/.profile / ~/.bash_profile, so npm-global / bun / nvm PATH
1259
+ // additions are imported — cron's default PATH is only
1260
+ // `/usr/bin:/bin` and would not find `pi`.
1261
+ // * `mkdir -p` makes the log dir self-healing for users with only
1262
+ // a project vault (no `~/.llm-wiki/` yet).
1263
+ // * All `$HOME` references are double-quoted to survive paths with spaces.
1264
+ // * `# llm-wiki-autoupdate` tags the line so the user can find and
1265
+ // remove it via `crontab -e` later (see `interval=stop`).
1266
+ const cronLine = `${config.cron} /bin/bash -lc 'mkdir -p "$HOME/.llm-wiki" && pi -p "/wiki-run" >> "$HOME/.llm-wiki/cron.log" 2>&1' # llm-wiki-autoupdate`;
1267
+
1172
1268
  return {
1173
1269
  content: [
1174
1270
  {
1175
1271
  type: "text",
1176
1272
  text: [
1177
- `⏰ To set up ${config.label} wiki updates, run:`,
1273
+ `⏰ To set up ${config.label} wiki updates, add this line to your crontab.`,
1274
+ "**This tool only prints the line — it does not install it.**",
1178
1275
  "",
1276
+ "```bash",
1277
+ "crontab -e",
1179
1278
  "```",
1180
- `schedule_prompt action=add schedule="${config.cron}" prompt="Run /wiki:run for the LLM Wiki" name="llm-wiki-autoupdate"`,
1279
+ "",
1280
+ "Then append:",
1281
+ "",
1282
+ "```cron",
1283
+ cronLine,
1181
1284
  "```",
1182
1285
  "",
1183
- "This will auto-discover, ingest, and lint on schedule.",
1286
+ `The line uses \`/bin/bash -lc\` so your shell profile (and the \`pi\` binary on npm-global / bun PATH) is loaded. Output goes to \`~/.llm-wiki/cron.log\`. If your system has no \`/bin/bash\`, replace with \`/bin/sh -c\` and ensure \`pi\` is in cron's PATH yourself.`,
1184
1287
  ].join("\n"),
1185
1288
  },
1186
1289
  ],
@@ -1188,6 +1291,8 @@ export function registerWikiWatch(pi: ExtensionAPI): void {
1188
1291
  interval: params.interval,
1189
1292
  cronSchedule: config.cron,
1190
1293
  label: config.label,
1294
+ cronLine,
1295
+ installed: false,
1191
1296
  } as Record<string, unknown>,
1192
1297
  };
1193
1298
  },
@@ -0,0 +1,67 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import { loadTaskConfig, persistTrajectoriesEnabled, trajectoriesEnabled } from "./task-config.js";
3
+
4
+ /**
5
+ * Activation surface for agent-trajectory working-memory (issue #80).
6
+ *
7
+ * The feature is opt-in / default-off. The trajectory tools
8
+ * (`wiki_capture_trajectory`, `wiki_distill_skills`, `wiki_recall_skill`) are
9
+ * registered conditionally at extension-load time (see index.ts) based on the
10
+ * `llm-wiki.trajectories` setting. Because pi has no runtime add/remove-tool
11
+ * API — the extension factory runs once at load — flipping the flag can only
12
+ * change the registered tool set by RELOADING extensions. This command does
13
+ * exactly that: persist the setting, then `ctx.reload()` so the gate
14
+ * re-evaluates and the tools appear (or disappear) immediately.
15
+ *
16
+ * /wiki-trajectories → show current state
17
+ * /wiki-trajectories on → enable + reload
18
+ * /wiki-trajectories off → disable + reload
19
+ */
20
+
21
+ const ON_WORDS = new Set(["on", "true", "enable", "enabled", "yes", "1"]);
22
+ const OFF_WORDS = new Set(["off", "false", "disable", "disabled", "no", "0"]);
23
+
24
+ export function registerWikiTrajectoriesCommand(pi: ExtensionAPI): void {
25
+ pi.registerCommand("wiki-trajectories", {
26
+ description:
27
+ "Enable or disable agent-trajectory working-memory (capture/distill/recall). Off by default.",
28
+ handler: async (args, ctx) => {
29
+ const arg = args.trim().toLowerCase();
30
+ const current = trajectoriesEnabled(loadTaskConfig(ctx.cwd));
31
+
32
+ if (!arg) {
33
+ ctx.ui.notify(
34
+ `LLM Wiki trajectories are ${current ? "ON" : "OFF"}. Use \`/wiki-trajectories on\` or \`off\`.`,
35
+ "info",
36
+ );
37
+ return;
38
+ }
39
+
40
+ let next: boolean;
41
+ if (ON_WORDS.has(arg)) next = true;
42
+ else if (OFF_WORDS.has(arg)) next = false;
43
+ else {
44
+ ctx.ui.notify(
45
+ `LLM Wiki: could not parse "${args.trim()}". Use \`on\` or \`off\`.`,
46
+ "error",
47
+ );
48
+ return;
49
+ }
50
+
51
+ if (next === current) {
52
+ ctx.ui.notify(`LLM Wiki trajectories already ${current ? "ON" : "OFF"}.`, "info");
53
+ return;
54
+ }
55
+
56
+ persistTrajectoriesEnabled(ctx.cwd, next);
57
+ ctx.ui.notify(
58
+ `LLM Wiki: trajectory tools ${next ? "enabled" : "disabled"} — reloading extensions…`,
59
+ "info",
60
+ );
61
+ // pi has no runtime register/unregister-tool API: reload re-runs the
62
+ // extension factory so the conditional registration re-evaluates and the
63
+ // tool set (and status line) reflect the new state.
64
+ await ctx.reload();
65
+ },
66
+ });
67
+ }