diff --git a/AGENTS.md b/AGENTS.md index 3dc4490..1395f46 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,6 +24,10 @@ Do not hand-edit `CHANGELOG.md` or `.release-please-manifest.json` (a guard work Every `pull_request` workflow (`ci.yml`, `guard-generated-files.yml`, `no-mistakes-required.yml`) uses `paths-ignore` for the release-please output set (`.release-please-manifest.json`, `CHANGELOG.md`, `package.json`) so release PRs create zero runs. Job-level bot `if`s stay as defense in depth. `test/release-ci-exclusions.test.ts` derives that set from `release-please-config.json` and fails if a workflow drifts; update the ignore lists when adding `extra-files` or changing `release-type`. +## Installable skill (`src/skill.ts` → `skills/gh-axi/SKILL.md`) + +The shipped skill stays a minimal stub and defers to the CLI for all actual guidance. gh-axi CLI output (`gh-axi` dashboard, `gh-axi --help`, `gh-axi --help`) is the single source of truth. Never re-duplicate CLI-owned instructions into the skill; prefer a pointer over restated detail. + ## GitHub Enterprise host support (`src/host.ts`, `src/cli.ts`) `gh-axi` targets a custom GitHub host (e.g. a GHE server like `ghe.example.com`) via a global `--hostname ` flag or the `GH_HOST` env var; explicit `--hostname` wins. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a2f9e7e..4c0a0f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,7 @@ See the [no-mistakes quick start](https://kunchenguid.github.io/no-mistakes/star - Do not bump `package.json`'s `version` by hand for ordinary changes. release-please updates it in the release PR. - Do not hand-edit `skills/gh-axi/SKILL.md`. - It is generated from the shared skill source, including frontmatter metadata; run `pnpm run build:skill` and commit the result. + It is generated from `src/skill.ts`, including frontmatter metadata; run `pnpm run build:skill` and commit the result. ## Questions diff --git a/README.md b/README.md index 17d4028..1c6a65a 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ npx skills add kunchenguid/gh-axi --skill gh-axi -g ``` That is the entire setup - no npm install needed. -The skill teaches your agent to run gh-axi through `npx -y gh-axi`, so the CLI comes along on demand. +The skill is a minimal discovery stub that directs your agent to the always-current `npx -y gh-axi` dashboard and help output instead of duplicating command guidance. You still need [`gh`](https://cli.github.com/) installed and authenticated via `gh auth login` (Node 20+ required). Stacked PR commands also require GitHub's official extension: `gh extension install github/gh-stack`. For GitHub Enterprise or another custom host, authenticate `gh` for that host and either pass `--hostname ` after the command or set `GH_HOST`. @@ -205,13 +205,14 @@ When a command also needs a destination repository, use a dedicated flag for it: ```sh pnpm run build # Compile TypeScript to dist/ -pnpm run build:skill # Regenerate skills/gh-axi/SKILL.md from shared skill source +pnpm run build:skill # Regenerate skills/gh-axi/SKILL.md from src/skill.ts pnpm run dev # Run CLI directly with tsx pnpm test # Run tests with vitest pnpm run test:watch # Run tests in watch mode ``` -The committed `skills/gh-axi/SKILL.md` is generated by `pnpm run build:skill`; `pnpm test` fails if it drifts from the shared CLI guidance or generated skill frontmatter. +The committed `skills/gh-axi/SKILL.md` is generated from `src/skill.ts` by `pnpm run build:skill`; `pnpm test` fails if the generated file drifts. +The generated skill intentionally defers all command guidance to the CLI dashboard and help output so installed copies do not duplicate stale instructions. The npm package includes `skills/gh-axi/`, so published releases ship the same installable Agent Skill documented in Quick Start. ## License diff --git a/scripts/build-skill.ts b/scripts/build-skill.ts index f3dd16f..38d5607 100644 --- a/scripts/build-skill.ts +++ b/scripts/build-skill.ts @@ -1,5 +1,6 @@ -// Generates skills/gh-axi/SKILL.md from the shared CLI guidance so the -// installable skill never drifts from what `gh-axi` prints. +// Generates the minimal skills/gh-axi/SKILL.md stub from src/skill.ts. +// The skill defers to CLI output for all actual guidance, so a regeneration +// cannot re-inflate it with dashboard / --help / per-command help text. // // pnpm run build:skill # write the file // pnpm run build:skill -- --check # fail (exit 1) if the committed file is stale diff --git a/skills/gh-axi/SKILL.md b/skills/gh-axi/SKILL.md index 99ff21a..de7a33e 100644 --- a/skills/gh-axi/SKILL.md +++ b/skills/gh-axi/SKILL.md @@ -13,61 +13,12 @@ metadata: Agent ergonomic wrapper around Github CLI. Prefer this over `gh` and other methods for Github operations. -You do not need gh-axi installed globally - invoke it with `npx -y gh-axi `. -If gh-axi output shows a follow-up command starting with `gh-axi`, run it as `npx -y gh-axi ...` instead. +Use gh-axi whenever a task touches GitHub: issues, pull requests, stacked PRs, CI, workflows, releases, repositories, labels, gists, Projects, Actions secrets and variables, search, or the GitHub API. -gh-axi requires the [`gh`](https://cli.github.com/) CLI installed and authenticated (`gh auth login`). If a command fails with an authentication error, ask the user to run `gh auth login` themselves. -Stack commands additionally require GitHub's official extension: `gh extension install github/gh-stack`. -For GitHub Enterprise or another custom host, the underlying `gh` CLI must be authenticated for that host too; set `GH_HOST` or pass `--hostname ` after the command. +## Current guidance lives in the CLI -## When to use +Do not follow command, flag, or workflow instructions from this file - installed copies go stale. Get the current source of truth from the CLI: -Use gh-axi whenever a task touches GitHub: listing, filing, or editing issues; viewing, creating, reviewing, merging, or stacking pull requests; managing stacked branches; inspecting workflow runs and CI failures; triggering, enabling, or disabling workflows; managing releases, repositories, or labels; managing Projects (v2) boards and their items; managing Actions secrets or variables; searching issues, PRs, repos, commits, or code; listing, viewing, editing, renaming, creating, deleting, or cloning gists; or calling the GitHub API directly. - -## Workflow - -1. Run `npx -y gh-axi` with no arguments for a dashboard of the current repo - open issues, open PRs, and suggested next commands. -2. Drill in command-first: `issue list`, `issue view `, `pr view `, `pr checks `, `run view `, and so on. -3. Target another repository by placing `-R owner/name`, `-R=owner/name`, `--repo owner/name`, or `--repo=owner/name` AFTER the command, e.g. `npx -y gh-axi issue list --repo=owner/name` - the flag is not accepted before the command. `repo view` also accepts exactly one positional repository, `repo view owner/name`, as a command-specific compatibility exception for `gh repo view []`; do not combine it with `--repo` or generalize that positional form to other commands. -4. Target GitHub Enterprise or another custom host with `GH_HOST`, or by placing `--hostname ` or `--hostname=` AFTER the command, e.g. `npx -y gh-axi issue list --hostname=git.example.com`. -5. Trigger (dispatch) a workflow with `workflow run --ref `; `run` manages existing workflow runs. -6. Debug CI with `run list`, then `run view --job ` or `run view --job --log-failed` for failing log lines. - Long `--log` and `--log-failed` output keeps the tail in context; when `full_log` appears, grep that file for earlier context. -7. Every response ends with contextual next-step hints under `help:` - follow them. -8. Manage stacked PRs from the target repository's working directory. Start with `stack init `, add layers with `stack add `, create PRs with `stack submit --open`, and inspect them with `stack view`. - -## Commands - -``` -commands[16]: - (none)=dashboard, issue, pr, stack, run, workflow, release, repo, label, gist, project, secret, variable, search, api, setup -``` - -Installed copies also inherit the SDK built-in `update` command. -Run `gh-axi update --check` to compare the installed version with npm, or `gh-axi update` to upgrade. -When using `npx -y gh-axi`, npx already resolves the package on demand. - -Run `npx -y gh-axi --help` for global flags, or `npx -y gh-axi --help` for per-command usage. - -## Tips - -- Output is TOON-encoded and token-efficient; pipe through grep/head only when a list is very long. -- Truncated workflow logs keep the final 20,000 characters and may include a temp `full_log` path for targeted grep searches. -- Most mutations are idempotent and report what changed. Stack branch creation and partial pushes require checking the reported status before retrying. -- Stack operations are cwd-bound and do not accept `-R`, `--repo`, or `GH_REPO`. They preserve the official extension's recovery exits and may partially push branches; inspect the reported status before retrying. -- gh-axi keeps stack operations headless: `stack view` always uses JSON, `stack submit` always uses `--auto`, and `stack merge ` always uses `--yes`. Interactive `gh stack modify` and `gh stack switch` are intentionally not exposed. -- For multi-line markdown bodies, comments, or release notes, write the text to a UTF-8 file and pass `--body-file ` or the release `--notes-file ` alias on commands that support file-backed text. -- Label, assignee, reviewer, and project flags repeat: pass the flag once per value, e.g. `issue edit 42 --add-label bug --add-label chore`, and every value is applied. A repeated flag with a missing or blank value is rejected, never silently dropped. -- Secret values are stdin-only: `echo -n "" | npx -y gh-axi secret set `. -- Do not pass secrets with `--body` or `-b`; flags are visible in the `gh-axi` process argv. -- Scope a secret to a deployment environment with `--env`/`-e ` on `secret list`, `set`, and `delete`; omit it for repository scope. Other `gh secret` scopes (`--org`, `--user`, `--app`) are rejected, not silently ignored. -- Variable values may use `--body`/`-b` or stdin because Actions variables are not secret. -- For multi-line variable values, pipe stdin to `npx -y gh-axi variable set `; `--body`/`-b` is for inline values only. -- Projects (v2) are owner-scoped: pass `--owner `, or omit it to use the current repo owner and then `@me`. -- Projects calls need the `project` or `read:project` OAuth scope; if scope errors occur, ask the user to run the `gh auth refresh -s ...` command shown by gh-axi. -- Use `gist list` to list your GitHub Gists; filter by visibility with `--public` or `--secret`, and add extra fields with `--fields url,owner,created`. Use `gist view ` to fetch a gist's metadata and file content; pass `--files` for names only, `-f/--filename ` for a single file, or `--full` to disable truncation. -- Use `gist edit ` to update a gist's files or description: pipe content via stdin with `--filename ` to replace or add a file, `--add ` to add from disk, `--remove ` to remove, and `--desc ` to update the description. `gist edit` never opens $EDITOR. -- Use `gist rename ` to rename a file within a gist. -- Use `gist create` to create a gist. Visibility is required: pass `--public` or `--secret` (omitting either, or passing both, is an error). Use positional paths (`gist create a.py b.py`) or repeatable `--file` flags; do not mix the two. Pipe content with `--filename ` for stdin input. A secret gist is unlisted — anyone with the URL can read it. -- Use `gist delete ` to delete a gist (always confirmed non-interactively). Use `gist clone ` to clone a gist locally. -- Use `api` for anything the dedicated commands do not cover, e.g. `npx -y gh-axi api repos/{owner}/{repo}/topics`. +- `npx -y gh-axi` for a dashboard of the current repo +- `npx -y gh-axi --help` for global flags and the command index +- `npx -y gh-axi --help` for per-command usage diff --git a/src/skill.ts b/src/skill.ts index 434cb96..ebbe842 100644 --- a/src/skill.ts +++ b/src/skill.ts @@ -1,4 +1,4 @@ -import { DESCRIPTION, TOP_HELP } from "./cli.js"; +import { DESCRIPTION } from "./cli.js"; // Trigger string Claude Code (and other agents) match against to auto-load the skill. // Kept terse and outcome-focused so it fires on "needs GitHub" intents. @@ -23,32 +23,27 @@ export const HERMES_TAGS = [ ]; export const HERMES_CATEGORY = "devops"; +// Hard cap so a future regeneration cannot silently re-inflate the stub with +// CLI-owned instructions. Dashboard, `--help`, and per-command help are the +// source of truth. +export const MAX_SKILL_MARKDOWN_CHARS = 2500; + function yamlDoubleQuote(value: string): string { return JSON.stringify(value); } /** - * Extract the `commands[N]:` block from the top-level help so the skill's - * command list can never drift from what `gh-axi --help` prints. - */ -export function extractCommandsBlock(): string { - const match = TOP_HELP.match(/^(commands\[\d+\]:\n(?: {2}.*\n)+)/m); - if (!match) { - throw new Error("Could not find commands block in TOP_HELP"); - } - return match[1].trimEnd(); -} - -/** - * Render the installable SKILL.md for the gh-axi skill. The body is built - * from the same shared guidance the CLI prints (description and top-level - * help), rewriting invocations to non-interactive `npx -y gh-axi ...` so the - * CLI comes along on demand. + * Render the installable SKILL.md for the gh-axi skill. + * + * This is a discovery stub, not a copy of CLI guidance. Installed skills go + * stale; `gh-axi` (dashboard), `gh-axi --help`, and `gh-axi --help` + * do not. Keep the body to what gh-axi is, when to reach for it, and pointers + * at those commands. * * @returns full SKILL.md contents including YAML frontmatter */ export function createSkillMarkdown(): string { - return `--- + const markdown = `--- name: gh-axi description: ${yamlDoubleQuote(SKILL_DESCRIPTION)} user-invocable: false @@ -63,62 +58,22 @@ metadata: ${DESCRIPTION} -You do not need gh-axi installed globally - invoke it with \`npx -y gh-axi \`. -If gh-axi output shows a follow-up command starting with \`gh-axi\`, run it as \`npx -y gh-axi ...\` instead. - -gh-axi requires the [\`gh\`](https://cli.github.com/) CLI installed and authenticated (\`gh auth login\`). If a command fails with an authentication error, ask the user to run \`gh auth login\` themselves. -Stack commands additionally require GitHub's official extension: \`gh extension install github/gh-stack\`. -For GitHub Enterprise or another custom host, the underlying \`gh\` CLI must be authenticated for that host too; set \`GH_HOST\` or pass \`--hostname \` after the command. +Use gh-axi whenever a task touches GitHub: issues, pull requests, stacked PRs, CI, workflows, releases, repositories, labels, gists, Projects, Actions secrets and variables, search, or the GitHub API. -## When to use +## Current guidance lives in the CLI -Use gh-axi whenever a task touches GitHub: listing, filing, or editing issues; viewing, creating, reviewing, merging, or stacking pull requests; managing stacked branches; inspecting workflow runs and CI failures; triggering, enabling, or disabling workflows; managing releases, repositories, or labels; managing Projects (v2) boards and their items; managing Actions secrets or variables; searching issues, PRs, repos, commits, or code; listing, viewing, editing, renaming, creating, deleting, or cloning gists; or calling the GitHub API directly. +Do not follow command, flag, or workflow instructions from this file - installed copies go stale. Get the current source of truth from the CLI: -## Workflow - -1. Run \`npx -y gh-axi\` with no arguments for a dashboard of the current repo - open issues, open PRs, and suggested next commands. -2. Drill in command-first: \`issue list\`, \`issue view \`, \`pr view \`, \`pr checks \`, \`run view \`, and so on. -3. Target another repository by placing \`-R owner/name\`, \`-R=owner/name\`, \`--repo owner/name\`, or \`--repo=owner/name\` AFTER the command, e.g. \`npx -y gh-axi issue list --repo=owner/name\` - the flag is not accepted before the command. \`repo view\` also accepts exactly one positional repository, \`repo view owner/name\`, as a command-specific compatibility exception for \`gh repo view []\`; do not combine it with \`--repo\` or generalize that positional form to other commands. -4. Target GitHub Enterprise or another custom host with \`GH_HOST\`, or by placing \`--hostname \` or \`--hostname=\` AFTER the command, e.g. \`npx -y gh-axi issue list --hostname=git.example.com\`. -5. Trigger (dispatch) a workflow with \`workflow run --ref \`; \`run\` manages existing workflow runs. -6. Debug CI with \`run list\`, then \`run view --job \` or \`run view --job --log-failed\` for failing log lines. - Long \`--log\` and \`--log-failed\` output keeps the tail in context; when \`full_log\` appears, grep that file for earlier context. -7. Every response ends with contextual next-step hints under \`help:\` - follow them. -8. Manage stacked PRs from the target repository's working directory. Start with \`stack init \`, add layers with \`stack add \`, create PRs with \`stack submit --open\`, and inspect them with \`stack view\`. - -## Commands - -\`\`\` -${extractCommandsBlock()} -\`\`\` - -Installed copies also inherit the SDK built-in \`update\` command. -Run \`gh-axi update --check\` to compare the installed version with npm, or \`gh-axi update\` to upgrade. -When using \`npx -y gh-axi\`, npx already resolves the package on demand. - -Run \`npx -y gh-axi --help\` for global flags, or \`npx -y gh-axi --help\` for per-command usage. +- \`npx -y gh-axi\` for a dashboard of the current repo +- \`npx -y gh-axi --help\` for global flags and the command index +- \`npx -y gh-axi --help\` for per-command usage +`; -## Tips + if (markdown.length > MAX_SKILL_MARKDOWN_CHARS) { + throw new Error( + `generated SKILL.md is ${markdown.length} chars; keep it a stub under ${MAX_SKILL_MARKDOWN_CHARS} and defer guidance to the CLI`, + ); + } -- Output is TOON-encoded and token-efficient; pipe through grep/head only when a list is very long. -- Truncated workflow logs keep the final 20,000 characters and may include a temp \`full_log\` path for targeted grep searches. -- Most mutations are idempotent and report what changed. Stack branch creation and partial pushes require checking the reported status before retrying. -- Stack operations are cwd-bound and do not accept \`-R\`, \`--repo\`, or \`GH_REPO\`. They preserve the official extension's recovery exits and may partially push branches; inspect the reported status before retrying. -- gh-axi keeps stack operations headless: \`stack view\` always uses JSON, \`stack submit\` always uses \`--auto\`, and \`stack merge \` always uses \`--yes\`. Interactive \`gh stack modify\` and \`gh stack switch\` are intentionally not exposed. -- For multi-line markdown bodies, comments, or release notes, write the text to a UTF-8 file and pass \`--body-file \` or the release \`--notes-file \` alias on commands that support file-backed text. -- Label, assignee, reviewer, and project flags repeat: pass the flag once per value, e.g. \`issue edit 42 --add-label bug --add-label chore\`, and every value is applied. A repeated flag with a missing or blank value is rejected, never silently dropped. -- Secret values are stdin-only: \`echo -n "" | npx -y gh-axi secret set \`. -- Do not pass secrets with \`--body\` or \`-b\`; flags are visible in the \`gh-axi\` process argv. -- Scope a secret to a deployment environment with \`--env\`/\`-e \` on \`secret list\`, \`set\`, and \`delete\`; omit it for repository scope. Other \`gh secret\` scopes (\`--org\`, \`--user\`, \`--app\`) are rejected, not silently ignored. -- Variable values may use \`--body\`/\`-b\` or stdin because Actions variables are not secret. -- For multi-line variable values, pipe stdin to \`npx -y gh-axi variable set \`; \`--body\`/\`-b\` is for inline values only. -- Projects (v2) are owner-scoped: pass \`--owner \`, or omit it to use the current repo owner and then \`@me\`. -- Projects calls need the \`project\` or \`read:project\` OAuth scope; if scope errors occur, ask the user to run the \`gh auth refresh -s ...\` command shown by gh-axi. -- Use \`gist list\` to list your GitHub Gists; filter by visibility with \`--public\` or \`--secret\`, and add extra fields with \`--fields url,owner,created\`. Use \`gist view \` to fetch a gist's metadata and file content; pass \`--files\` for names only, \`-f/--filename \` for a single file, or \`--full\` to disable truncation. -- Use \`gist edit \` to update a gist's files or description: pipe content via stdin with \`--filename \` to replace or add a file, \`--add \` to add from disk, \`--remove \` to remove, and \`--desc \` to update the description. \`gist edit\` never opens $EDITOR. -- Use \`gist rename \` to rename a file within a gist. -- Use \`gist create\` to create a gist. Visibility is required: pass \`--public\` or \`--secret\` (omitting either, or passing both, is an error). Use positional paths (\`gist create a.py b.py\`) or repeatable \`--file\` flags; do not mix the two. Pipe content with \`--filename \` for stdin input. A secret gist is unlisted — anyone with the URL can read it. -- Use \`gist delete \` to delete a gist (always confirmed non-interactively). Use \`gist clone \` to clone a gist locally. -- Use \`api\` for anything the dedicated commands do not cover, e.g. \`npx -y gh-axi api repos/{owner}/{repo}/topics\`. -`; + return markdown; } diff --git a/test/skill.test.ts b/test/skill.test.ts index 866a972..48329fb 100644 --- a/test/skill.test.ts +++ b/test/skill.test.ts @@ -1,11 +1,12 @@ import { readFileSync } from "node:fs"; import { describe, it, expect } from "vitest"; import { parse } from "yaml"; +import { DESCRIPTION, TOP_HELP } from "../src/cli.js"; import { createSkillMarkdown, - extractCommandsBlock, HERMES_CATEGORY, HERMES_TAGS, + MAX_SKILL_MARKDOWN_CHARS, SKILL_AUTHOR, SKILL_DESCRIPTION, } from "../src/skill.js"; @@ -18,6 +19,14 @@ function parseFrontmatter(markdown: string): Record { return parse(match[1], { strict: true }) as Record; } +function skillBody(markdown: string): string { + const end = markdown.indexOf("\n---\n", 3); + if (end < 0) { + throw new Error("Missing frontmatter closer"); + } + return markdown.slice(end + 5); +} + describe("createSkillMarkdown", () => { it("matches the committed skills/gh-axi/SKILL.md", () => { const committed = readFileSync( @@ -63,30 +72,28 @@ describe("createSkillMarkdown", () => { expect(frontmatter).not.toHaveProperty("required_environment_variables"); }); - it("teaches npx invocation instead of assuming a global install", () => { + it("stays a short stub that defers to the CLI", () => { const markdown = createSkillMarkdown(); - expect(markdown).toContain("npx -y gh-axi"); + const body = skillBody(markdown); + expect(markdown.length).toBeLessThanOrEqual(MAX_SKILL_MARKDOWN_CHARS); + expect(body).toContain(DESCRIPTION); + expect(body).toMatch(/whenever a task touches GitHub/i); + expect(body).toContain("npx -y gh-axi"); + expect(body).toContain("npx -y gh-axi --help"); + expect(body).toContain("npx -y gh-axi --help"); + expect(body).toMatch(/stale/i); + expect(body).toMatch( + /- `npx -y gh-axi --help` for per-command usage\n$/, + ); }); - it("documents the gh prerequisite", () => { + it("does not bake CLI-owned guidance into the skill", () => { const markdown = createSkillMarkdown(); - expect(markdown).toContain("gh auth login"); - }); - - it("documents non-interactive stacked PR workflows", () => { - const skill = createSkillMarkdown(); - expect(skill).toContain("gh extension install github/gh-stack"); - expect(skill).toContain("stack submit --open"); - expect(skill).toContain("Stack operations are cwd-bound"); - expect(skill).toContain("stack view` always uses JSON"); - }); -}); - -describe("extractCommandsBlock", () => { - it("pulls the commands list from the top-level help", () => { - const block = extractCommandsBlock(); - expect(block).toMatch(/^commands\[\d+\]:\n/); - expect(block).toContain("issue"); - expect(block).toContain("setup"); + const body = skillBody(markdown); + expect(body).not.toMatch(/^## Commands/m); + expect(body).not.toMatch(/^## Tips/m); + expect(body).not.toMatch(/^## Workflow/m); + expect(body).not.toContain("commands["); + expect(body).not.toContain(TOP_HELP.trim()); }); });