Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/python-readme-first-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@operatorstack/yield": patch
---

Add a complete Python onboarding guide, use the public PyPI package in generated requirements, and keep npm documentation links and registry badges specific to npm.
34 changes: 19 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@

<p align="center">
<a href="https://www.npmjs.com/package/@operatorstack/yield"><img alt="npm version" src="https://img.shields.io/npm/v/@operatorstack/yield?style=flat-square" /></a>
<!-- npm-exclude:start -->
<a href="https://pypi.org/project/yieldskill/"><img alt="PyPI version" src="https://img.shields.io/pypi/v/yieldskill?style=flat-square" /></a>
<!-- npm-exclude:end -->
<a href="https://github.com/operatorstack/yield/actions/workflows/verify.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/operatorstack/yield/verify.yml?branch=main&amp;style=flat-square&amp;label=build" /></a>
<a href="LICENSE"><img alt="MIT license" src="https://img.shields.io/npm/l/@operatorstack/yield?style=flat-square" /></a>
<a href="https://github.com/operatorstack/yield/blob/main/LICENSE"><img alt="MIT license" src="https://img.shields.io/npm/l/@operatorstack/yield?style=flat-square" /></a>
</p>

<p align="center">
<a href="https://yield.operatorstack.systems/">Website</a> ·
<a href="https://yield.operatorstack.systems/docs/">Documentation</a> ·
<a href="https://www.npmjs.com/package/@operatorstack/yield">npm</a> ·
<!-- npm-exclude:start -->
<a href="https://pypi.org/project/yieldskill/">PyPI</a> ·
<!-- npm-exclude:end -->
<a href="https://github.com/operatorstack/yield">GitHub</a>
</p>

Expand Down Expand Up @@ -98,7 +102,7 @@ defineSkill((ctx) => {
The example uses harmless commands so its fixture can run in any checkout.
Replace them with the test, publish, and registry commands for your project.
The complete tested source is in
[`examples/release-checklist`](examples/release-checklist/).
[`examples/release-checklist`](https://github.com/operatorstack/yield/tree/main/examples/release-checklist/).

## Use Yield in five steps

Expand Down Expand Up @@ -213,8 +217,8 @@ forking. Every side effect crosses one of these primitives:
| `require` | Bind a required claim to recorded evidence. |
| `blocked` / `refused` | Stop honestly when work cannot or must not continue. |

See the [primitive guides](docs/primitives/README.md) and
[runtime reference](docs/reference/cli.md) for the full contract.
See the [primitive guides](https://github.com/operatorstack/yield/blob/main/docs/primitives/README.md) and
[runtime reference](https://github.com/operatorstack/yield/blob/main/docs/reference/cli.md) for the full contract.

## Languages and coding agents

Expand All @@ -223,10 +227,10 @@ the same program in every language and compares observable behavior.

| Language | SDK | Example |
|---|---|---|
| TypeScript | [`@operatorstack/yield`](sdk/typescript/) | [`release-checklist`](examples/release-checklist/) |
| Python | [`yieldskill`](sdk/python/) | [`env-doctor`](examples/env-doctor/) |
| Go | [`sdk/yield`](sdk/yield/) | [`investigate`](examples/investigate/) |
| Rust | [`yieldskill`](sdk/rust/) | [`data-migration`](examples/data-migration/) |
| TypeScript | [`@operatorstack/yield`](https://github.com/operatorstack/yield/tree/main/sdk/typescript/) | [`release-checklist`](https://github.com/operatorstack/yield/tree/main/examples/release-checklist/) |
| Python | [`yieldskill`](https://github.com/operatorstack/yield/tree/main/sdk/python/) | [`env-doctor`](https://github.com/operatorstack/yield/tree/main/examples/env-doctor/) |
| Go | [`sdk/yield`](https://github.com/operatorstack/yield/tree/main/sdk/yield/) | [`investigate`](https://github.com/operatorstack/yield/tree/main/examples/investigate/) |
| Rust | [`yieldskill`](https://github.com/operatorstack/yield/tree/main/sdk/rust/) | [`data-migration`](https://github.com/operatorstack/yield/tree/main/examples/data-migration/) |

Cursor, Codex, and Claude Code are verified integrations. Yield also includes
registry-backed project paths for 73 more coding agents. Those paths support
Expand All @@ -250,12 +254,12 @@ loop, multi-agent orchestrator, or security sandbox.
## Documentation and development

- [Read the public documentation](https://yield.operatorstack.systems/docs/)
- [What a skill workflow is](docs/skill-workflows.md)
- [Ten-minute TypeScript quickstart](docs/quickstart.md)
- [Working examples in all four languages](docs/examples.md)
- [Coding-agent setup](docs/agent-setup.md)
- [Testing workflow effects](docs/testing-fixtures.md)
- [Guarantees and evaluation results](evals/README.md)
- [What a skill workflow is](https://github.com/operatorstack/yield/blob/main/docs/skill-workflows.md)
- [Ten-minute TypeScript quickstart](https://github.com/operatorstack/yield/blob/main/docs/quickstart.md)
- [Working examples in all four languages](https://github.com/operatorstack/yield/blob/main/docs/examples.md)
- [Coding-agent setup](https://github.com/operatorstack/yield/blob/main/docs/agent-setup.md)
- [Testing workflow effects](https://github.com/operatorstack/yield/blob/main/docs/testing-fixtures.md)
- [Guarantees and evaluation results](https://github.com/operatorstack/yield/blob/main/evals/README.md)

Run the main checks from the repository root:

Expand All @@ -264,7 +268,7 @@ go test ./...
npm run test:release
```

The [example library](examples/library/) contains ten common workflows in all
The [example library](https://github.com/operatorstack/yield/tree/main/examples/library/) contains ten common workflows in all
four SDKs, including code review, failure investigation, CI repair, dependency
updates, database migration, security audit, and package release.

Expand Down
3 changes: 3 additions & 0 deletions cmd/yskill/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ func TestScaffoldSkillWritesLanguageSpecificEntrypoints(t *testing.T) {
if !strings.Contains(manifest, tt.pin) {
t.Fatalf("manifest does not contain %q:\n%s", tt.pin, manifest)
}
if tt.language == "python" && strings.Contains(manifest, "--index-url") {
t.Fatalf("public Python scaffold contains a private package index:\n%s", manifest)
}
})
}
if tidyCalls != 1 {
Expand Down
2 changes: 1 addition & 1 deletion cmd/yskill/scaffold.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func scaffoldFiles(name, language, sdkPath string) map[string]string {
case "python":
return map[string]string{
"main.py": mainPython,
"requirements.txt": fmt.Sprintf("--index-url https://get.operatorstack.systems/pip/simple/\nyieldskill==%s\n", v),
"requirements.txt": fmt.Sprintf("yieldskill==%s\n", v),
"skill.json": "{\"version\":1,\"language\":\"python\",\"run\":[\"python\",\"main.py\"]}\n",
}
case "rust":
Expand Down
4 changes: 2 additions & 2 deletions evals/results/latest.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"schema_version": 2,
"methodology_version": "1.1",
"generated_at": "2026-08-07T20:19:15.755Z",
"source_digest": "1695983e8279e9580deefe615b29814c8cc2777ce043cc73dd47caf992d7f269",
"generated_at": "2026-08-07T21:08:41.456Z",
"source_digest": "e702eb9bc1362b9a4d77fd41c9860bad97405c7aab3b40aba3a6ba3046c43933",
"status": "passed",
"workflow_conformance": {
"passed": 40,
Expand Down
6 changes: 4 additions & 2 deletions examples/env-doctor/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
from yieldskill import define_skill # noqa: E402


# README_EXAMPLE_START
def program(ctx):
probe = ctx.run_command("probe-python", "python3 --version", timeout_seconds=60)
probe = ctx.run_command("probe-python", "python3 --version || python --version", timeout_seconds=60)

diagnosis = ctx.agent_task(
"diagnose",
Expand All @@ -35,7 +36,7 @@ def program(ctx):
)
if answer != "done":
ctx.blocked("the environment fix was not applied")
recheck = ctx.run_command("recheck-python", "python3 --version", timeout_seconds=60)
recheck = ctx.run_command("recheck-python", "python3 --version || python --version", timeout_seconds=60)
ctx.require(recheck.exit_code == 0, "the environment probe passes after the fix", recheck)
return {"healthy": True, "fixed": True}

Expand All @@ -44,3 +45,4 @@ def program(ctx):


define_skill(program)
# README_EXAMPLE_END
9 changes: 7 additions & 2 deletions packaging/assemble.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export function isPackageVersion(value) {
return stableVersion.test(value) || canaryVersion.test(value);
}

export function npmReadme(readme) {
return readme.replace(/\s*<!-- npm-exclude:start -->[\s\S]*?<!-- npm-exclude:end -->/g, "");
}

function parseArgs(argv) {
const values = {};
for (let index = 0; index < argv.length; index += 2) values[argv[index]?.replace(/^--/, "")] = argv[index + 1];
Expand Down Expand Up @@ -51,11 +55,12 @@ async function assembleNpm({ version, binaries, output }) {
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")),
const [readme] = await Promise.all([
readFile(join(root, "README.md"), "utf8"),
cp(join(root, "LICENSE"), join(main, "LICENSE")),
cp(join(root, "assets/yield-mark.svg"), join(main, "assets/yield-mark.svg")),
]);
await writeFile(join(main, "README.md"), npmReadme(readme));
const packageJson = await json(join(main, "package.json"));
packageJson.version = version;
packageJson.publishConfig = {
Expand Down
5 changes: 4 additions & 1 deletion packaging/assemble.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ test("assembles one public npm package and six matching npm and Python runtimes"
Object.fromEntries(targets.map((target) => [npmPackage(target), "1.2.3"])),
);
const assembledReadme = await readFile(join(output, "npm/yield/README.md"), "utf8");
assert.equal(assembledReadme, await readFile(join(import.meta.dirname, "../README.md"), "utf8"));
const repositoryReadme = await readFile(join(import.meta.dirname, "../README.md"), "utf8");
assert.match(repositoryReadme, /pypi\.org\/project\/yieldskill/);
assert.match(assembledReadme, /npmjs\.com\/package\/@operatorstack\/yield/);
assert.doesNotMatch(assembledReadme, /pypi\.org|PyPI version|npm-exclude/);
assert.equal(
await readFile(join(output, "npm/yield/assets/yield-mark.svg"), "utf8"),
await readFile(join(import.meta.dirname, "../assets/yield-mark.svg"), "utf8"),
Expand Down
59 changes: 59 additions & 0 deletions scripts/readme.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,56 @@ test("README release example matches the tested TypeScript source", async () =>
assert.equal(readmeProgram, sourceMatch[1].trim());
});

test("Python README example matches the tested environment doctor", async () => {
const [readme, source] = await Promise.all([
text("sdk/python/README.md"),
text("examples/env-doctor/main.py"),
]);

const readmeMatch = readme.match(
/<!-- python-example:start -->\s*```python\n([\s\S]*?)\n```\s*<!-- python-example:end -->/,
);
assert.ok(readmeMatch, "Python README example markers are missing");

const sourceMatch = source.match(
/# README_EXAMPLE_START\n([\s\S]*?)\n# README_EXAMPLE_END/,
);
assert.ok(sourceMatch, "Python source example markers are missing");

const readmeProgram = readmeMatch[1]
.replace(/^from yieldskill import define_skill\n+/, "")
.trim();
assert.equal(readmeProgram, sourceMatch[1].trim());
});

test("Python README presents a public five-step workflow", async () => {
const readme = await text("sdk/python/README.md");
const headings = [
"### 1. Install Yield",
"### 2. Create the workflow",
"### 3. Test the workflow",
"### 4. Register the skill",
"### 5. Run 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, /python -m pip install yieldskill/);
assert.match(readme, /python -m yieldskill init skills\/env-doctor/);
assert.match(readme, /python -m yieldskill doctor skills\/env-doctor --test/);
assert.match(readme, /python -m yieldskill register skills\/env-doctor/);
assert.match(readme, /^\/env-doctor$/m);
assert.match(readme, /https:\/\/github\.com\/operatorstack\/yield\/blob\/main\/docs\/reference\/cli\.md/);
assert.doesNotMatch(readme, /get\.operatorstack\.systems\/pip/);
assert.doesNotMatch(readme, /npmjs\.com|npm version/);
assert.doesNotMatch(readme, /(?:href|src)="(?!https:\/\/)/);
});

test("README agent claims match the pinned registry", async () => {
const [readme, registryText] = await Promise.all([
text("README.md"),
Expand Down Expand Up @@ -132,3 +182,12 @@ test("README and quickstart use the public documentation and package registries"
assert.match(quickstart, /^\/review$/m);
assert.match(agentSetup, /^## Run the registered skill$/m);
});

test("root README links survive npm package rendering", async () => {
const readme = await text("README.md");
assert.match(readme, /https:\/\/github\.com\/operatorstack\/yield\/blob\/main\/docs\/skill-workflows\.md/);
assert.match(readme, /https:\/\/github\.com\/operatorstack\/yield\/blob\/main\/evals\/README\.md/);
assert.match(readme, /https:\/\/github\.com\/operatorstack\/yield\/tree\/main\/examples\/library\//);
assert.doesNotMatch(readme, /\]\((?!https?:\/\/|#|mailto:)[^)]+\)/);
assert.doesNotMatch(readme, /href="(?!https?:\/\/|#|mailto:)[^"]+"/);
});
Loading