From ea3e8f959fc793a1b16ffe2bd5a65e04d62b732b Mon Sep 17 00:00:00 2001 From: Robin Date: Sun, 12 Jul 2026 01:48:05 +0200 Subject: [PATCH 1/9] feat(skills): add canonical Akua agent skill --- release-please-config.json | 5 + .../agent-skills-standard-following/SKILL.md | 60 +++++++ .../skill-package.json | 16 ++ test/agent-skill.test.ts | 161 ++++++++++++++++++ test/release-please-config.test.ts | 6 + 5 files changed, 248 insertions(+) create mode 100644 skills/agent-skills-standard-following/SKILL.md create mode 100644 skills/agent-skills-standard-following/skill-package.json create mode 100644 test/agent-skill.test.ts diff --git a/release-please-config.json b/release-please-config.json index 5aa86b8..95be370 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -9,6 +9,11 @@ { "type": "generic", "path": "src/bin/akua.ts" + }, + { + "type": "json", + "path": "skills/agent-skills-standard-following/skill-package.json", + "jsonpath": "$.version" } ] } diff --git a/skills/agent-skills-standard-following/SKILL.md b/skills/agent-skills-standard-following/SKILL.md new file mode 100644 index 0000000..078054b --- /dev/null +++ b/skills/agent-skills-standard-following/SKILL.md @@ -0,0 +1,60 @@ +--- +name: agent-skills-standard-following +description: Guide agents working with Akua documentation, workspaces, authentication, generated CLI commands, structured output, and approval-safe operations. Use for Akua setup, inspection, troubleshooting, or change requests. +--- + +# Work with Akua + +Use the most authoritative available surface for each kind of information. Keep discovery and reads separate from mutations, and report what actually happened. + +## Choose the authoritative surface + +1. Prefer the Akua docs MCP for current product and API documentation. Treat remembered documentation, copied examples, and model knowledge as potentially stale. +2. Use the Akua platform MCP and its Code Mode for authoritative workspace state. Read the relevant objects before drawing conclusions or proposing changes. +3. Use the canonical `akua` CLI when it is present and the needed behavior is released. Inspect the installed surface instead of assuming a command or install channel exists. + +If an authoritative surface is unavailable, say which check could not be made. Do not substitute an inferred live state. + +## Inspect the installed CLI + +Start with read-only discovery: + +```sh +command -v akua +akua --version +akua --help --output agent +akua auth status --output agent +akua commands --output agent +``` + +Use `akua commands --resource ` or `akua commands --operation-id ` to narrow the generated public command registry. Registry presence describes the generated surface; it does not prove that execution is implemented. If a command reports that it is not implemented, return to the docs or platform MCP instead of inventing flags or claiming success. + +Never expose tokens from `AKUA_API_TOKEN` or the local config. `akua auth status` reports the effective source without revealing the credential. Treat `akua auth login --token ` and `akua auth logout` as local configuration mutations and run them only when explicitly requested. + +## Select structured output deliberately + +Akua supports `human`, `agent`, `json`, and `quiet` output modes. + +- Set `AGENT=true` or `AGENT=` to select agent-oriented output by default. +- Use `--output agent` for compact, self-describing AGENT output with observations, data, and next steps. +- Use `--json` or `--output json` when a program must parse the result. +- Use `--quiet` only when the exit status is sufficient. + +Check the exit status and structured error payload before reporting success. Do not parse human prose when a structured mode is available. + +## Preserve approvals and mutation boundaries + +- Read current workspace state before planning a change. Use explicit workspace and resource identifiers. +- Do not create, update, delete, deploy, or resolve an approval request unless the human explicitly authorized that action. +- Preserve human approval gates. Do not add `--yes`, `--force`, confirmation responses, or approval resolutions merely to make automation continue. +- In agent, JSON, quiet, CI, or other non-interactive environments, never assume an interactive prompt will protect a mutation. Treat a confirmation-required or unsafe-mutation refusal as a stop condition. +- When a dry-run or preview is documented and available, inspect it before asking for mutation approval. Do not claim a preview changed live state. +- After an authorized mutation, read the authoritative state again and report identifiers, observed status, and any remaining approval or wait step. + +Generated command discovery, desired configuration, and a submitted request are not evidence that live state changed. State clearly when execution is unavailable, refused, awaiting approval, or unverified. + +## Avoid legacy and unavailable paths + +Use only the `akua` executable. Do not suggest CNAP-era binaries, commands, module paths, domains, or compatibility aliases. + +Do not claim that Akua is publicly installable through npm, Homebrew, `npx`, a skills marketplace, or another channel unless the current Akua docs MCP or a published release explicitly confirms it. If `akua` is not installed, report that fact and use the available MCP surfaces; do not invent installation instructions. diff --git a/skills/agent-skills-standard-following/skill-package.json b/skills/agent-skills-standard-following/skill-package.json new file mode 100644 index 0000000..601045f --- /dev/null +++ b/skills/agent-skills-standard-following/skill-package.json @@ -0,0 +1,16 @@ +{ + "schema_version": 1, + "name": "agent-skills-standard-following", + "version": "0.7.0", + "provenance": { + "repository": "https://github.com/akua-dev/cli", + "path": "skills/agent-skills-standard-following/SKILL.md", + "revision_source": "git" + }, + "files": [ + { + "path": "SKILL.md", + "sha256": "49ceb008dce8f6f1fbfc51fdb389f09986556db406331e0b9870006752f877ee" + } + ] +} diff --git a/test/agent-skill.test.ts b/test/agent-skill.test.ts new file mode 100644 index 0000000..28dd4fd --- /dev/null +++ b/test/agent-skill.test.ts @@ -0,0 +1,161 @@ +import { describe, expect, test } from "bun:test"; +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import { basename, dirname } from "node:path"; + +const SKILL_PATH = "skills/agent-skills-standard-following/SKILL.md"; +const PACKAGE_PATH = "skills/agent-skills-standard-following/skill-package.json"; +const SKILL_NAME = "agent-skills-standard-following"; +const SOURCE_REPOSITORY = "https://github.com/akua-dev/cli"; + +interface SkillPackage { + schema_version: number; + name: string; + version: string; + provenance: { + repository: string; + path: string; + revision_source: string; + }; + files: Array<{ + path: string; + sha256: string; + }>; +} + +describe("canonical Akua agent skill", () => { + test("is a valid, release-versioned Agent Skills artifact", () => { + const skill = readFileSync(SKILL_PATH, "utf8"); + const skillPackage = readSkillPackage(PACKAGE_PATH); + const rootPackage = JSON.parse(readFileSync("package.json", "utf8")) as { version: string }; + + expect(() => validateFrontmatter(SKILL_PATH, skill)).not.toThrow(); + expect(() => validateSkillPackage(skillPackage, skill, rootPackage.version)).not.toThrow(); + }); + + test("rejects malformed or non-standard frontmatter", () => { + const malformed = `---\nname: ${SKILL_NAME}\ndescription:\nversion: 1.0.0\n---\n# Akua\n`; + + expect(() => validateFrontmatter(SKILL_PATH, malformed)).toThrow("frontmatter keys"); + }); + + test("rejects skill path and name drift", () => { + const drifted = `---\nname: another-skill\ndescription: Use when working with Akua.\n---\n# Akua\n`; + + expect(() => validateFrontmatter(SKILL_PATH, drifted)).toThrow("must match its parent directory"); + }); + + test("rejects stale version and provenance metadata", () => { + const skill = validSkill(); + const staleVersion = { ...validSkillPackage(skill), version: "0.0.0" }; + const staleProvenance = { + ...validSkillPackage(skill), + provenance: { ...validSkillPackage(skill).provenance, repository: "https://github.com/akua-dev/skills" }, + }; + + expect(() => validateSkillPackage(staleVersion, skill, "1.2.3")).toThrow("version must match"); + expect(() => validateSkillPackage(staleProvenance, skill, "1.2.3")).toThrow("provenance must identify"); + }); + + test("rejects non-deterministic or stale package contents", () => { + const skill = validSkill(); + const generatedAt = { ...validSkillPackage(skill), generated_at: new Date().toISOString() }; + const staleDigest = { + ...validSkillPackage(skill), + files: [{ path: "SKILL.md", sha256: "0".repeat(64) }], + }; + + expect(() => validateSkillPackage(generatedAt, skill, "1.2.3")).toThrow("package keys"); + expect(() => validateSkillPackage(staleDigest, skill, "1.2.3")).toThrow("deterministic SKILL.md digest"); + }); + + test("release automation keeps skill package version metadata current", () => { + const config = JSON.parse(readFileSync("release-please-config.json", "utf8")) as { + packages?: Record; + }; + + expect(config.packages?.["."]?.["extra-files"]).toContainEqual({ + type: "json", + path: PACKAGE_PATH, + jsonpath: "$.version", + }); + }); +}); + +function validateFrontmatter(path: string, source: string): void { + const match = source.match(/^---\n([\s\S]*?)\n---\n([\s\S]+)$/); + if (!match) { + throw new Error("SKILL.md must contain YAML frontmatter followed by Markdown"); + } + + const entries = match[1].split("\n").map((line) => { + const separator = line.indexOf(":"); + if (separator <= 0) { + throw new Error("frontmatter must contain scalar key-value pairs"); + } + return [line.slice(0, separator), line.slice(separator + 1).trim()] as const; + }); + const fields = Object.fromEntries(entries); + + if (entries.map(([key]) => key).join(",") !== "name,description" || !fields.name || !fields.description) { + throw new Error("frontmatter keys must be exactly name and description"); + } + if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(fields.name) || fields.name.length > 64) { + throw new Error("skill name must follow the Agent Skills naming rules"); + } + if (fields.name !== basename(dirname(path))) { + throw new Error("skill name must match its parent directory"); + } + if (fields.description.length > 1024) { + throw new Error("skill description must not exceed 1024 characters"); + } +} + +function validateSkillPackage(skillPackage: SkillPackage, skill: string, rootVersion: string): void { + if (Object.keys(skillPackage).join(",") !== "schema_version,name,version,provenance,files") { + throw new Error("package keys must be stable and exclude generated timestamps"); + } + if (skillPackage.schema_version !== 1 || skillPackage.name !== SKILL_NAME) { + throw new Error("package schema and name must identify the canonical skill"); + } + if (skillPackage.version !== rootVersion) { + throw new Error("skill package version must match the CLI release version"); + } + if ( + JSON.stringify(skillPackage.provenance) !== + JSON.stringify({ repository: SOURCE_REPOSITORY, path: SKILL_PATH, revision_source: "git" }) + ) { + throw new Error("package provenance must identify the CLI-owned source and containing git revision"); + } + + const expectedFiles = [{ path: "SKILL.md", sha256: sha256(skill) }]; + if (JSON.stringify(skillPackage.files) !== JSON.stringify(expectedFiles)) { + throw new Error("package files must contain the deterministic SKILL.md digest"); + } +} + +function readSkillPackage(path: string): SkillPackage { + return JSON.parse(readFileSync(path, "utf8")) as SkillPackage; +} + +function sha256(value: string): string { + return createHash("sha256").update(value).digest("hex"); +} + +function validSkill(): string { + return `---\nname: ${SKILL_NAME}\ndescription: Use when working with Akua.\n---\n# Akua\n`; +} + +function validSkillPackage(skill: string): SkillPackage { + return { + schema_version: 1, + name: SKILL_NAME, + version: "1.2.3", + provenance: { + repository: SOURCE_REPOSITORY, + path: SKILL_PATH, + revision_source: "git", + }, + files: [{ path: "SKILL.md", sha256: sha256(skill) }], + }; +} diff --git a/test/release-please-config.test.ts b/test/release-please-config.test.ts index 0084180..e27c4f1 100644 --- a/test/release-please-config.test.ts +++ b/test/release-please-config.test.ts @@ -10,6 +10,7 @@ interface ReleasePleaseConfig { "extra-files"?: Array<{ type?: string; path?: string; + jsonpath?: string; }>; }>; } @@ -27,6 +28,11 @@ describe("release-please configuration", () => { type: "generic", path: "src/bin/akua.ts", }, + { + type: "json", + path: "skills/agent-skills-standard-following/skill-package.json", + jsonpath: "$.version", + }, ], }); expect(config["include-component-in-tag"]).toBe(false); From b678f115b7d435ae6d6c671c2da08dc274ba72fd Mon Sep 17 00:00:00 2001 From: Robin Date: Sun, 12 Jul 2026 04:11:06 +0200 Subject: [PATCH 2/9] no-mistakes(review): Validate skill frontmatter with Bun YAML parser --- test/agent-skill.test.ts | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/test/agent-skill.test.ts b/test/agent-skill.test.ts index 28dd4fd..e57391a 100644 --- a/test/agent-skill.test.ts +++ b/test/agent-skill.test.ts @@ -39,6 +39,12 @@ describe("canonical Akua agent skill", () => { expect(() => validateFrontmatter(SKILL_PATH, malformed)).toThrow("frontmatter keys"); }); + test("rejects invalid YAML frontmatter syntax", () => { + const invalidYaml = `---\nname: ${SKILL_NAME}\ndescription: [unterminated\n---\n# Akua\n`; + + expect(() => validateFrontmatter(SKILL_PATH, invalidYaml)).toThrow(); + }); + test("rejects skill path and name drift", () => { const drifted = `---\nname: another-skill\ndescription: Use when working with Akua.\n---\n# Akua\n`; @@ -88,25 +94,30 @@ function validateFrontmatter(path: string, source: string): void { throw new Error("SKILL.md must contain YAML frontmatter followed by Markdown"); } - const entries = match[1].split("\n").map((line) => { - const separator = line.indexOf(":"); - if (separator <= 0) { - throw new Error("frontmatter must contain scalar key-value pairs"); - } - return [line.slice(0, separator), line.slice(separator + 1).trim()] as const; - }); - const fields = Object.fromEntries(entries); + const parsed: unknown = Bun.YAML.parse(match[1]); + if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { + throw new Error("frontmatter keys must be exactly name and description"); + } - if (entries.map(([key]) => key).join(",") !== "name,description" || !fields.name || !fields.description) { + const fields = parsed as Record; + const name = fields.name; + const description = fields.description; + if ( + Object.keys(fields).join(",") !== "name,description" || + typeof name !== "string" || + !name || + typeof description !== "string" || + !description + ) { throw new Error("frontmatter keys must be exactly name and description"); } - if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(fields.name) || fields.name.length > 64) { + if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64) { throw new Error("skill name must follow the Agent Skills naming rules"); } - if (fields.name !== basename(dirname(path))) { + if (name !== basename(dirname(path))) { throw new Error("skill name must match its parent directory"); } - if (fields.description.length > 1024) { + if (description.length > 1024) { throw new Error("skill description must not exceed 1024 characters"); } } From 4183435a94051bb3dead7e46de75cf52db944b85 Mon Sep 17 00:00:00 2001 From: Robin Date: Sun, 12 Jul 2026 04:18:15 +0200 Subject: [PATCH 3/9] no-mistakes(document): Document canonical agent skill release wiring --- README.md | 10 ++++++---- docs/architecture.md | 14 ++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2b243e3..a848ff5 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,9 @@ release. This scaffold establishes the architecture, packaging path, OpenAPI fetch task, public operation registry generation, release automation, output/error runtime -contract, and local auth/config token handling. It does not yet implement full -API command execution. +contract, local auth/config token handling, and the canonical versioned Akua +[agent skill artifact](skills/agent-skills-standard-following/SKILL.md). It does +not yet implement full API command execution. ## Development @@ -89,8 +90,9 @@ any other tracked or untracked files. Release Please runs in manifest mode from `release-please-config.json` and `.release-please-manifest.json`. It prepares release pull requests for the root Bun package, updates package metadata, `CHANGELOG.md`, and the `akua --version` -marker in `src/bin/akua.ts`, and creates `v*` version tags and GitHub releases -after release PRs merge. +marker in `src/bin/akua.ts`, keeps the canonical agent skill package version +aligned, and creates `v*` version tags and GitHub releases after release PRs +merge. The workflow uses `secrets.RELEASE_PLEASE_TOKEN` when configured and otherwise falls back to its job-scoped `GITHUB_TOKEN`. The optional release token lets diff --git a/docs/architecture.md b/docs/architecture.md index 724cb37..ae0587d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -9,7 +9,8 @@ Status: greenfield scaffold with local auth/config MVP. - Repository: standalone open-source `akua-dev/cli`. - Packaging: Bun self-contained executable via `bun build --compile`. - API source of truth: `https://api.akua.dev/v1/openapi.json`. -- First release: local auth/config plus public API commands only. +- First release CLI command surface: local auth/config plus public API commands + only. - Compatibility: no `cnap` binary, Go module, config path, env var, or command compatibility unless a later captain decision changes this. - No live infrastructure mutation is required for development or tests. The @@ -29,6 +30,8 @@ src/bin/akua.ts executable entrypoint src/commands/auth.ts local auth/config command implementation src/runtime/ output, errors, exit codes, command contracts src/generated/commands.gen.ts generated public command registry +skills/agent-skills-standard-following/ + canonical versioned Akua agent skill artifact .github/workflows/update-openapi.yml idempotent public OpenAPI update automation .github/workflows/release-please.yml @@ -37,7 +40,7 @@ src/generated/commands.gen.ts generated public command registry release-please-config.json Release Please manifest-mode config .release-please-manifest.json Release Please root package version manifest docs/architecture.md this spec -test/ Bun tests for scaffold and auth/config contracts +test/ Bun tests for CLI and artifact contracts ``` ## OpenAPI And Command Generation @@ -269,8 +272,9 @@ Release Please runs in manifest mode for the root Bun package. It uses `release-please-config.json` and `.release-please-manifest.json` to prepare release PRs, update package metadata and `CHANGELOG.md`, keep the `src/bin/akua.ts` `x-release-please-version` marker aligned with -`akua --version`, create `v*` version tags without a component prefix, and -create GitHub releases after release PRs merge. The workflow uses +`akua --version`, keep the canonical agent skill package version aligned with +the CLI release, create `v*` version tags without a component prefix, and create +GitHub releases after release PRs merge. The workflow uses `secrets.RELEASE_PLEASE_TOKEN` when configured and otherwise falls back to its job-scoped `GITHUB_TOKEN`. The optional release token is required for release-created tags to trigger the tag-based binary workflow because GitHub @@ -291,6 +295,8 @@ Current tests cover: - structured error payloads; - OpenAPI fetch guard and document shape validation; - public-only operation collection; +- canonical agent skill frontmatter, provenance, deterministic package digest, + and release version wiring; - Release Please config, manifest, token, and CLI version marker validation. Current validation also runs `mise run generate:check` to catch generated From c5804d35468573429eb4a2a60b2d38810eef141f Mon Sep 17 00:00:00 2001 From: Robin Date: Sun, 12 Jul 2026 04:22:44 +0200 Subject: [PATCH 4/9] chore: keep skill artifact scope narrow --- README.md | 10 ++++------ docs/architecture.md | 14 ++++---------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index a848ff5..2b243e3 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,8 @@ release. This scaffold establishes the architecture, packaging path, OpenAPI fetch task, public operation registry generation, release automation, output/error runtime -contract, local auth/config token handling, and the canonical versioned Akua -[agent skill artifact](skills/agent-skills-standard-following/SKILL.md). It does -not yet implement full API command execution. +contract, and local auth/config token handling. It does not yet implement full +API command execution. ## Development @@ -90,9 +89,8 @@ any other tracked or untracked files. Release Please runs in manifest mode from `release-please-config.json` and `.release-please-manifest.json`. It prepares release pull requests for the root Bun package, updates package metadata, `CHANGELOG.md`, and the `akua --version` -marker in `src/bin/akua.ts`, keeps the canonical agent skill package version -aligned, and creates `v*` version tags and GitHub releases after release PRs -merge. +marker in `src/bin/akua.ts`, and creates `v*` version tags and GitHub releases +after release PRs merge. The workflow uses `secrets.RELEASE_PLEASE_TOKEN` when configured and otherwise falls back to its job-scoped `GITHUB_TOKEN`. The optional release token lets diff --git a/docs/architecture.md b/docs/architecture.md index ae0587d..724cb37 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -9,8 +9,7 @@ Status: greenfield scaffold with local auth/config MVP. - Repository: standalone open-source `akua-dev/cli`. - Packaging: Bun self-contained executable via `bun build --compile`. - API source of truth: `https://api.akua.dev/v1/openapi.json`. -- First release CLI command surface: local auth/config plus public API commands - only. +- First release: local auth/config plus public API commands only. - Compatibility: no `cnap` binary, Go module, config path, env var, or command compatibility unless a later captain decision changes this. - No live infrastructure mutation is required for development or tests. The @@ -30,8 +29,6 @@ src/bin/akua.ts executable entrypoint src/commands/auth.ts local auth/config command implementation src/runtime/ output, errors, exit codes, command contracts src/generated/commands.gen.ts generated public command registry -skills/agent-skills-standard-following/ - canonical versioned Akua agent skill artifact .github/workflows/update-openapi.yml idempotent public OpenAPI update automation .github/workflows/release-please.yml @@ -40,7 +37,7 @@ skills/agent-skills-standard-following/ release-please-config.json Release Please manifest-mode config .release-please-manifest.json Release Please root package version manifest docs/architecture.md this spec -test/ Bun tests for CLI and artifact contracts +test/ Bun tests for scaffold and auth/config contracts ``` ## OpenAPI And Command Generation @@ -272,9 +269,8 @@ Release Please runs in manifest mode for the root Bun package. It uses `release-please-config.json` and `.release-please-manifest.json` to prepare release PRs, update package metadata and `CHANGELOG.md`, keep the `src/bin/akua.ts` `x-release-please-version` marker aligned with -`akua --version`, keep the canonical agent skill package version aligned with -the CLI release, create `v*` version tags without a component prefix, and create -GitHub releases after release PRs merge. The workflow uses +`akua --version`, create `v*` version tags without a component prefix, and +create GitHub releases after release PRs merge. The workflow uses `secrets.RELEASE_PLEASE_TOKEN` when configured and otherwise falls back to its job-scoped `GITHUB_TOKEN`. The optional release token is required for release-created tags to trigger the tag-based binary workflow because GitHub @@ -295,8 +291,6 @@ Current tests cover: - structured error payloads; - OpenAPI fetch guard and document shape validation; - public-only operation collection; -- canonical agent skill frontmatter, provenance, deterministic package digest, - and release version wiring; - Release Please config, manifest, token, and CLI version marker validation. Current validation also runs `mise run generate:check` to catch generated From 51a8662d57962acdc9d9dcfebfa5558bdbcf23fc Mon Sep 17 00:00:00 2001 From: Robin Date: Sun, 12 Jul 2026 09:38:40 +0200 Subject: [PATCH 5/9] fix(skills): use canonical Akua identity --- release-please-config.json | 2 +- .../SKILL.md | 2 +- .../skill-package.json | 6 +++--- test/agent-skill.test.ts | 12 +++++++++--- test/release-please-config.test.ts | 2 +- 5 files changed, 15 insertions(+), 9 deletions(-) rename skills/{agent-skills-standard-following => akua}/SKILL.md (99%) rename skills/{agent-skills-standard-following => akua}/skill-package.json (52%) diff --git a/release-please-config.json b/release-please-config.json index 95be370..899abd4 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -12,7 +12,7 @@ }, { "type": "json", - "path": "skills/agent-skills-standard-following/skill-package.json", + "path": "skills/akua/skill-package.json", "jsonpath": "$.version" } ] diff --git a/skills/agent-skills-standard-following/SKILL.md b/skills/akua/SKILL.md similarity index 99% rename from skills/agent-skills-standard-following/SKILL.md rename to skills/akua/SKILL.md index 078054b..54f4521 100644 --- a/skills/agent-skills-standard-following/SKILL.md +++ b/skills/akua/SKILL.md @@ -1,5 +1,5 @@ --- -name: agent-skills-standard-following +name: akua description: Guide agents working with Akua documentation, workspaces, authentication, generated CLI commands, structured output, and approval-safe operations. Use for Akua setup, inspection, troubleshooting, or change requests. --- diff --git a/skills/agent-skills-standard-following/skill-package.json b/skills/akua/skill-package.json similarity index 52% rename from skills/agent-skills-standard-following/skill-package.json rename to skills/akua/skill-package.json index 601045f..3a2a211 100644 --- a/skills/agent-skills-standard-following/skill-package.json +++ b/skills/akua/skill-package.json @@ -1,16 +1,16 @@ { "schema_version": 1, - "name": "agent-skills-standard-following", + "name": "akua", "version": "0.7.0", "provenance": { "repository": "https://github.com/akua-dev/cli", - "path": "skills/agent-skills-standard-following/SKILL.md", + "path": "skills/akua/SKILL.md", "revision_source": "git" }, "files": [ { "path": "SKILL.md", - "sha256": "49ceb008dce8f6f1fbfc51fdb389f09986556db406331e0b9870006752f877ee" + "sha256": "01e1d7a7812ec3026419aebecfb27e54cbaff18c8386e9e50451f9af2a635979" } ] } diff --git a/test/agent-skill.test.ts b/test/agent-skill.test.ts index e57391a..f001eb4 100644 --- a/test/agent-skill.test.ts +++ b/test/agent-skill.test.ts @@ -3,9 +3,9 @@ import { createHash } from "node:crypto"; import { readFileSync } from "node:fs"; import { basename, dirname } from "node:path"; -const SKILL_PATH = "skills/agent-skills-standard-following/SKILL.md"; -const PACKAGE_PATH = "skills/agent-skills-standard-following/skill-package.json"; -const SKILL_NAME = "agent-skills-standard-following"; +const SKILL_PATH = "skills/akua/SKILL.md"; +const PACKAGE_PATH = "skills/akua/skill-package.json"; +const SKILL_NAME = "akua"; const SOURCE_REPOSITORY = "https://github.com/akua-dev/cli"; interface SkillPackage { @@ -51,6 +51,12 @@ describe("canonical Akua agent skill", () => { expect(() => validateFrontmatter(SKILL_PATH, drifted)).toThrow("must match its parent directory"); }); + test("rejects task-shaped public skill identities", () => { + const taskShaped = `---\nname: agent-skill-compliance-task\ndescription: Use when working with Akua.\n---\n# Akua\n`; + + expect(() => validateFrontmatter(SKILL_PATH, taskShaped)).toThrow("must match its parent directory"); + }); + test("rejects stale version and provenance metadata", () => { const skill = validSkill(); const staleVersion = { ...validSkillPackage(skill), version: "0.0.0" }; diff --git a/test/release-please-config.test.ts b/test/release-please-config.test.ts index e27c4f1..5655acb 100644 --- a/test/release-please-config.test.ts +++ b/test/release-please-config.test.ts @@ -30,7 +30,7 @@ describe("release-please configuration", () => { }, { type: "json", - path: "skills/agent-skills-standard-following/skill-package.json", + path: "skills/akua/skill-package.json", jsonpath: "$.version", }, ], From 8724c6cdbcd586820d188da4ce54408d4b7b51d0 Mon Sep 17 00:00:00 2001 From: Robin Date: Sun, 12 Jul 2026 09:46:02 +0200 Subject: [PATCH 6/9] no-mistakes(review): Enforce canonical Akua skill identity --- test/agent-skill.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/agent-skill.test.ts b/test/agent-skill.test.ts index f001eb4..4fef327 100644 --- a/test/agent-skill.test.ts +++ b/test/agent-skill.test.ts @@ -52,9 +52,10 @@ describe("canonical Akua agent skill", () => { }); test("rejects task-shaped public skill identities", () => { + const taskShapedPath = "skills/agent-skill-compliance-task/SKILL.md"; const taskShaped = `---\nname: agent-skill-compliance-task\ndescription: Use when working with Akua.\n---\n# Akua\n`; - expect(() => validateFrontmatter(SKILL_PATH, taskShaped)).toThrow("must match its parent directory"); + expect(() => validateFrontmatter(taskShapedPath, taskShaped)).toThrow("must use the canonical Akua identity"); }); test("rejects stale version and provenance metadata", () => { @@ -123,6 +124,9 @@ function validateFrontmatter(path: string, source: string): void { if (name !== basename(dirname(path))) { throw new Error("skill name must match its parent directory"); } + if (name !== SKILL_NAME) { + throw new Error("skill name must use the canonical Akua identity"); + } if (description.length > 1024) { throw new Error("skill description must not exceed 1024 characters"); } From e6b067c323c9a5889650f24eab5a92030a1e0d45 Mon Sep 17 00:00:00 2001 From: Robin Date: Sun, 12 Jul 2026 09:57:21 +0200 Subject: [PATCH 7/9] no-mistakes(document): Document canonical skill release wiring --- docs/architecture.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 724cb37..cb405d3 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -9,7 +9,8 @@ Status: greenfield scaffold with local auth/config MVP. - Repository: standalone open-source `akua-dev/cli`. - Packaging: Bun self-contained executable via `bun build --compile`. - API source of truth: `https://api.akua.dev/v1/openapi.json`. -- First release: local auth/config plus public API commands only. +- First release CLI command surface: local auth/config plus public API commands + only. - Compatibility: no `cnap` binary, Go module, config path, env var, or command compatibility unless a later captain decision changes this. - No live infrastructure mutation is required for development or tests. The @@ -29,6 +30,7 @@ src/bin/akua.ts executable entrypoint src/commands/auth.ts local auth/config command implementation src/runtime/ output, errors, exit codes, command contracts src/generated/commands.gen.ts generated public command registry +skills/akua/ canonical versioned Akua agent skill artifact .github/workflows/update-openapi.yml idempotent public OpenAPI update automation .github/workflows/release-please.yml @@ -37,7 +39,7 @@ src/generated/commands.gen.ts generated public command registry release-please-config.json Release Please manifest-mode config .release-please-manifest.json Release Please root package version manifest docs/architecture.md this spec -test/ Bun tests for scaffold and auth/config contracts +test/ Bun tests for CLI and artifact contracts ``` ## OpenAPI And Command Generation @@ -269,8 +271,9 @@ Release Please runs in manifest mode for the root Bun package. It uses `release-please-config.json` and `.release-please-manifest.json` to prepare release PRs, update package metadata and `CHANGELOG.md`, keep the `src/bin/akua.ts` `x-release-please-version` marker aligned with -`akua --version`, create `v*` version tags without a component prefix, and -create GitHub releases after release PRs merge. The workflow uses +`akua --version`, keep the canonical agent skill package version aligned with +the CLI release, create `v*` version tags without a component prefix, and create +GitHub releases after release PRs merge. The workflow uses `secrets.RELEASE_PLEASE_TOKEN` when configured and otherwise falls back to its job-scoped `GITHUB_TOKEN`. The optional release token is required for release-created tags to trigger the tag-based binary workflow because GitHub @@ -291,6 +294,8 @@ Current tests cover: - structured error payloads; - OpenAPI fetch guard and document shape validation; - public-only operation collection; +- canonical agent skill identity, frontmatter, provenance, deterministic package + digest, and release version wiring; - Release Please config, manifest, token, and CLI version marker validation. Current validation also runs `mise run generate:check` to catch generated From 473650ed0967776d250d8901756285c1a9732a35 Mon Sep 17 00:00:00 2001 From: Robin Date: Sun, 12 Jul 2026 09:59:04 +0200 Subject: [PATCH 8/9] chore: restore approved skill scope --- docs/architecture.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index cb405d3..724cb37 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -9,8 +9,7 @@ Status: greenfield scaffold with local auth/config MVP. - Repository: standalone open-source `akua-dev/cli`. - Packaging: Bun self-contained executable via `bun build --compile`. - API source of truth: `https://api.akua.dev/v1/openapi.json`. -- First release CLI command surface: local auth/config plus public API commands - only. +- First release: local auth/config plus public API commands only. - Compatibility: no `cnap` binary, Go module, config path, env var, or command compatibility unless a later captain decision changes this. - No live infrastructure mutation is required for development or tests. The @@ -30,7 +29,6 @@ src/bin/akua.ts executable entrypoint src/commands/auth.ts local auth/config command implementation src/runtime/ output, errors, exit codes, command contracts src/generated/commands.gen.ts generated public command registry -skills/akua/ canonical versioned Akua agent skill artifact .github/workflows/update-openapi.yml idempotent public OpenAPI update automation .github/workflows/release-please.yml @@ -39,7 +37,7 @@ skills/akua/ canonical versioned Akua agent skill artifact release-please-config.json Release Please manifest-mode config .release-please-manifest.json Release Please root package version manifest docs/architecture.md this spec -test/ Bun tests for CLI and artifact contracts +test/ Bun tests for scaffold and auth/config contracts ``` ## OpenAPI And Command Generation @@ -271,9 +269,8 @@ Release Please runs in manifest mode for the root Bun package. It uses `release-please-config.json` and `.release-please-manifest.json` to prepare release PRs, update package metadata and `CHANGELOG.md`, keep the `src/bin/akua.ts` `x-release-please-version` marker aligned with -`akua --version`, keep the canonical agent skill package version aligned with -the CLI release, create `v*` version tags without a component prefix, and create -GitHub releases after release PRs merge. The workflow uses +`akua --version`, create `v*` version tags without a component prefix, and +create GitHub releases after release PRs merge. The workflow uses `secrets.RELEASE_PLEASE_TOKEN` when configured and otherwise falls back to its job-scoped `GITHUB_TOKEN`. The optional release token is required for release-created tags to trigger the tag-based binary workflow because GitHub @@ -294,8 +291,6 @@ Current tests cover: - structured error payloads; - OpenAPI fetch guard and document shape validation; - public-only operation collection; -- canonical agent skill identity, frontmatter, provenance, deterministic package - digest, and release version wiring; - Release Please config, manifest, token, and CLI version marker validation. Current validation also runs `mise run generate:check` to catch generated From 0eb081bdbf9c51315adf1d59ec037e914862ec1d Mon Sep 17 00:00:00 2001 From: Robin Date: Tue, 14 Jul 2026 15:08:25 +0200 Subject: [PATCH 9/9] no-mistakes(review): Harden skill authentication and frontmatter validation --- skills/akua/SKILL.md | 2 +- skills/akua/skill-package.json | 2 +- test/agent-skill.test.ts | 121 +++++++++++++++++++++++++++++---- 3 files changed, 110 insertions(+), 15 deletions(-) diff --git a/skills/akua/SKILL.md b/skills/akua/SKILL.md index 54f4521..2e08a6b 100644 --- a/skills/akua/SKILL.md +++ b/skills/akua/SKILL.md @@ -29,7 +29,7 @@ akua commands --output agent Use `akua commands --resource ` or `akua commands --operation-id ` to narrow the generated public command registry. Registry presence describes the generated surface; it does not prove that execution is implemented. If a command reports that it is not implemented, return to the docs or platform MCP instead of inventing flags or claiming success. -Never expose tokens from `AKUA_API_TOKEN` or the local config. `akua auth status` reports the effective source without revealing the credential. Treat `akua auth login --token ` and `akua auth logout` as local configuration mutations and run them only when explicitly requested. +Agents must never place authentication tokens in tool arguments, process argv, transcripts, or logs. When authentication setup is requested, instruct the human to run `akua auth login --token ` privately and locally themselves. Afterward, agents may run `akua auth status --output agent` to confirm the effective source without revealing the credential. Agents may run `akua auth logout` only when explicitly requested. ## Select structured output deliberately diff --git a/skills/akua/skill-package.json b/skills/akua/skill-package.json index 3a2a211..1a11e40 100644 --- a/skills/akua/skill-package.json +++ b/skills/akua/skill-package.json @@ -10,7 +10,7 @@ "files": [ { "path": "SKILL.md", - "sha256": "01e1d7a7812ec3026419aebecfb27e54cbaff18c8386e9e50451f9af2a635979" + "sha256": "abec4823b7e9ea42d9e261ce598442101524e8309a01377f494cf54cdac6c5c3" } ] } diff --git a/test/agent-skill.test.ts b/test/agent-skill.test.ts index 4fef327..9f6e4a1 100644 --- a/test/agent-skill.test.ts +++ b/test/agent-skill.test.ts @@ -7,6 +7,14 @@ const SKILL_PATH = "skills/akua/SKILL.md"; const PACKAGE_PATH = "skills/akua/skill-package.json"; const SKILL_NAME = "akua"; const SOURCE_REPOSITORY = "https://github.com/akua-dev/cli"; +const FRONTMATTER_FIELDS = new Set([ + "name", + "description", + "license", + "compatibility", + "metadata", + "allowed-tools", +]); interface SkillPackage { schema_version: number; @@ -33,10 +41,65 @@ describe("canonical Akua agent skill", () => { expect(() => validateSkillPackage(skillPackage, skill, rootPackage.version)).not.toThrow(); }); - test("rejects malformed or non-standard frontmatter", () => { - const malformed = `---\nname: ${SKILL_NAME}\ndescription:\nversion: 1.0.0\n---\n# Akua\n`; + test("directs authentication setup through the human without exposing tokens", () => { + const skill = readFileSync(SKILL_PATH, "utf8"); + + expect(skill).toContain( + "Agents must never place authentication tokens in tool arguments, process argv, transcripts, or logs.", + ); + expect(skill).toContain( + "instruct the human to run `akua auth login --token ` privately and locally themselves", + ); + expect(skill).toContain("agents may run `akua auth status --output agent`"); + }); + + test("accepts standard optional frontmatter fields in any key order", () => { + const reordered = skillWithFrontmatter(`allowed-tools: Bash(git:*) Read +metadata: + author: akua-dev + version: "1.0" +description: Use when working with Akua. +license: Apache-2.0 +name: ${SKILL_NAME} +compatibility: Requires Bun 1.3.7 or newer`); - expect(() => validateFrontmatter(SKILL_PATH, malformed)).toThrow("frontmatter keys"); + expect(() => validateFrontmatter(SKILL_PATH, reordered)).not.toThrow(); + }); + + test("rejects unknown frontmatter fields", () => { + const unknown = skillWithFrontmatter(`name: ${SKILL_NAME} +description: Use when working with Akua. +version: 1.0.0`); + + expect(() => validateFrontmatter(SKILL_PATH, unknown)).toThrow("unknown frontmatter field: version"); + }); + + test("rejects invalid optional frontmatter field values", () => { + const invalidFields = [ + "license: []", + "compatibility: 1", + `compatibility: ${"x".repeat(501)}`, + "metadata: []", + "metadata:\n version: 1", + "allowed-tools: []", + ]; + + for (const field of invalidFields) { + const invalid = skillWithFrontmatter(`name: ${SKILL_NAME} +description: Use when working with Akua. +${field}`); + + expect(() => validateFrontmatter(SKILL_PATH, invalid)).toThrow(); + } + }); + + test("rejects invalid descriptions", () => { + const missing = skillWithFrontmatter(`name: ${SKILL_NAME}`); + const blank = skillWithFrontmatter(`name: ${SKILL_NAME} +description: " "`); + + expect(() => validateFrontmatter(SKILL_PATH, missing)).toThrow("description must be a non-empty string"); + expect(() => validateFrontmatter(SKILL_PATH, blank)).toThrow("description must be a non-empty string"); }); test("rejects invalid YAML frontmatter syntax", () => { @@ -103,20 +166,22 @@ function validateFrontmatter(path: string, source: string): void { const parsed: unknown = Bun.YAML.parse(match[1]); if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { - throw new Error("frontmatter keys must be exactly name and description"); + throw new Error("frontmatter must be a YAML mapping"); } const fields = parsed as Record; + const unknownFields = Object.keys(fields).filter((field) => !FRONTMATTER_FIELDS.has(field)); + if (unknownFields.length > 0) { + throw new Error(`unknown frontmatter field: ${unknownFields.join(", ")}`); + } + const name = fields.name; const description = fields.description; - if ( - Object.keys(fields).join(",") !== "name,description" || - typeof name !== "string" || - !name || - typeof description !== "string" || - !description - ) { - throw new Error("frontmatter keys must be exactly name and description"); + if (typeof name !== "string" || !name) { + throw new Error("skill name must be a non-empty string"); + } + if (typeof description !== "string" || !description.trim()) { + throw new Error("skill description must be a non-empty string"); } if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64) { throw new Error("skill name must follow the Agent Skills naming rules"); @@ -130,6 +195,32 @@ function validateFrontmatter(path: string, source: string): void { if (description.length > 1024) { throw new Error("skill description must not exceed 1024 characters"); } + + validateOptionalString(fields, "license"); + validateOptionalString(fields, "allowed-tools"); + + const compatibility = validateOptionalString(fields, "compatibility"); + if (compatibility !== undefined && (compatibility.length === 0 || compatibility.length > 500)) { + throw new Error("skill compatibility must contain 1-500 characters"); + } + + const metadata = fields.metadata; + if (metadata !== undefined) { + if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata)) { + throw new Error("skill metadata must be a string-to-string mapping"); + } + if (Object.values(metadata).some((value) => typeof value !== "string")) { + throw new Error("skill metadata must be a string-to-string mapping"); + } + } +} + +function validateOptionalString(fields: Record, field: string): string | undefined { + const value = fields[field]; + if (value !== undefined && typeof value !== "string") { + throw new Error(`skill ${field} must be a string`); + } + return value; } function validateSkillPackage(skillPackage: SkillPackage, skill: string, rootVersion: string): void { @@ -164,7 +255,11 @@ function sha256(value: string): string { } function validSkill(): string { - return `---\nname: ${SKILL_NAME}\ndescription: Use when working with Akua.\n---\n# Akua\n`; + return skillWithFrontmatter(`name: ${SKILL_NAME}\ndescription: Use when working with Akua.`); +} + +function skillWithFrontmatter(frontmatter: string): string { + return `---\n${frontmatter}\n---\n# Akua\n`; } function validSkillPackage(skill: string): SkillPackage {