pi-byterover 0.2.6 → 0.2.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-byterover",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "private": false,
5
5
  "description": "Pi extension that recalls and persists ByteRover memory.",
6
6
  "keywords": [
@@ -32,8 +32,7 @@
32
32
  "provenance": true
33
33
  },
34
34
  "dependencies": {
35
- "@byterover/brv-bridge": "^1.2.0",
36
- "zod": "^4.4.3"
35
+ "@byterover/brv-bridge": "^1.2.0"
37
36
  },
38
37
  "devDependencies": {
39
38
  "byterover-cli": "3.16.1"
@@ -343,22 +343,7 @@ export const createByteRoverExtension = (
343
343
 
344
344
  if (config.manualTools) {
345
345
  registerManualTools({
346
- pi: {
347
- registerTool: (tool) => {
348
- switch (tool.name) {
349
- case "brv_recall":
350
- pi.registerTool(tool);
351
- return;
352
- case "brv_search":
353
- pi.registerTool(tool);
354
- return;
355
- case "brv_persist":
356
- pi.registerTool(tool);
357
- return;
358
- }
359
- },
360
- },
361
- config,
346
+ pi,
362
347
  bridge,
363
348
  createBridge,
364
349
  });
@@ -1,10 +1,9 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
- import type * as z from "zod/v4";
5
- import { ConfigSchema } from "./config.js";
4
+ import { parseConfigDocument, type ByteroverConfig } from "./config.js";
6
5
 
7
- export type ByteroverConfig = z.infer<typeof ConfigSchema>;
6
+ export type { ByteroverConfig };
8
7
 
9
8
  export type LoadConfigOptions = {
10
9
  cwd: string;
@@ -21,7 +20,7 @@ const invalidConfig = (source: string, error: Error): LoadConfigResult => ({
21
20
  error: new Error(`Invalid Byterover configuration in ${source}: ${error.message}`),
22
21
  });
23
22
 
24
- /** Loads the highest-precedence ByteRover JSON configuration through its Zod boundary. */
23
+ /** Loads the highest-precedence ByteRover JSON configuration through its TypeBox boundary. */
25
24
  export const loadConfig = async ({
26
25
  cwd,
27
26
  homeDir = homedir(),
@@ -41,11 +40,11 @@ export const loadConfig = async ({
41
40
  }
42
41
 
43
42
  try {
44
- return { success: true, source, config: ConfigSchema.parse(JSON.parse(raw)) };
43
+ return { success: true, source, config: parseConfigDocument(JSON.parse(raw)) };
45
44
  } catch (cause) {
46
45
  return invalidConfig(source, cause instanceof Error ? cause : new Error(String(cause)));
47
46
  }
48
47
  }
49
48
 
50
- return { success: true, config: ConfigSchema.parse(undefined) };
49
+ return { success: true, config: parseConfigDocument(undefined) };
51
50
  };
package/src/config.ts CHANGED
@@ -1,4 +1,5 @@
1
- import * as z from "zod/v4";
1
+ import { Decode, type StaticDecode, Type } from "typebox";
2
+ import { Value } from "typebox/value";
2
3
 
3
4
  export const brvGitignoreBeginMarker = "# BEGIN pi-byterover";
4
5
  export const brvGitignoreEndMarker = "# END pi-byterover";
@@ -46,29 +47,44 @@ export const configDefaults = {
46
47
  maxRecallChars: 4096,
47
48
  };
48
49
 
49
- const positiveInteger = () => z.number().int().positive();
50
- const nonEmptyString = () => z.string().trim().min(1);
50
+ /** Raw, partially-specified Byterover configuration document. */
51
+ export type ByteroverConfigDocument = StaticDecode<typeof ConfigSchema>;
51
52
 
52
- export const ConfigSchema = z
53
- .object({
54
- enabled: z.boolean().default(configDefaults.enabled),
53
+ /** Fully defaulted Byterover configuration. */
54
+ export type ByteroverConfig = ByteroverConfigDocument & typeof configDefaults;
55
+
56
+ /** Parses one Byterover configuration document, rejecting invalid values, then applies defaults. */
57
+ // oxlint-disable-next-line anti-slop/no-unknown-parameters -- This function IS the untrusted-document parser boundary.
58
+ export const parseConfigDocument = (value: unknown): ByteroverConfig => ({
59
+ ...configDefaults,
60
+ ...Value.Decode(ConfigSchema, value === undefined ? {} : value),
61
+ });
62
+
63
+ const trimmedNonEmptyString = () =>
64
+ Decode(Type.String({ minLength: 1, pattern: "\\S" }), (value) => value.trim());
65
+
66
+ export const ConfigSchema = Type.Object(
67
+ {
68
+ enabled: Type.Optional(Type.Boolean()),
55
69
  // BrvBridge options
56
- brvPath: nonEmptyString().optional().default(configDefaults.brvPath),
57
- searchTimeoutMs: positiveInteger().default(configDefaults.searchTimeoutMs),
58
- recallTimeoutMs: positiveInteger().default(configDefaults.recallTimeoutMs),
59
- persistTimeoutMs: positiveInteger().default(configDefaults.persistTimeoutMs),
70
+ brvPath: Type.Optional(trimmedNonEmptyString()),
71
+ searchTimeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
72
+ recallTimeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
73
+ persistTimeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
60
74
  // Plugin options
61
- quiet: z.boolean().default(configDefaults.quiet),
62
- autoRecall: z.boolean().default(configDefaults.autoRecall),
63
- autoPersist: z.boolean().default(configDefaults.autoPersist),
64
- manualTools: z.boolean().default(configDefaults.manualTools),
65
- contextTagName: nonEmptyString()
66
- .regex(/^[A-Za-z][A-Za-z0-9._-]*$/u)
67
- .default(configDefaults.contextTagName),
68
- recallPrompt: nonEmptyString().default(configDefaults.recallPrompt),
69
- persistPrompt: nonEmptyString().default(configDefaults.persistPrompt),
70
- maxRecallTurns: positiveInteger().default(configDefaults.maxRecallTurns),
71
- maxRecallChars: positiveInteger().default(configDefaults.maxRecallChars),
72
- })
73
- .optional()
74
- .default(configDefaults);
75
+ quiet: Type.Optional(Type.Boolean()),
76
+ autoRecall: Type.Optional(Type.Boolean()),
77
+ autoPersist: Type.Optional(Type.Boolean()),
78
+ manualTools: Type.Optional(Type.Boolean()),
79
+ contextTagName: Type.Optional(
80
+ Decode(Type.String({ minLength: 1, pattern: "^\\s*[A-Za-z][A-Za-z0-9._-]*\\s*$" }), (value) =>
81
+ value.trim(),
82
+ ),
83
+ ),
84
+ recallPrompt: Type.Optional(trimmedNonEmptyString()),
85
+ persistPrompt: Type.Optional(trimmedNonEmptyString()),
86
+ maxRecallTurns: Type.Optional(Type.Integer({ minimum: 1 })),
87
+ maxRecallChars: Type.Optional(Type.Integer({ minimum: 1 })),
88
+ },
89
+ { additionalProperties: false },
90
+ );
package/src/gitignore.ts CHANGED
@@ -7,7 +7,8 @@ import {
7
7
  brvGitignoreRules,
8
8
  } from "./config.js";
9
9
 
10
- const escapeRegExp = (value: string) => {
10
+ /** Escape one string for verbatim interpolation into a regular expression. */
11
+ export const escapeRegExp = (value: string) => {
11
12
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12
13
  };
13
14
 
package/src/recall.ts CHANGED
@@ -1,6 +1,4 @@
1
- const escapeRegExp = (value: string) => {
2
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3
- };
1
+ import { escapeRegExp } from "./gitignore.js";
4
2
 
5
3
  export const stripEchoedRecallQuery = (content: string, query: string) => {
6
4
  const trimmedContent = content.trim();
package/src/tools.ts CHANGED
@@ -6,15 +6,11 @@ import type {
6
6
  } from "@earendil-works/pi-coding-agent";
7
7
  import { type Static, type TSchema, Type } from "typebox";
8
8
  import type { ByteRoverBridge, ByteRoverBridgeFactory } from "./byterover-bridge.js";
9
- import type { ConfigSchema } from "./config.js";
10
9
  import { stripEchoedRecallQuery } from "./recall.js";
11
10
 
12
- type Config = ReturnType<typeof ConfigSchema.parse>;
13
-
14
11
  export type RegisterManualToolsInput = {
15
12
  pi: ByteRoverManualToolHost;
16
13
  bridge: ByteRoverBridge;
17
- config: Config;
18
14
  createBridge: ByteRoverBridgeFactory;
19
15
  };
20
16
 
@@ -114,9 +110,21 @@ export type ByteRoverManualToolDefinition =
114
110
  | ManualToolDefinition<"brv_search", typeof SearchParameters>
115
111
  | ManualToolDefinition<"brv_persist", typeof PersistParameters>;
116
112
 
117
- /** Registers typed ByteRover manual tools with Pi or a faithful recording host. */
113
+ /** Lists the three registered manual-memory tool names. */
114
+ export const BYTE_ROVER_MANUAL_TOOL_NAMES = ["brv_recall", "brv_search", "brv_persist"] as const;
115
+
116
+ /** Narrows any tool definition to one of the three ByteRover manual tools by name. */
117
+ export const isByteRoverManualToolDefinition = (tool: {
118
+ readonly name: string;
119
+ }): tool is ByteRoverManualToolDefinition =>
120
+ // SAFETY: widening the const tuple to readonly string[] only relaxes literal checking for includes().
121
+ (BYTE_ROVER_MANUAL_TOOL_NAMES as readonly string[]).includes(tool.name);
122
+
123
+ /** Registers ByteRover manual tools with Pi or a faithful recording host. */
118
124
  export interface ByteRoverManualToolHost {
119
- registerTool(tool: ByteRoverManualToolDefinition): void;
125
+ registerTool<TParams extends TSchema = TSchema, TDetails = unknown, TState = unknown>(
126
+ tool: ToolDefinition<TParams, TDetails, TState>,
127
+ ): void;
120
128
  }
121
129
 
122
130
  const textResult = (text: string): AgentToolResult<undefined> => ({
@@ -124,8 +132,6 @@ const textResult = (text: string): AgentToolResult<undefined> => ({
124
132
  details: undefined,
125
133
  });
126
134
 
127
- const errorMessage = (error: Error) => error.message;
128
-
129
135
  export const formatSearchResults = (
130
136
  results: readonly SearchResultItem[],
131
137
  totalFound: number,
@@ -158,11 +164,9 @@ export const formatSearchResults = (
158
164
  export const registerManualTools = ({
159
165
  pi,
160
166
  bridge,
161
- config,
162
167
  createBridge,
163
- }: RegisterManualToolsInput) => {
164
- if (!config.manualTools) return;
165
-
168
+ }: Omit<RegisterManualToolsInput, "config">) => {
169
+ // ponytail: the session-start caller gates on config.manualTools before reaching this registration.
166
170
  pi.registerTool({
167
171
  name: "brv_recall",
168
172
  label: "ByteRover Recall",
@@ -185,7 +189,7 @@ export const registerManualTools = ({
185
189
  return textResult(content || "No relevant ByteRover context found.");
186
190
  } catch (cause) {
187
191
  const error = cause instanceof Error ? cause : new Error(String(cause));
188
- return textResult(`ByteRover recall failed: ${errorMessage(error)}`);
192
+ return textResult(`ByteRover recall failed: ${error.message}`);
189
193
  }
190
194
  },
191
195
  });
@@ -214,7 +218,7 @@ export const registerManualTools = ({
214
218
  );
215
219
  } catch (cause) {
216
220
  const error = cause instanceof Error ? cause : new Error(String(cause));
217
- return textResult(`ByteRover search failed: ${errorMessage(error)}`);
221
+ return textResult(`ByteRover search failed: ${error.message}`);
218
222
  }
219
223
  },
220
224
  });
@@ -243,7 +247,7 @@ export const registerManualTools = ({
243
247
  return textResult(`ByteRover persist ${brvResult.status}${suffix}`);
244
248
  } catch (cause) {
245
249
  const error = cause instanceof Error ? cause : new Error(String(cause));
246
- return textResult(`ByteRover persist failed: ${errorMessage(error)}`);
250
+ return textResult(`ByteRover persist failed: ${error.message}`);
247
251
  }
248
252
  },
249
253
  });