Skip to content

Commit 9dc7bd4

Browse files
clawsweeper[bot]LLagoon3Takhoffman
authored
fix(memory-wiki): make wiki_lint tool output path-safe (#83687)
Summary: - The PR updates the memory-wiki `wiki_lint` tool to show vault-relative lint report paths in tool text and details, keeps the core linter/CLI result absolute, adds regression coverage, and adds a changelog entry. - Reproducibility: yes. there is a high-confidence source reproduction path: current main returns the linter's ... tPath` in `wiki_lint` text and raw details. I did not execute the harness because this review is read-only. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(memory-wiki): make wiki_lint tool output path-safe Validation: - ClawSweeper review passed for head df5c7db. - Required merge gates passed before the squash merge. Prepared head SHA: df5c7db Review: #83687 (comment) Co-authored-by: LLagoon3 <choonarm3@gmail.com> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
1 parent 38f11a0 commit 9dc7bd4

3 files changed

Lines changed: 67 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Docs: https://docs.openclaw.ai
4444

4545
- Agents/image generation: allow distinct `image_generate` prompts to start separate session-backed background tasks while same-prompt retries still return the active task status. (#83614) Thanks @Elarwei001.
4646
- Sessions: skip trailing custom transcript entries when checking tail assistant replies so embedded CLI gap-fill does not duplicate canonical assistant output. (#83635) Thanks @yaoyi1222.
47+
- Memory Wiki: keep `wiki_lint` tool output path-safe by reporting vault-internal lint reports as relative paths in tool text and details while preserving absolute report paths for CLI/file callers. (#83439) Thanks @LLagoon3.
4748
- Telegram: keep verbose tool progress visible without mirroring non-final progress into active session transcripts, preventing embedded provider replies from aborting mid-run. (#83631) Thanks @kurplunkin.
4849
- Cron: link isolated scheduled task runs to their stable cron session so task status and cleanup can follow the backing agent run. (#83606) Thanks @jai.
4950
- CLI: enforce the documented Node.js 22.19 runtime floor in the source launcher.

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)