Skip to content

Commit a4b9a29

Browse files
LLagoon3clawsweeper[bot]
authored andcommitted
fix(memory-wiki): make wiki lint tool output path-safe
1 parent cf19441 commit a4b9a29

2 files changed

Lines changed: 66 additions & 3 deletions

File tree

extensions/memory-wiki/src/tool.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import fs from "node:fs/promises";
2+
import path from "node:path";
13
import { describe, expect, it } from "vitest";
24
import type { ResolvedMemoryWikiConfig } from "./config.js";
3-
import { createWikiApplyTool } from "./tool.js";
5+
import { createWikiApplyTool, createWikiLintTool } from "./tool.js";
6+
import { lintMemoryWikiVault } from "./lint.js";
7+
import { createMemoryWikiTestHarness } from "./test-helpers.js";
48

59
function asSchemaObject(value: unknown): Record<string, unknown> {
610
if (typeof value !== "object" || value === null || Array.isArray(value)) {
@@ -10,6 +14,8 @@ function asSchemaObject(value: unknown): Record<string, unknown> {
1014
}
1115

1216
describe("memory-wiki tools", () => {
17+
const harness = createMemoryWikiTestHarness();
18+
1319
it("allows provenance metadata in wiki_apply claim evidence", () => {
1420
const tool = createWikiApplyTool({} as ResolvedMemoryWikiConfig);
1521
const applyProperties = asSchemaObject(asSchemaObject(tool.parameters).properties);
@@ -33,4 +39,40 @@ describe("memory-wiki tools", () => {
3339
]);
3440
expect(evidenceProperties.confidence).toEqual({ type: "number", minimum: 0, maximum: 1 });
3541
});
42+
43+
it("returns tool-safe relative report paths from wiki_lint", async () => {
44+
const { rootDir, config } = await harness.createVault({ initialize: true });
45+
await fs.mkdir(path.join(rootDir, "syntheses"), { recursive: true });
46+
await fs.writeFile(
47+
path.join(rootDir, "syntheses", "bad.md"),
48+
[
49+
"---",
50+
"id: synth-bad",
51+
"pageType: synthesis",
52+
"title: Bad Page",
53+
"---",
54+
"",
55+
"This links to [[Missing Page]].",
56+
].join("\n"),
57+
"utf8",
58+
);
59+
60+
const tool = createWikiLintTool(config);
61+
const result = await tool.execute("lint-call", {});
62+
const text = result.content.find((part) => part.type === "text")?.text ?? "";
63+
const details = asSchemaObject(result.details);
64+
65+
expect(text).toContain("Report: reports/lint.md");
66+
expect(text).not.toContain(rootDir);
67+
expect(details.reportPath).toBe("reports/lint.md");
68+
expect(details).not.toHaveProperty("vaultRoot");
69+
expect(JSON.stringify(details)).not.toContain(rootDir);
70+
expect(asSchemaObject(details.issuesByCategory).links).toEqual(
71+
expect.arrayContaining([expect.objectContaining({ code: "broken-wikilink" })]),
72+
);
73+
74+
const lintResult = await lintMemoryWikiVault(config);
75+
expect(path.isAbsolute(lintResult.reportPath)).toBe(true);
76+
expect(lintResult.reportPath).toContain(rootDir);
77+
});
3678
});

extensions/memory-wiki/src/tool.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from "node:path";
12
import { Type } from "typebox";
23
import type { AnyAgentTool, OpenClawConfig } from "../api.js";
34
import { applyMemoryWikiMutation, normalizeMemoryWikiMutationInput } from "./apply.js";
@@ -11,6 +12,20 @@ import { getMemoryWikiPage, searchMemoryWiki, WIKI_SEARCH_MODES } from "./query.
1112
import { syncMemoryWikiImportedSources } from "./source-sync.js";
1213
import { renderMemoryWikiStatus, resolveMemoryWikiStatus } from "./status.js";
1314

15+
function formatWikiToolReportPath(config: ResolvedMemoryWikiConfig, reportPath: string): string {
16+
const vaultRoot = path.resolve(config.vault.path);
17+
const resolvedReportPath = path.resolve(reportPath);
18+
const relativeReportPath = path.relative(vaultRoot, resolvedReportPath);
19+
if (
20+
!relativeReportPath ||
21+
relativeReportPath.startsWith("..") ||
22+
path.isAbsolute(relativeReportPath)
23+
) {
24+
return reportPath;
25+
}
26+
return relativeReportPath.replace(/\\/g, "/");
27+
}
28+
1429
const WikiStatusSchema = Type.Object({}, { additionalProperties: false });
1530
const WikiLintSchema = Type.Object({}, { additionalProperties: false });
1631
const WikiSearchBackendSchema = Type.Union(
@@ -182,6 +197,7 @@ export function createWikiLintTool(
182197
const provenance = result.issuesByCategory.provenance.length;
183198
const errors = result.issues.filter((issue) => issue.severity === "error").length;
184199
const warnings = result.issues.filter((issue) => issue.severity === "warning").length;
200+
const reportPath = formatWikiToolReportPath(config, result.reportPath);
185201
const summary =
186202
result.issueCount === 0
187203
? "No wiki lint issues."
@@ -190,11 +206,16 @@ export function createWikiLintTool(
190206
`Contradictions: ${contradictions}`,
191207
`Open questions: ${openQuestions}`,
192208
`Provenance gaps: ${provenance}`,
193-
`Report: ${result.reportPath}`,
209+
`Report: ${reportPath}`,
194210
].join("\n");
195211
return {
196212
content: [{ type: "text", text: summary }],
197-
details: result,
213+
details: {
214+
issueCount: result.issueCount,
215+
issues: result.issues,
216+
issuesByCategory: result.issuesByCategory,
217+
reportPath,
218+
},
198219
};
199220
},
200221
};

0 commit comments

Comments
 (0)