@zosmaai/pi-llm-wiki 0.9.0 → 0.9.2

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.
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { getAgentDir } from "@mariozechner/pi-coding-agent";
4
4
 
@@ -69,10 +69,58 @@ export interface TaskConfig {
69
69
  * previews inline. Clamped to a non-negative integer.
70
70
  */
71
71
  recallLinksThreshold?: number;
72
+
73
+ /**
74
+ * Max characters of a distilled `skill`/`case` body inlined directly into a
75
+ * recall block before truncation (recall-adherence fix). Skills/cases are
76
+ * meant to be APPLIED immediately, so links-first recall inlines their short
77
+ * body instead of a bare link the agent often skips. Set to 0 to DISABLE
78
+ * inlining entirely — skills/cases then fall back to the normal link/preview
79
+ * path (pure links-first), and no page body is read at format time. Only
80
+ * relevant when the trajectories feature is on (skill/case pages exist only
81
+ * then). Default 1600. Clamped to a non-negative integer. Mirrors the
82
+ * `recallLinksThreshold` knob — the other context-window lever for recall.
83
+ */
84
+ recallSkillInlineMax?: number;
85
+
86
+ /**
87
+ * Surface wiki activity in the UI (issue #77). When enabled (the default),
88
+ * the status line reflects recall hits and the periodic observe/retro
89
+ * reminder is shown to the user (`display: true`) instead of being injected
90
+ * silently. Set to `false` to restore the previous quiet behavior — a static
91
+ * status line and a hidden (`display: false`) reminder — for users who do
92
+ * not want any chat-level wiki notices.
93
+ */
94
+ notices?: boolean;
95
+
96
+ /**
97
+ * Agent-trajectory working-memory (capture → distill → recall), issue #80.
98
+ * OPT-IN, default OFF: only an explicit `trajectories: true` enables it.
99
+ * When off, the trajectory tools are never registered (see index.ts), so
100
+ * they cost nothing in the system prompt for the ~95% who don't use them.
101
+ */
102
+ trajectories?: boolean;
72
103
  }
73
104
 
74
105
  export const TASK_DEFAULTS: TaskConfig = {};
75
106
 
107
+ /**
108
+ * Resolve whether user-facing wiki notices are enabled (issue #77). Defaults
109
+ * to `true`; only an explicit `notices: false` disables them.
110
+ */
111
+ export function noticesEnabled(config: TaskConfig | undefined): boolean {
112
+ return config?.notices !== false;
113
+ }
114
+
115
+ /**
116
+ * Resolve whether agent-trajectory working-memory is enabled (issue #80).
117
+ * INVERSE polarity of `noticesEnabled`: defaults to `false`; only an explicit
118
+ * `trajectories: true` turns it on.
119
+ */
120
+ export function trajectoriesEnabled(config: TaskConfig | undefined): boolean {
121
+ return config?.trajectories === true;
122
+ }
123
+
76
124
  const SETTINGS_KEY = "llm-wiki";
77
125
 
78
126
  function readModelSpec(value: unknown): { provider: string; id: string } | undefined {
@@ -85,9 +133,8 @@ function readModelSpec(value: unknown): { provider: string; id: string } | undef
85
133
  }
86
134
 
87
135
  function readNamespacedConfig(path: string): Partial<TaskConfig> {
88
- if (!existsSync(path)) return {};
89
136
  try {
90
- const raw = JSON.parse(readFileSync(path, "utf-8")) as Record<string, unknown>;
137
+ const raw = readSettingsObject(path);
91
138
  const nested = raw[SETTINGS_KEY];
92
139
  if (!nested || typeof nested !== "object") return {};
93
140
  const section = nested as Record<string, unknown>;
@@ -115,6 +162,19 @@ function readNamespacedConfig(path: string): Partial<TaskConfig> {
115
162
  if (typeof threshold === "number" && Number.isFinite(threshold)) {
116
163
  out.recallLinksThreshold = Math.max(0, Math.floor(threshold));
117
164
  }
165
+
166
+ const inlineMax = section.recallSkillInlineMax;
167
+ if (typeof inlineMax === "number" && Number.isFinite(inlineMax)) {
168
+ out.recallSkillInlineMax = Math.max(0, Math.floor(inlineMax));
169
+ }
170
+
171
+ if (typeof section.notices === "boolean") {
172
+ out.notices = section.notices;
173
+ }
174
+
175
+ if (typeof section.trajectories === "boolean") {
176
+ out.trajectories = section.trajectories;
177
+ }
118
178
  return out;
119
179
  } catch {
120
180
  return {};
@@ -138,6 +198,22 @@ export function parseModelRef(ref: string): { provider: string; id: string } | u
138
198
  return { provider, id };
139
199
  }
140
200
 
201
+ /**
202
+ * Read a settings JSON file as a plain object, or `{}` when it is absent or
203
+ * corrupt. Reads directly (no `existsSync` pre-check) so there is no
204
+ * check-then-use race: a missing file throws ENOENT, which the catch treats
205
+ * the same as an empty file.
206
+ */
207
+ function readSettingsObject(path: string): Record<string, unknown> {
208
+ try {
209
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
210
+ if (parsed && typeof parsed === "object") return parsed as Record<string, unknown>;
211
+ } catch {
212
+ // Missing or corrupt settings file: start from an empty object.
213
+ }
214
+ return {};
215
+ }
216
+
141
217
  /**
142
218
  * Persist (or clear) the wiki background `taskModel` in the PROJECT settings
143
219
  * file `<cwd>/.pi/settings.json` under the namespaced `llm-wiki` key (issue
@@ -151,16 +227,7 @@ export function persistTaskModel(
151
227
  model: { provider: string; id: string } | undefined,
152
228
  ): void {
153
229
  const settingsPath = join(cwd, ".pi", "settings.json");
154
- let raw: Record<string, unknown> = {};
155
- if (existsSync(settingsPath)) {
156
- try {
157
- const parsed = JSON.parse(readFileSync(settingsPath, "utf-8"));
158
- if (parsed && typeof parsed === "object") raw = parsed as Record<string, unknown>;
159
- } catch {
160
- // Corrupt settings file: start from an empty object rather than throw.
161
- raw = {};
162
- }
163
- }
230
+ const raw = readSettingsObject(settingsPath);
164
231
 
165
232
  const existing = raw[SETTINGS_KEY];
166
233
  const section: Record<string, unknown> =
@@ -178,6 +245,33 @@ export function persistTaskModel(
178
245
  writeFileSync(settingsPath, `${JSON.stringify(raw, null, 2)}\n`, "utf-8");
179
246
  }
180
247
 
248
+ /**
249
+ * Persist the agent-trajectory flag in the PROJECT settings file
250
+ * `<cwd>/.pi/settings.json` under the namespaced `llm-wiki` key (issue #80).
251
+ * Mirrors `persistTaskModel`: project settings win in `loadTaskConfig`, other
252
+ * keys are preserved. `true` writes `trajectories: true`; `false` removes the
253
+ * key (reverting to the default-off behavior).
254
+ */
255
+ export function persistTrajectoriesEnabled(cwd: string, enabled: boolean): void {
256
+ const settingsPath = join(cwd, ".pi", "settings.json");
257
+ const raw = readSettingsObject(settingsPath);
258
+
259
+ const existing = raw[SETTINGS_KEY];
260
+ const section: Record<string, unknown> =
261
+ existing && typeof existing === "object" ? { ...(existing as Record<string, unknown>) } : {};
262
+
263
+ if (enabled) {
264
+ section.trajectories = true;
265
+ } else {
266
+ // biome-ignore lint/performance/noDelete: one-off settings rewrite, not a hot path; removing the key keeps the JSON clean (default is off)
267
+ delete section.trajectories;
268
+ }
269
+ raw[SETTINGS_KEY] = section;
270
+
271
+ mkdirSync(dirname(settingsPath), { recursive: true });
272
+ writeFileSync(settingsPath, `${JSON.stringify(raw, null, 2)}\n`, "utf-8");
273
+ }
274
+
181
275
  export function loadTaskConfig(cwd: string): TaskConfig {
182
276
  let globalPath: string;
183
277
  try {