59 lines
3.0 KiB
TypeScript
59 lines
3.0 KiB
TypeScript
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
|
//
|
|
// The coding-agent contract this template ships: CLAUDE.md imports AGENTS.md instead of repeating it, the
|
|
// committed Claude Code settings keep secrets unreadable and a push behind a person, the support-report skill is
|
|
// readable by its tool, and the format-on-edit hook formats only this repository's own source files.
|
|
|
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
|
import { tmpdir } from "node:os"
|
|
import { join } from "node:path"
|
|
import { afterEach, describe, expect, it } from "vitest"
|
|
import { fileToFormat } from "../scripts/agent-format.mjs"
|
|
|
|
const APP_ROOT = new URL("..", import.meta.url).pathname
|
|
const read = (path: string) => readFileSync(join(APP_ROOT, path), "utf8")
|
|
|
|
const made: string[] = []
|
|
afterEach(() => {
|
|
for (const dir of made.splice(0)) rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
describe("the coding-agent contract", () => {
|
|
it("has CLAUDE.md import AGENTS.md, the one source of the rules", () => {
|
|
expect(read("CLAUDE.md")).toMatch(/^@AGENTS\.md$/m)
|
|
expect(read("AGENTS.md")).toContain("## Work that starts from a support report")
|
|
})
|
|
|
|
it("commits Claude Code settings that refuse .env reads and ask before a push or a migration run", () => {
|
|
const settings = JSON.parse(read(".claude/settings.json"))
|
|
expect(settings.permissions.deny).toEqual(expect.arrayContaining(["Read(/.env)", "Read(/.env.local)"]))
|
|
expect(settings.permissions.ask).toEqual(expect.arrayContaining(["Bash(git push *)", "Bash(pnpm migrate *)"]))
|
|
expect(settings.permissions.allow).toContain("Bash(pnpm gate)")
|
|
// Nothing that publishes or deploys is pre-approved.
|
|
for (const rule of settings.permissions.allow) expect(rule).not.toMatch(/push|migrate|deploy/)
|
|
expect(read(".gitignore")).toContain(".claude/settings.local.json")
|
|
})
|
|
|
|
it("ships the support-report skill with the frontmatter its tool reads first", () => {
|
|
const skill = read(".claude/skills/fix-support-report/SKILL.md")
|
|
expect(skill.startsWith("---\nname: fix-support-report\ndescription: ")).toBe(true)
|
|
expect(skill).toMatch(/Reproduce it before changing code/)
|
|
})
|
|
})
|
|
|
|
describe("the format-on-edit hook", () => {
|
|
it("names a repository source file Biome formats, and nothing else", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "agent-format-"))
|
|
made.push(root)
|
|
writeFileSync(join(root, "a.ts"), "export const a = 1\n")
|
|
writeFileSync(join(root, "notes.md"), "# notes\n")
|
|
expect(fileToFormat({ tool_input: { file_path: join(root, "a.ts") } }, root)).toBe("a.ts")
|
|
expect(fileToFormat({ tool_input: { file_path: "a.ts" } }, root)).toBe("a.ts")
|
|
expect(fileToFormat({ tool_input: { file_path: "notes.md" } }, root)).toBeNull()
|
|
expect(fileToFormat({ tool_input: { file_path: "missing.ts" } }, root)).toBeNull()
|
|
expect(fileToFormat({ tool_input: { file_path: "../outside.ts" } }, root)).toBeNull()
|
|
expect(fileToFormat({ tool_input: {} }, root)).toBeNull()
|
|
expect(fileToFormat(null, root)).toBeNull()
|
|
})
|
|
})
|