Files
locode/src/plugins/loader.test.ts
T
kim 6fe98887d5 v0.6.0: complete all 12 local-model upgrades + raise maxIterations to 100
Upgrade candidates (all 12 done):
- #1 parallel read-only tool execution (runToolBatch, 4 loop sites)
- #2 configurable retry policy (maxRetries + exponential backoff via SDK)
- #3 script-aware token estimation (CJK/symbol/structure-aware heuristic)
- #4 head+tail output capping (truncate.ts), auto-applies to bash/git/etc
- #5 partial-history compaction (preserve recent tail, summarize older prefix)
- #6 dynamic max_tokens (resolveMaxTokens)
- #7 richer tool descriptions with "use when" guidance
- #8 MCP reconnect retry + /mcp reconnect command + session toolset refresh
- #9 context-window cache TTL (cachedAt timestamp, default 7 days)
- #10 edit_file similar-match suggestion on old_string miss (bounded Levenshtein)
- #11 git_status output head+tail (resolved via #4)
- #12 auto-accept now approves all mutating tools; auto-edit stays edit-only

Other:
- DEFAULT_MAX_ITERATIONS 50 -> 100 (local models issue one tool call per step)
- MaxIterationsError message guides resume + config override
- plus prior known-issues work (grep -e/--, session id sanitization, MCP content
  types, 12 hook events, plugin collisions, skill references, git ops expansion,
  configurable autoCompactThreshold, bashGuard, pathGuard, FilePanel, replay)
2026-08-20 16:52:03 +09:00

67 lines
3.2 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
import path from "node:path";
import os from "node:os";
import { loadPlugin } from "./loader.js";
describe("loadPlugin", () => {
let tempDir: string;
beforeEach(() => {
tempDir = path.join(os.tmpdir(), `locode-plugin-${Math.random().toString(36).slice(2)}`);
mkdirSync(tempDir, { recursive: true });
});
afterEach(() => {
rmSync(tempDir, { recursive: true, force: true });
});
it("loads a skill with references/*.md files", () => {
mkdirSync(path.join(tempDir, ".claude-plugin"), { recursive: true });
mkdirSync(path.join(tempDir, "skills", "dataviz", "references"), { recursive: true });
writeFileSync(
path.join(tempDir, ".claude-plugin", "plugin.json"),
JSON.stringify({ name: "test-plugin" }),
);
writeFileSync(path.join(tempDir, "skills", "dataviz", "SKILL.md"), "---\ndescription: dataviz skill\n---\nmain skill");
writeFileSync(path.join(tempDir, "skills", "dataviz", "references", "colors.md"), "use blue");
const plugin = loadPlugin(tempDir);
expect(plugin.skills).toHaveLength(1);
expect(plugin.skills[0]).toMatchObject({ name: "dataviz", content: "main skill" });
expect(plugin.skills[0]?.references).toEqual([{ name: "colors", content: "use blue" }]);
});
it("loads commands and agents", () => {
mkdirSync(path.join(tempDir, ".claude-plugin"), { recursive: true });
writeFileSync(path.join(tempDir, ".claude-plugin", "plugin.json"), JSON.stringify({ name: "test-plugin" }));
mkdirSync(path.join(tempDir, "commands"), { recursive: true });
writeFileSync(path.join(tempDir, "commands", "hello.md"), "---\ndescription: Say hello\n---\nHello $ARGUMENTS");
mkdirSync(path.join(tempDir, "agents"), { recursive: true });
writeFileSync(path.join(tempDir, "agents", "review.md"), "---\ndescription: Code reviewer\n---\nYou review code.");
const plugin = loadPlugin(tempDir);
expect(plugin.commands).toHaveLength(1);
expect(plugin.commands[0]).toMatchObject({ name: "hello", description: "Say hello" });
expect(plugin.agents).toHaveLength(1);
expect(plugin.agents[0]).toMatchObject({ name: "review", description: "Code reviewer" });
});
it("parses a command's allowed-tools frontmatter, translating Claude Code tool names", () => {
mkdirSync(path.join(tempDir, ".claude-plugin"), { recursive: true });
writeFileSync(path.join(tempDir, ".claude-plugin", "plugin.json"), JSON.stringify({ name: "test-plugin" }));
mkdirSync(path.join(tempDir, "commands"), { recursive: true });
writeFileSync(
path.join(tempDir, "commands", "readonly.md"),
"---\ndescription: Look but don't touch\nallowed-tools: Read, Grep\n---\nInvestigate $ARGUMENTS",
);
writeFileSync(path.join(tempDir, "commands", "unrestricted.md"), "---\ndescription: No restriction\n---\nDo $ARGUMENTS");
const plugin = loadPlugin(tempDir);
const readonly = plugin.commands.find((c) => c.name === "readonly");
const unrestricted = plugin.commands.find((c) => c.name === "unrestricted");
expect(readonly?.allowedTools).toEqual(["read_file", "grep"]);
expect(unrestricted?.allowedTools).toBeUndefined();
});
});