poe-code 3.0.311 → 3.0.313

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": "poe-code",
3
- "version": "3.0.311",
3
+ "version": "3.0.313",
4
4
  "description": "CLI tool to configure Poe API for developer workflows.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,6 +20,9 @@ export async function loadMarkdownDocument(file, dependencies = {}) {
20
20
  };
21
21
  }
22
22
  export function resolveMarkdownPath(file, cwd = process.cwd()) {
23
+ if (file.trim().length === 0) {
24
+ throw new UserError("invalid file: expected a non-empty path");
25
+ }
23
26
  return path.isAbsolute(file) ? file : path.resolve(cwd, file);
24
27
  }
25
28
  export function sliceMarkdownBytes(source, start, end) {
@@ -58,6 +61,9 @@ function hasYamlLikeLeadingFrontmatter(source) {
58
61
  if (lines[0] !== "---") {
59
62
  return false;
60
63
  }
64
+ if (lines[1]?.trim().length === 0) {
65
+ return false;
66
+ }
61
67
  for (const line of lines.slice(1)) {
62
68
  const trimmed = line.trim();
63
69
  if (trimmed.length === 0) {
@@ -1,7 +1,11 @@
1
1
  import { loadMarkdownDocument, sliceMarkdownBytes } from "./document.js";
2
2
  import { resolveSection } from "./resolve.js";
3
+ import { UserError } from "toolcraft";
3
4
  export function createReadSection(dependencies = {}) {
4
5
  return async function readSection(params) {
6
+ if (params.section.trim().length === 0) {
7
+ throw new UserError("invalid section: expected a non-empty section id");
8
+ }
5
9
  const { source, sections } = await loadMarkdownDocument(params.file, dependencies);
6
10
  const section = resolveSection(sections, params.section);
7
11
  const end = params.includeChildren === false ? section.bodyEndNoChildren : section.bodyEnd;
@@ -1,14 +1,17 @@
1
1
  import { UserError } from "toolcraft";
2
2
  export function resolveSection(sections, id) {
3
3
  const trimmedId = id.trim();
4
- const unnumberedTitleMatch = sections.find((section) => section.number === null && section.title === trimmedId);
5
- if (unnumberedTitleMatch !== undefined) {
6
- return unnumberedTitleMatch;
4
+ if (trimmedId.length === 0) {
5
+ throw new UserError("invalid section: expected a non-empty section id");
7
6
  }
8
7
  const sectionByNumber = sections.find((section) => section.number === trimmedId);
9
8
  if (sectionByNumber !== undefined) {
10
9
  return sectionByNumber;
11
10
  }
11
+ const unnumberedTitleMatch = sections.find((section) => section.number === null && section.title === trimmedId);
12
+ if (unnumberedTitleMatch !== undefined) {
13
+ return unnumberedTitleMatch;
14
+ }
12
15
  const titleMatches = sections.filter((section) => section.title === trimmedId);
13
16
  if (titleMatches.length === 1) {
14
17
  return titleMatches[0];
@@ -4,7 +4,10 @@ export function scanMarkdown(source) {
4
4
  if (ast.type !== "root") {
5
5
  return [];
6
6
  }
7
- const headings = ast.children.filter(isHeadingNode);
7
+ const htmlCommentRanges = collectHtmlCommentRanges(source);
8
+ const headings = ast.children
9
+ .filter(isHeadingNode)
10
+ .filter((heading) => !isInsideHtmlComment(getRequiredRange(heading).start, htmlCommentRanges));
8
11
  if (headings.length === 0) {
9
12
  return [];
10
13
  }
@@ -25,6 +28,30 @@ export function scanMarkdown(source) {
25
28
  applyNumbers(sections, baselineDepth);
26
29
  return sections;
27
30
  }
31
+ function collectHtmlCommentRanges(source) {
32
+ const ranges = [];
33
+ let searchStart = 0;
34
+ while (searchStart < source.length) {
35
+ const commentStart = source.indexOf("<!--", searchStart);
36
+ if (commentStart === -1) {
37
+ return ranges;
38
+ }
39
+ const commentEndMarker = source.indexOf("-->", commentStart + "<!--".length);
40
+ const commentEnd = commentEndMarker === -1 ? source.length : commentEndMarker + "-->".length;
41
+ ranges.push({
42
+ start: byteOffset(source, commentStart),
43
+ end: byteOffset(source, commentEnd)
44
+ });
45
+ searchStart = commentEnd;
46
+ }
47
+ return ranges;
48
+ }
49
+ function isInsideHtmlComment(offset, ranges) {
50
+ return ranges.some((range) => offset >= range.start && offset < range.end);
51
+ }
52
+ function byteOffset(source, index) {
53
+ return Buffer.byteLength(source.slice(0, index), "utf8");
54
+ }
28
55
  function isHeadingNode(node) {
29
56
  return node.type === "heading";
30
57
  }
@@ -3,8 +3,12 @@ import { S } from "toolcraft-schema";
3
3
  import { readMarkdown } from "../core/read-markdown.js";
4
4
  import { readSection } from "../core/read-section.js";
5
5
  const readParams = S.Object({
6
- file: S.String({ description: "Path to the markdown file" }),
7
- depth: S.Optional(S.Number({ description: "Limit TOC to headings at depth <= n" }))
6
+ file: S.String({ description: "Path to the markdown file", minLength: 1 }),
7
+ depth: S.Optional(S.Number({
8
+ description: "Limit TOC to headings at depth <= n",
9
+ jsonType: "integer",
10
+ minimum: 0
11
+ }))
8
12
  });
9
13
  const tocEntryResult = S.Object({
10
14
  depth: S.Number(),
@@ -24,8 +28,11 @@ export const readTool = defineCommand({
24
28
  handler: async ({ params }) => readMarkdown(params)
25
29
  });
26
30
  const readSectionParams = S.Object({
27
- file: S.String({ description: "Path to the markdown file" }),
28
- section: S.String({ description: "Numeric path or exact heading text to read" }),
31
+ file: S.String({ description: "Path to the markdown file", minLength: 1 }),
32
+ section: S.String({
33
+ description: "Numeric path or exact heading text to read",
34
+ minLength: 1
35
+ }),
29
36
  includeChildren: S.Optional(S.Boolean({ description: "Include nested child sections" }))
30
37
  });
31
38
  export const readSectionTool = defineCommand({
@@ -0,0 +1,2 @@
1
+ import type { QueryResult } from "./types.js";
2
+ export declare function parseMemoryAgentResponse(stdout: string): Pick<QueryResult, "answer" | "citations" | "tokensUsed">;
@@ -0,0 +1,38 @@
1
+ const validCitationConfidences = new Set(["extracted", "inferred", "ambiguous"]);
2
+ export function parseMemoryAgentResponse(stdout) {
3
+ let value;
4
+ try {
5
+ value = JSON.parse(stdout);
6
+ }
7
+ catch {
8
+ throw new Error("Memory agent returned invalid JSON output.");
9
+ }
10
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
11
+ throw new Error("Memory agent returned an invalid result payload.");
12
+ }
13
+ const result = value;
14
+ if (typeof result.answer !== "string" ||
15
+ !Array.isArray(result.citations) ||
16
+ !isNonNegativeInteger(result.tokensUsed) ||
17
+ !result.citations.every(isQueryCitation)) {
18
+ throw new Error("Memory agent returned an invalid result payload.");
19
+ }
20
+ return {
21
+ answer: result.answer,
22
+ citations: result.citations,
23
+ tokensUsed: result.tokensUsed
24
+ };
25
+ }
26
+ function isQueryCitation(value) {
27
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
28
+ return false;
29
+ }
30
+ const citation = value;
31
+ return (typeof citation.relPath === "string" &&
32
+ (citation.section === undefined || typeof citation.section === "string") &&
33
+ typeof citation.confidence === "string" &&
34
+ validCitationConfidences.has(citation.confidence));
35
+ }
36
+ function isNonNegativeInteger(value) {
37
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
38
+ }
@@ -97,17 +97,21 @@ export async function clearCache(root, opts = {}) {
97
97
  async function assertIngestCachePathIsNotSymlink(root) {
98
98
  await assertNoSymlinkSegments(root, MEMORY_INGEST_CACHE_DIR_RELPATH);
99
99
  }
100
- function parseCacheEntry(value, _key) {
100
+ function parseCacheEntry(value, requestedKey) {
101
101
  const object = expectRecord(value);
102
+ const key = expectString(getOwnEntry(object, "key"), "key");
103
+ if (key !== requestedKey) {
104
+ throw new Error(`Cache entry key "${key}" does not match requested key "${requestedKey}".`);
105
+ }
102
106
  return {
103
- key: expectString(getOwnEntry(object, "key"), "key"),
107
+ key,
104
108
  ingestedAt: expectString(getOwnEntry(object, "ingestedAt"), "ingestedAt"),
105
109
  sourceLabel: expectString(getOwnEntry(object, "sourceLabel"), "sourceLabel"),
106
110
  diff: parseMemoryDiff(getOwnEntry(object, "diff")),
107
- exitCode: expectNumber(getOwnEntry(object, "exitCode"), "exitCode"),
108
- durationMs: expectNumber(getOwnEntry(object, "durationMs"), "durationMs"),
109
- memoryTokens: expectNumber(getOwnEntry(object, "memoryTokens"), "memoryTokens"),
110
- sourceTokens: expectNumber(getOwnEntry(object, "sourceTokens"), "sourceTokens"),
111
+ exitCode: expectNonNegativeInteger(getOwnEntry(object, "exitCode"), "exitCode"),
112
+ durationMs: expectNonNegativeInteger(getOwnEntry(object, "durationMs"), "durationMs"),
113
+ memoryTokens: expectNonNegativeInteger(getOwnEntry(object, "memoryTokens"), "memoryTokens"),
114
+ sourceTokens: expectNonNegativeInteger(getOwnEntry(object, "sourceTokens"), "sourceTokens"),
111
115
  promptTemplateVersion: expectString(getOwnEntry(object, "promptTemplateVersion"), "promptTemplateVersion"),
112
116
  agentId: expectString(getOwnEntry(object, "agentId"), "agentId")
113
117
  };
@@ -132,9 +136,9 @@ function expectString(value, field) {
132
136
  }
133
137
  return value;
134
138
  }
135
- function expectNumber(value, field) {
136
- if (typeof value !== "number" || Number.isNaN(value)) {
137
- throw new Error(`Expected number at "${field}".`);
139
+ function expectNonNegativeInteger(value, field) {
140
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
141
+ throw new Error(`Expected non-negative integer at "${field}".`);
138
142
  }
139
143
  return value;
140
144
  }
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { countTokens } from "tokenfill";
4
4
  import { spawn } from "@poe-code/agent-spawn";
5
5
  import { resolveAgent } from "@poe-code/poe-code-config";
6
+ import { parseMemoryAgentResponse } from "./agent-response.js";
6
7
  import { hasOwnErrorCode } from "./errors.js";
7
8
  import { readPage } from "./pages.js";
8
9
  import { selectQueryContext } from "./query.js";
@@ -33,7 +34,7 @@ export async function explainPage(root, options) {
33
34
  };
34
35
  const agentId = (await resolveAgent(configOptions, options.agent ?? null)) ?? options.agent ?? "claude-code";
35
36
  const spawned = await spawn(agentId, { prompt });
36
- const response = parseExplainResponse(spawned.stdout);
37
+ const response = parseMemoryAgentResponse(spawned.stdout);
37
38
  return {
38
39
  answer: response.answer,
39
40
  citations: response.citations,
@@ -47,30 +48,6 @@ export async function explainPage(root, options) {
47
48
  outboundSources: targetPage.frontmatter.sources ?? []
48
49
  };
49
50
  }
50
- function parseExplainResponse(stdout) {
51
- let value;
52
- try {
53
- value = JSON.parse(stdout);
54
- }
55
- catch {
56
- throw new Error("Memory agent returned invalid JSON output.");
57
- }
58
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
59
- throw new Error("Memory agent returned an invalid result payload.");
60
- }
61
- const response = value;
62
- if (typeof response.answer !== "string" ||
63
- !Array.isArray(response.citations) ||
64
- typeof response.tokensUsed !== "number" ||
65
- !Number.isFinite(response.tokensUsed)) {
66
- throw new Error("Memory agent returned an invalid result payload.");
67
- }
68
- return {
69
- answer: response.answer,
70
- citations: response.citations,
71
- tokensUsed: response.tokensUsed
72
- };
73
- }
74
51
  function collectRelatedPages(pages, targetRelPath, outboundSources) {
75
52
  const memorySourcePaths = new Set(outboundSources.map((source) => source.path));
76
53
  return pages.filter((page) => {
@@ -23,7 +23,11 @@ export function serializeFrontmatter(frontmatter, body) {
23
23
  return stringifyFrontmatter(serialized, body);
24
24
  }
25
25
  export function parseSourceRef(serialized) {
26
- const [rawPath, rawAnchor] = serialized.split("#", 2);
26
+ const parts = serialized.split("#");
27
+ if (parts.length > 2) {
28
+ throw new Error(`Invalid source ref "${serialized}".`);
29
+ }
30
+ const [rawPath, rawAnchor] = parts;
27
31
  const normalizedPath = rawPath?.trim();
28
32
  if (normalizedPath === undefined || normalizedPath.length === 0) {
29
33
  throw new Error(`Invalid source ref "${serialized}".`);
@@ -68,6 +68,9 @@ async function assertMemoryRootIsNotSymlink(root) {
68
68
  try {
69
69
  const stat7 = await fs.lstat(currentPath);
70
70
  if (stat7.isSymbolicLink()) {
71
+ if (await isAllowedMacSystemAlias(currentPath)) {
72
+ continue;
73
+ }
71
74
  throw new MemoryPathError(`Memory root "${root}" cannot be a symbolic link.`);
72
75
  }
73
76
  } catch (error2) {
@@ -78,6 +81,17 @@ async function assertMemoryRootIsNotSymlink(root) {
78
81
  }
79
82
  }
80
83
  }
84
+ async function isAllowedMacSystemAlias(currentPath) {
85
+ if (currentPath !== "/var") {
86
+ return false;
87
+ }
88
+ try {
89
+ const target = await fs.readlink(currentPath);
90
+ return target === "/private/var" || target === "private/var";
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
81
95
  function isMissing(error2) {
82
96
  return hasOwnErrorCode(error2, "ENOENT");
83
97
  }
@@ -5388,7 +5402,11 @@ function serializeFrontmatter(frontmatter, body) {
5388
5402
  return stringifyFrontmatter(serialized, body);
5389
5403
  }
5390
5404
  function parseSourceRef(serialized) {
5391
- const [rawPath, rawAnchor] = serialized.split("#", 2);
5405
+ const parts = serialized.split("#");
5406
+ if (parts.length > 2) {
5407
+ throw new Error(`Invalid source ref "${serialized}".`);
5408
+ }
5409
+ const [rawPath, rawAnchor] = parts;
5392
5410
  const normalizedPath = rawPath?.trim();
5393
5411
  if (normalizedPath === void 0 || normalizedPath.length === 0) {
5394
5412
  throw new Error(`Invalid source ref "${serialized}".`);
@@ -5626,7 +5644,7 @@ async function searchMemory(root, query) {
5626
5644
  if (normalizedQuery.length === 0) {
5627
5645
  throw new Error("Search query cannot be empty.");
5628
5646
  }
5629
- const relPaths = await collectMarkdownRelPaths(root);
5647
+ const relPaths = await collectMarkdownRelPaths(root, MEMORY_PAGES_DIR_RELPATH);
5630
5648
  const hits = [];
5631
5649
  for (const relPath of relPaths) {
5632
5650
  await assertNoSymlinkSegments(root, relPath);
@@ -6149,7 +6167,7 @@ async function appendToPage(root, relPath, content, opts) {
6149
6167
  const before = await snapshot(root);
6150
6168
  await fs8.mkdir(path22.dirname(pagePath), { recursive: true });
6151
6169
  await assertNoSymlinkSegments(root, pageRelPath);
6152
- const parsed = originalPage === void 0 ? { frontmatter: {}, body: "" } : parseFrontmatter2(originalPage);
6170
+ const parsed = parseAppendTarget(originalPage, pageRelPath);
6153
6171
  try {
6154
6172
  await writeFileAtomically2(
6155
6173
  pagePath,
@@ -6161,6 +6179,18 @@ async function appendToPage(root, relPath, content, opts) {
6161
6179
  throw error2;
6162
6180
  }
6163
6181
  }
6182
+ function parseAppendTarget(originalPage, relPath) {
6183
+ if (originalPage === void 0) {
6184
+ return { frontmatter: {}, body: "" };
6185
+ }
6186
+ try {
6187
+ return parseFrontmatter2(originalPage);
6188
+ } catch (error2) {
6189
+ const message2 = error2 instanceof Error ? error2.message : String(error2);
6190
+ console.warn(`Failed to parse frontmatter for "${relPath}": ${message2}`);
6191
+ return { frontmatter: {}, body: originalPage };
6192
+ }
6193
+ }
6164
6194
  async function clearMemory(root) {
6165
6195
  await assertMemoryRootIsNotSymlink(root);
6166
6196
  const stagedRoot = `${root}.clear-${randomUUID7()}`;
@@ -6494,17 +6524,21 @@ async function clearCache(root, opts = {}) {
6494
6524
  async function assertIngestCachePathIsNotSymlink(root) {
6495
6525
  await assertNoSymlinkSegments(root, MEMORY_INGEST_CACHE_DIR_RELPATH);
6496
6526
  }
6497
- function parseCacheEntry(value, _key) {
6527
+ function parseCacheEntry(value, requestedKey) {
6498
6528
  const object = expectRecord(value);
6529
+ const key = expectString(getOwnEntry8(object, "key"), "key");
6530
+ if (key !== requestedKey) {
6531
+ throw new Error(`Cache entry key "${key}" does not match requested key "${requestedKey}".`);
6532
+ }
6499
6533
  return {
6500
- key: expectString(getOwnEntry8(object, "key"), "key"),
6534
+ key,
6501
6535
  ingestedAt: expectString(getOwnEntry8(object, "ingestedAt"), "ingestedAt"),
6502
6536
  sourceLabel: expectString(getOwnEntry8(object, "sourceLabel"), "sourceLabel"),
6503
6537
  diff: parseMemoryDiff(getOwnEntry8(object, "diff")),
6504
- exitCode: expectNumber(getOwnEntry8(object, "exitCode"), "exitCode"),
6505
- durationMs: expectNumber(getOwnEntry8(object, "durationMs"), "durationMs"),
6506
- memoryTokens: expectNumber(getOwnEntry8(object, "memoryTokens"), "memoryTokens"),
6507
- sourceTokens: expectNumber(getOwnEntry8(object, "sourceTokens"), "sourceTokens"),
6538
+ exitCode: expectNonNegativeInteger(getOwnEntry8(object, "exitCode"), "exitCode"),
6539
+ durationMs: expectNonNegativeInteger(getOwnEntry8(object, "durationMs"), "durationMs"),
6540
+ memoryTokens: expectNonNegativeInteger(getOwnEntry8(object, "memoryTokens"), "memoryTokens"),
6541
+ sourceTokens: expectNonNegativeInteger(getOwnEntry8(object, "sourceTokens"), "sourceTokens"),
6508
6542
  promptTemplateVersion: expectString(
6509
6543
  getOwnEntry8(object, "promptTemplateVersion"),
6510
6544
  "promptTemplateVersion"
@@ -6532,9 +6566,9 @@ function expectString(value, field) {
6532
6566
  }
6533
6567
  return value;
6534
6568
  }
6535
- function expectNumber(value, field) {
6536
- if (typeof value !== "number" || Number.isNaN(value)) {
6537
- throw new Error(`Expected number at "${field}".`);
6569
+ function expectNonNegativeInteger(value, field) {
6570
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
6571
+ throw new Error(`Expected non-negative integer at "${field}".`);
6538
6572
  }
6539
6573
  return value;
6540
6574
  }
@@ -16586,7 +16620,7 @@ async function installMemory(options) {
16586
16620
  }
16587
16621
  throw error2;
16588
16622
  }
16589
- mcpConfigPath = options.agent === "codex" ? `${options.homeDir}/.config/codex/mcp-config.json` : `${options.homeDir}/.mcp.json`;
16623
+ mcpConfigPath = resolveMcpConfigPath(options.agent, options.homeDir, options.platform);
16590
16624
  }
16591
16625
  return {
16592
16626
  skillInstalled: !options.mcpOnly,
@@ -16595,6 +16629,14 @@ async function installMemory(options) {
16595
16629
  mcpConfigPath
16596
16630
  };
16597
16631
  }
16632
+ function resolveMcpConfigPath(agent, homeDir, platform) {
16633
+ const support = resolveAgentSupport3(agent);
16634
+ if (support.status !== "supported" || support.config === void 0) {
16635
+ throw new Error(`Unsupported agent: ${agent}`);
16636
+ }
16637
+ const configFile = typeof support.config.configFile === "function" ? support.config.configFile(platform) : support.config.configFile;
16638
+ return configFile.startsWith("~/") ? path61.join(homeDir, configFile.slice(2)) : configFile;
16639
+ }
16598
16640
  async function removeInstalledSkill(options, skillPath) {
16599
16641
  const baseDir = options.scope === "global" ? options.homeDir : options.cwd;
16600
16642
  const displayPath = skillPath.startsWith("~/") ? skillPath.slice(2) : skillPath;
@@ -16609,6 +16651,41 @@ async function removeInstalledSkill(options, skillPath) {
16609
16651
  // packages/memory/src/query.ts
16610
16652
  import * as fs18 from "node:fs/promises";
16611
16653
  import path62 from "node:path";
16654
+
16655
+ // packages/memory/src/agent-response.ts
16656
+ var validCitationConfidences = /* @__PURE__ */ new Set(["extracted", "inferred", "ambiguous"]);
16657
+ function parseMemoryAgentResponse(stdout) {
16658
+ let value;
16659
+ try {
16660
+ value = JSON.parse(stdout);
16661
+ } catch {
16662
+ throw new Error("Memory agent returned invalid JSON output.");
16663
+ }
16664
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
16665
+ throw new Error("Memory agent returned an invalid result payload.");
16666
+ }
16667
+ const result = value;
16668
+ if (typeof result.answer !== "string" || !Array.isArray(result.citations) || !isNonNegativeInteger(result.tokensUsed) || !result.citations.every(isQueryCitation)) {
16669
+ throw new Error("Memory agent returned an invalid result payload.");
16670
+ }
16671
+ return {
16672
+ answer: result.answer,
16673
+ citations: result.citations,
16674
+ tokensUsed: result.tokensUsed
16675
+ };
16676
+ }
16677
+ function isQueryCitation(value) {
16678
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
16679
+ return false;
16680
+ }
16681
+ const citation = value;
16682
+ return typeof citation.relPath === "string" && (citation.section === void 0 || typeof citation.section === "string") && typeof citation.confidence === "string" && validCitationConfidences.has(citation.confidence);
16683
+ }
16684
+ function isNonNegativeInteger(value) {
16685
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
16686
+ }
16687
+
16688
+ // packages/memory/src/query.ts
16612
16689
  async function queryMemory(root, options) {
16613
16690
  const pages = await listPages(root);
16614
16691
  if (pages.length === 0) {
@@ -16628,7 +16705,7 @@ async function queryMemory(root, options) {
16628
16705
  const agentId = await resolveAgent(configOptions, options.agent ?? null) ?? options.agent ?? "claude-code";
16629
16706
  const context = await selectQueryContext(root, options.question, options.budget);
16630
16707
  const spawned = await spawn4(agentId, { prompt: context.prompt });
16631
- const result = parseQueryResponse(spawned.stdout);
16708
+ const result = parseMemoryAgentResponse(spawned.stdout);
16632
16709
  return {
16633
16710
  answer: result.answer,
16634
16711
  citations: result.citations,
@@ -16637,26 +16714,6 @@ async function queryMemory(root, options) {
16637
16714
  exitCode: spawned.exitCode
16638
16715
  };
16639
16716
  }
16640
- function parseQueryResponse(stdout) {
16641
- let value;
16642
- try {
16643
- value = JSON.parse(stdout);
16644
- } catch {
16645
- throw new Error("Memory agent returned invalid JSON output.");
16646
- }
16647
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
16648
- throw new Error("Memory agent returned an invalid result payload.");
16649
- }
16650
- const result = value;
16651
- if (typeof result.answer !== "string" || !Array.isArray(result.citations) || typeof result.tokensUsed !== "number" || !Number.isFinite(result.tokensUsed)) {
16652
- throw new Error("Memory agent returned an invalid result payload.");
16653
- }
16654
- return {
16655
- answer: result.answer,
16656
- citations: result.citations,
16657
- tokensUsed: result.tokensUsed
16658
- };
16659
- }
16660
16717
  async function selectQueryContext(root, question, budget) {
16661
16718
  if (!Number.isFinite(budget) || budget < 0) {
16662
16719
  throw new Error("budget must be a finite non-negative number");
@@ -16774,7 +16831,7 @@ async function explainPage(root, options) {
16774
16831
  };
16775
16832
  const agentId = await resolveAgent(configOptions, options.agent ?? null) ?? options.agent ?? "claude-code";
16776
16833
  const spawned = await spawn4(agentId, { prompt });
16777
- const response = parseExplainResponse(spawned.stdout);
16834
+ const response = parseMemoryAgentResponse(spawned.stdout);
16778
16835
  return {
16779
16836
  answer: response.answer,
16780
16837
  citations: response.citations,
@@ -16785,26 +16842,6 @@ async function explainPage(root, options) {
16785
16842
  outboundSources: targetPage.frontmatter.sources ?? []
16786
16843
  };
16787
16844
  }
16788
- function parseExplainResponse(stdout) {
16789
- let value;
16790
- try {
16791
- value = JSON.parse(stdout);
16792
- } catch {
16793
- throw new Error("Memory agent returned invalid JSON output.");
16794
- }
16795
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
16796
- throw new Error("Memory agent returned an invalid result payload.");
16797
- }
16798
- const response = value;
16799
- if (typeof response.answer !== "string" || !Array.isArray(response.citations) || typeof response.tokensUsed !== "number" || !Number.isFinite(response.tokensUsed)) {
16800
- throw new Error("Memory agent returned an invalid result payload.");
16801
- }
16802
- return {
16803
- answer: response.answer,
16804
- citations: response.citations,
16805
- tokensUsed: response.tokensUsed
16806
- };
16807
- }
16808
16845
  function collectRelatedPages(pages, targetRelPath, outboundSources) {
16809
16846
  const memorySourcePaths = new Set(outboundSources.map((source) => source.path));
16810
16847
  return pages.filter((page) => {