From 2d0a3e0dabee60db26cf26550fb013561430fc84 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Fri, 7 Aug 2026 13:16:52 +0100 Subject: [PATCH] Improve README onboarding and package its logo --- .changeset/readme-code-to-registered-skill.md | 5 ++ README.md | 74 +++++++++++++++++-- assets/yield-mark.svg | 13 ++++ examples/release-checklist/main.ts | 9 +++ packaging/assemble.mjs | 3 + packaging/assemble.test.mjs | 5 ++ scripts/readme.test.mjs | 49 +++++++++++- 7 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 .changeset/readme-code-to-registered-skill.md create mode 100644 assets/yield-mark.svg diff --git a/.changeset/readme-code-to-registered-skill.md b/.changeset/readme-code-to-registered-skill.md new file mode 100644 index 0000000..b95b3f9 --- /dev/null +++ b/.changeset/readme-code-to-registered-skill.md @@ -0,0 +1,5 @@ +--- +"@operatorstack/yield": patch +--- + +Explain how workflow code becomes a registered coding-agent skill and use a cleaner README mark. diff --git a/README.md b/README.md index c35e5fc..83a2434 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Yield + Yield

@@ -49,9 +49,14 @@ import { defineSkill } from "@operatorstack/yield"; type Review = { critical: number; summary: string }; defineSkill((ctx) => { + // Yield runs commands itself and records their output and exit status. const tests = ctx.runCommand("test", "echo tests-ok", 300); + + // A failed requirement stops the workflow and keeps its evidence. ctx.require(tests.exit_code === 0, "the test command succeeds", tests); + // Review gives TypeScript its compile-time type. The JSON schema checks the + // coding agent's response at runtime before this workflow can continue. const review = ctx.agentTask( "review-release", "Review this release. Report critical findings and a short summary.", @@ -67,12 +72,16 @@ defineSkill((ctx) => { ); ctx.require(review.critical === 0, "the review has no critical findings", review); + // Yield emits these fixed choices. A supported host may show native controls; + // otherwise the coding agent asks through its normal interface. const approval = ctx.askUser("approve-publish", "Publish this package?", [ { value: "yes", label: "Publish" }, { value: "no", label: "Stop" }, ]); if (approval !== "yes") ctx.refused("the operator declined publication"); + // Publishing cannot start before approval. Verification is a separate step, + // so completion requires evidence that the registry contains the release. const publish = ctx.runCommand("publish", "echo publish-ok", 600); ctx.require(publish.exit_code === 0, "the publish command succeeds", publish); @@ -89,9 +98,11 @@ Replace them with the test, publish, and registry commands for your project. The complete tested source is in [`examples/release-checklist`](examples/release-checklist/). -## Install and create a workflow +## Use Yield in four steps + +### 1. Install Yield -Install the TypeScript SDK and its repository-local CLI: +Install the TypeScript SDK and its repository-local CLI in your project: ```bash npm install --save-exact @operatorstack/yield @@ -102,22 +113,69 @@ npm exec -- yskill --version use trusted publishing. The SDK package and all six runtime packages include SLSA v1 provenance. -Create, test, and register a workflow: +### 2. Create the workflow ```bash npm exec -- yskill init skills/release \ --language typescript \ --description "Test, review, approve, publish, and verify a package." +``` + +The command creates one canonical workflow inside your repository: + +```text +skills/ +└── release/ + ├── SKILL.md + ├── fixtures/ + │ ├── responses.json + │ └── test.json + ├── main.ts + ├── package.json + └── skill.json +``` -# Replace the starter with your workflow and fixture. +Replace the starter in `skills/release/main.ts` with your workflow. Update +`skills/release/fixtures/responses.json` with deterministic answers for agent +and user operations. + +### 3. Test the workflow + +```bash npm exec -- yskill doctor skills/release --test +``` + +This runs commands for real and supplies agent and user responses from the +fixture. A successful test reaches `completed` without leaving a run journal. -# Detect installed agents, or pass --agent cursor,codex,claude-code. +### 4. Register and use the skill + +Registration is the discovery step. This command detects installed verified +agents and writes a small adapter for each one: + +```bash npm exec -- yskill register skills/release ``` -Yield writes small adapters into each coding agent's project skill directory. -It does not copy the workflow or install its dependencies again. +Select verified agents explicitly when you do not want automatic detection: + +```bash +npm exec -- yskill register skills/release \ + --agent cursor,codex,claude-code +``` + +If all three are selected, Yield creates these generated files: + +```text +.cursor/skills/release/SKILL.md # Cursor +.agents/skills/release/SKILL.md # Codex +.claude/skills/release/SKILL.md # Claude Code +``` + +The adapters point back to `skills/release`. They do not copy the workflow or +install its dependencies again. Start a new agent session after registration, +then invoke `/release` where slash skills are supported or ask the agent to use +the release skill. ## How Yield runs and resumes diff --git a/assets/yield-mark.svg b/assets/yield-mark.svg new file mode 100644 index 0000000..c3b052c --- /dev/null +++ b/assets/yield-mark.svg @@ -0,0 +1,13 @@ + + Yield + + + + + + + + + + + diff --git a/examples/release-checklist/main.ts b/examples/release-checklist/main.ts index b3feeb4..6629430 100644 --- a/examples/release-checklist/main.ts +++ b/examples/release-checklist/main.ts @@ -6,9 +6,14 @@ import { defineSkill } from "../../sdk/typescript/src/index.ts"; type Review = { critical: number; summary: string }; defineSkill((ctx) => { + // Yield runs commands itself and records their output and exit status. const tests = ctx.runCommand("test", "echo tests-ok", 300); + + // A failed requirement stops the workflow and keeps its evidence. ctx.require(tests.exit_code === 0, "the test command succeeds", tests); + // Review gives TypeScript its compile-time type. The JSON schema checks the + // coding agent's response at runtime before this workflow can continue. const review = ctx.agentTask( "review-release", "Review this release. Report critical findings and a short summary.", @@ -24,12 +29,16 @@ defineSkill((ctx) => { ); ctx.require(review.critical === 0, "the review has no critical findings", review); + // Yield emits these fixed choices. A supported host may show native controls; + // otherwise the coding agent asks through its normal interface. const approval = ctx.askUser("approve-publish", "Publish this package?", [ { value: "yes", label: "Publish" }, { value: "no", label: "Stop" }, ]); if (approval !== "yes") ctx.refused("the operator declined publication"); + // Publishing cannot start before approval. Verification is a separate step, + // so completion requires evidence that the registry contains the release. const publish = ctx.runCommand("publish", "echo publish-ok", 600); ctx.require(publish.exit_code === 0, "the publish command succeeds", publish); diff --git a/packaging/assemble.mjs b/packaging/assemble.mjs index 447de63..ba395c8 100644 --- a/packaging/assemble.mjs +++ b/packaging/assemble.mjs @@ -50,9 +50,11 @@ async function assembleNpm({ version, binaries, output }) { const npm = join(output, "npm"); const main = join(npm, "yield"); await cp(join(root, "sdk/typescript"), main, { recursive: true, filter: (source) => !source.includes("node_modules") && !source.includes("/dist") }); + await mkdir(join(main, "assets"), { recursive: true }); await Promise.all([ cp(join(root, "README.md"), join(main, "README.md")), cp(join(root, "LICENSE"), join(main, "LICENSE")), + cp(join(root, "assets/yield-mark.svg"), join(main, "assets/yield-mark.svg")), ]); const packageJson = await json(join(main, "package.json")); packageJson.version = version; @@ -62,6 +64,7 @@ async function assembleNpm({ version, binaries, output }) { registry: "https://registry.npmjs.org/", }; packageJson.optionalDependencies = Object.fromEntries(targets.map((target) => [npmPackage(target), version])); + packageJson.files = [...new Set([...(packageJson.files ?? []), "assets"])]; await writeFile(join(main, "package.json"), `${JSON.stringify(packageJson, null, 2)}\n`); for (const target of targets) { diff --git a/packaging/assemble.test.mjs b/packaging/assemble.test.mjs index bd7d870..a0658d3 100644 --- a/packaging/assemble.test.mjs +++ b/packaging/assemble.test.mjs @@ -45,6 +45,11 @@ test("assembles one public npm package and six matching runtimes", async (t) => ); const assembledReadme = await readFile(join(output, "npm/yield/README.md"), "utf8"); assert.equal(assembledReadme, await readFile(join(import.meta.dirname, "../README.md"), "utf8")); + assert.equal( + await readFile(join(output, "npm/yield/assets/yield-mark.svg"), "utf8"), + await readFile(join(import.meta.dirname, "../assets/yield-mark.svg"), "utf8"), + ); + assert.ok(main.files.includes("assets")); assert.match(assembledReadme, /

Yield<\/h1>/); assert.match(await readFile(join(output, "npm/yield/LICENSE"), "utf8"), /MIT License/); diff --git a/scripts/readme.test.mjs b/scripts/readme.test.mjs index a49dd99..206a7fb 100644 --- a/scripts/readme.test.mjs +++ b/scripts/readme.test.mjs @@ -53,8 +53,53 @@ test("README agent claims match the pinned registry", async () => { assert.doesNotMatch(readme, /Agent Plugins and Yield/); }); -test("README uses the compact Yield mark", async () => { +test("README presents the workflow as four ordered steps", async () => { const readme = await text("README.md"); - assert.match(readme, /https:\/\/yield\.operatorstack\.systems\/favicon\.svg/); + const headings = [ + "### 1. Install Yield", + "### 2. Create the workflow", + "### 3. Test the workflow", + "### 4. Register and use the skill", + ]; + + let previous = -1; + for (const heading of headings) { + const current = readme.indexOf(heading); + assert.ok(current > previous, `${heading} is missing or out of order`); + previous = current; + } + + assert.match(readme, /npm exec -- yskill doctor skills\/release --test/); + assert.match(readme, /npm exec -- yskill register skills\/release/); + assert.match(readme, /Registration is the discovery step\./); +}); + +test("README adapter paths match every verified agent", async () => { + const [readme, registryText] = await Promise.all([ + text("README.md"), + text("cmd/yskill/registry/agents.json"), + ]); + const registry = JSON.parse(registryText); + + for (const agent of registry.agents.filter((entry) => entry.tier === "verified")) { + const adapter = `${agent.project_dir}/release/SKILL.md`; + assert.match(readme, new RegExp(adapter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))); + } + + assert.match(readme, /--agent cursor,codex,claude-code/); + assert.match(readme, /If all three are selected/); +}); + +test("README uses the edge-cropped Yield mark", async () => { + const [readme, mark] = await Promise.all([ + text("README.md"), + text("assets/yield-mark.svg"), + ]); + assert.match( + readme, + /Yield/, + ); assert.doesNotMatch(readme, /apple-touch-icon\.png/); + assert.match(mark, /viewBox="0 0 60 60"/); + assert.match(mark, /