@@ -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 @@
+
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,
+ //,
+ );
assert.doesNotMatch(readme, /apple-touch-icon\.png/);
+ assert.match(mark, /viewBox="0 0 60 60"/);
+ assert.match(mark, /