feat: auto-register plugin-compaction-init slash command

Adds a custom OpenCode command that scaffolds the compaction memory
file with ## Keep and ## Discard sections of classification rules.
The configured memoryFile path is substituted into both the command
description and the prompt template, and any arguments passed after
the command name are appended as user instructions.
This commit is contained in:
2026-08-11 12:48:11 +05:30
parent edb5bb47f4
commit e8c76ea922
4 changed files with 165 additions and 1 deletions
+106 -1
View File
@@ -2,7 +2,10 @@ import { afterEach, describe, expect, test } from "bun:test";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { CompactionPromptPlugin } from "../src/index.js";
import {
buildInitCommandTemplate,
CompactionPromptPlugin,
} from "../src/index.js";
const temporaryDirectories: string[] = [];
@@ -159,3 +162,105 @@ describe("CompactionPromptPlugin", () => {
);
});
});
describe("buildInitCommandTemplate", () => {
test("substitutes the configured memory file path verbatim", () => {
const template = buildInitCommandTemplate("docs/agent-memory.md");
expect(template).toContain("`docs/agent-memory.md`");
expect(template).not.toContain(".opencode/compaction.md");
});
test("contains the file skeleton and both section headings", () => {
const template = buildInitCommandTemplate(".opencode/compaction.md");
expect(template).toContain("```\nKeep all details related to");
expect(template).toContain("## Keep\n");
expect(template).toContain("## Discard\n");
});
test("leaves $ARGUMENTS$ unsubstituted for OpenCode to fill in", () => {
const template = buildInitCommandTemplate(".opencode/compaction.md");
expect(template.trimEnd().endsWith("$ARGUMENTS$")).toBe(true);
});
});
describe("CompactionPromptPlugin config hook", () => {
test("registers the plugin-compaction-init command by default", async () => {
const context = await createContext();
const hooks = await CompactionPromptPlugin(context as never);
const cfg: {
command?: Record<string, { description: string; template: string }>;
} = {};
await hooks.config?.(cfg as never);
expect(cfg.command?.["plugin-compaction-init"]).toBeDefined();
expect(
cfg.command?.["plugin-compaction-init"].description.length,
).toBeGreaterThan(0);
expect(cfg.command?.["plugin-compaction-init"].template).toContain(
".opencode/compaction.md",
);
expect(cfg.command?.["plugin-compaction-init"].template).toContain(
"## Keep",
);
expect(cfg.command?.["plugin-compaction-init"].template).toContain(
"## Discard",
);
expect(cfg.command?.["plugin-compaction-init"].template).toContain(
"$ARGUMENTS$",
);
expect(context.logs).toHaveLength(0);
});
test("uses the configured memoryFile in the registered template", async () => {
const context = await createContext();
const hooks = await CompactionPromptPlugin(context as never, {
memoryFile: "notes/agent.md",
});
const cfg: {
command?: Record<string, { description: string; template: string }>;
} = {};
await hooks.config?.(cfg as never);
expect(cfg.command?.["plugin-compaction-init"].template).toContain(
"`notes/agent.md`",
);
expect(cfg.command?.["plugin-compaction-init"].template).not.toContain(
".opencode/compaction.md",
);
});
test("skips registration and logs a warning when the command is already defined", async () => {
const context = await createContext();
const hooks = await CompactionPromptPlugin(context as never);
const userTemplate = "user-provided template body";
const cfg: {
command?: Record<string, { description: string; template: string }>;
} = {
command: {
"plugin-compaction-init": {
description: "user description",
template: userTemplate,
},
},
};
await hooks.config?.(cfg as never);
expect(cfg.command?.["plugin-compaction-init"].template).toBe(userTemplate);
expect(context.logs).toHaveLength(1);
expect(context.logs[0]).toMatchObject({
body: {
level: "warn",
service: "opencode-plugin-compaction-prompt",
},
});
expect(
(context.logs[0] as { body?: { message?: string } }).body?.message,
).toContain("plugin-compaction-init");
});
});