Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/aidd-context/hooks/update_memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ function updateBlock(content, innerContent) {

// memory/-relative path, e.g. aidd_docs/memory/internal/x.md -> internal/x.md.
function memoryRelative(path, filePath) {
return filePath.replace(/\\/g, "/").replace(`${memoryPath(path)}/`, "");
return filePath.replace(/\\/g, "/").replace(`${memoryPath(path).replace(/\\/g, "/")}/`, "");
}

function buildToc(rootFiles, onDemandFiles, path) {
Expand Down
31 changes: 30 additions & 1 deletion scripts/__tests__/update-memory.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ const test = require("node:test");
// where a "type": "module" package.json decides how it is parsed. Driving it as a
// subprocess is the only way to test what actually ships.
const HOOK = path.resolve(__dirname, "../../plugins/aidd-context/hooks/update_memory.js");
// Run from inside a Claude Code session, an inherited CLAUDE_PROJECT_DIR would send the hook
// into that real project and rewrite its CLAUDE.md and memory README.
const HOOK_ENV = { ...process.env, CLAUDE_PROJECT_DIR: "" };

const OPEN = "<!-- aidd_project_memory:start -->";
const CLOSE = "<!-- aidd_project_memory:end -->";
Expand Down Expand Up @@ -49,7 +52,7 @@ function run({
fs.copyFileSync(HOOK, hook);
}

const invoke = () => spawnSync(process.execPath, [hook, ...(args ?? ["claude"])], { cwd: root });
const invoke = () => spawnSync(process.execPath, [hook, ...(args ?? ["claude"])], { cwd: root, env: HOOK_ENV });
const read = () => fs.readFileSync(path.join(root, contextAt), "utf8");

const first = invoke();
Expand Down Expand Up @@ -231,3 +234,29 @@ test("a nested context file prefixes its links with the climb back out", () => {
/^\[aidd_docs\/memory\/architecture\.md\]\(\.\.\/aidd_docs\/memory\/architecture\.md\)$/mu,
);
});

// The README sits inside the memory directory, so its links are relative to it. The path
// is joined with the platform separator but the file paths are normalised to "/", so a
// replace on the joined form silently matches nothing on Windows.
test("the memory README lists its files relative to itself, on every platform's separator", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "update-memory-"));
try {
const memory = path.join(root, "aidd_docs/memory");
fs.mkdirSync(path.join(memory, "internal/decisions"), { recursive: true });
fs.writeFileSync(path.join(memory, "architecture.md"), "# x\n");
fs.writeFileSync(path.join(memory, "internal/decisions/x.md"), "# x\n");
fs.writeFileSync(path.join(memory, "README.md"), "<!-- files:start -->\n<!-- files:end -->\n");
fs.writeFileSync(path.join(root, "CLAUDE.md"), `${OPEN}\n${CLOSE}\n`);

const result = spawnSync(process.execPath, [HOOK, "claude"], { cwd: root, env: HOOK_ENV });
assert.equal(result.status, 0, result.stderr.toString());

const readme = fs.readFileSync(path.join(memory, "README.md"), "utf8");
const toc = readme.slice(readme.indexOf("<!-- files:start -->"), readme.indexOf("<!-- files:end -->"));
assert.match(toc, /^- \[architecture\.md\]\(architecture\.md\)$/mu);
assert.match(toc, /^- \[internal\/decisions\/x\.md\]\(internal\/decisions\/x\.md\)$/mu);
assert.doesNotMatch(toc, /aidd_docs\/memory\//u);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
Loading