feat: add setup skill/hooks and update shadow (task 0018)
All checks were successful
CI / test (pull_request) Successful in 52s
CI / test (push) Successful in 51s

Distribute gitea-axi's ambient context via explicit user actions (ADRs
0009, 0013), with no postinstall script:

- Bundle the Agent Skill markdown at skills/gitea-axi/SKILL.md (a
  minimal pointer, not a command reference) and ship it via package.json
  files.
- Add `setup`, which installs the skill into ~/.claude/skills/
  idempotently (installed/updated/unchanged).
- Add `setup hooks`, which registers a SessionStart hook running the
  bare dashboard for Claude Code, Codex, and OpenCode via the SDK's
  installSessionStartHooks(), updating managed entries in place.
- Shadow the SDK's built-in `update` so it fails with VALIDATION_ERROR
  and points at the npm update command, keeping the ten-code error list
  intact.

Integration tests drive all three at the CLI seam against a temporary
HOME; these commands make no Gitea API calls, so there is no live-Gitea
e2e case.
This commit was merged in pull request #19.
This commit is contained in:
2026-07-14 11:26:56 -04:00
parent 5b6a9b4917
commit be7226b321
9 changed files with 402 additions and 6 deletions

54
test/skill.test.ts Normal file
View File

@@ -0,0 +1,54 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
const skill = readFileSync(
new URL("../skills/gitea-axi/SKILL.md", import.meta.url),
"utf8",
);
const packageJson = JSON.parse(
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
) as { files?: string[] };
describe("bundled Agent Skill markdown", () => {
it("is published in the package", () => {
expect(packageJson.files).toContain("skills");
});
it("has frontmatter with a description that triggers on Gitea issue/PR work", () => {
// Frontmatter is a leading `---`-delimited block.
const match = skill.match(/^---\n([\s\S]*?)\n---/);
const frontmatter = match?.[1];
expect(frontmatter, "expected YAML frontmatter delimited by ---").toBeDefined();
expect(frontmatter).toMatch(/description:/i);
const description = frontmatter!.toLowerCase();
expect(description).toContain("gitea");
expect(description).toContain("issue");
expect(description).toContain("pull request");
});
it("says to prefer gitea-axi over tea, raw API, and git", () => {
const body = skill.toLowerCase();
expect(body).toContain("tea");
expect(body).toContain("api");
expect(body).toContain("git");
});
it("references each command group as a one-liner", () => {
const body = skill.toLowerCase();
for (const group of ["issue", "pr", "label", "search", "setup"]) {
expect(body, `expected the skill to mention the ${group} command group`).toContain(
group,
);
}
});
it("points at the bare dashboard and per-command help for discovery", () => {
const body = skill.toLowerCase();
// Bare dashboard: running the binary with no arguments.
expect(body).toContain("no argument");
expect(body).toContain("dashboard");
// Per-command help.
expect(body).toContain("--help");
});
});