Skip to content

Commit e8ef677

Browse files
authored
Improve Python package onboarding (#44)
1 parent 99d37b4 commit e8ef677

10 files changed

Lines changed: 299 additions & 45 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@operatorstack/yield": patch
3+
---
4+
5+
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.

README.md

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,20 @@
1414

1515
<p align="center">
1616
<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>
17+
<!-- npm-exclude:start -->
1718
<a href="https://pypi.org/project/yieldskill/"><img alt="PyPI version" src="https://img.shields.io/pypi/v/yieldskill?style=flat-square" /></a>
19+
<!-- npm-exclude:end -->
1820
<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>
19-
<a href="LICENSE"><img alt="MIT license" src="https://img.shields.io/npm/l/@operatorstack/yield?style=flat-square" /></a>
21+
<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>
2022
</p>
2123

2224
<p align="center">
2325
<a href="https://yield.operatorstack.systems/">Website</a> ·
2426
<a href="https://yield.operatorstack.systems/docs/">Documentation</a> ·
2527
<a href="https://www.npmjs.com/package/@operatorstack/yield">npm</a> ·
28+
<!-- npm-exclude:start -->
2629
<a href="https://pypi.org/project/yieldskill/">PyPI</a> ·
30+
<!-- npm-exclude:end -->
2731
<a href="https://github.com/operatorstack/yield">GitHub</a>
2832
</p>
2933

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

103107
## Use Yield in five steps
104108

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

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

219223
## Languages and coding agents
220224

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

224228
| Language | SDK | Example |
225229
|---|---|---|
226-
| TypeScript | [`@operatorstack/yield`](sdk/typescript/) | [`release-checklist`](examples/release-checklist/) |
227-
| Python | [`yieldskill`](sdk/python/) | [`env-doctor`](examples/env-doctor/) |
228-
| Go | [`sdk/yield`](sdk/yield/) | [`investigate`](examples/investigate/) |
229-
| Rust | [`yieldskill`](sdk/rust/) | [`data-migration`](examples/data-migration/) |
230+
| TypeScript | [`@operatorstack/yield`](https://github.com/operatorstack/yield/tree/main/sdk/typescript/) | [`release-checklist`](https://github.com/operatorstack/yield/tree/main/examples/release-checklist/) |
231+
| Python | [`yieldskill`](https://github.com/operatorstack/yield/tree/main/sdk/python/) | [`env-doctor`](https://github.com/operatorstack/yield/tree/main/examples/env-doctor/) |
232+
| Go | [`sdk/yield`](https://github.com/operatorstack/yield/tree/main/sdk/yield/) | [`investigate`](https://github.com/operatorstack/yield/tree/main/examples/investigate/) |
233+
| Rust | [`yieldskill`](https://github.com/operatorstack/yield/tree/main/sdk/rust/) | [`data-migration`](https://github.com/operatorstack/yield/tree/main/examples/data-migration/) |
230234

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

252256
- [Read the public documentation](https://yield.operatorstack.systems/docs/)
253-
- [What a skill workflow is](docs/skill-workflows.md)
254-
- [Ten-minute TypeScript quickstart](docs/quickstart.md)
255-
- [Working examples in all four languages](docs/examples.md)
256-
- [Coding-agent setup](docs/agent-setup.md)
257-
- [Testing workflow effects](docs/testing-fixtures.md)
258-
- [Guarantees and evaluation results](evals/README.md)
257+
- [What a skill workflow is](https://github.com/operatorstack/yield/blob/main/docs/skill-workflows.md)
258+
- [Ten-minute TypeScript quickstart](https://github.com/operatorstack/yield/blob/main/docs/quickstart.md)
259+
- [Working examples in all four languages](https://github.com/operatorstack/yield/blob/main/docs/examples.md)
260+
- [Coding-agent setup](https://github.com/operatorstack/yield/blob/main/docs/agent-setup.md)
261+
- [Testing workflow effects](https://github.com/operatorstack/yield/blob/main/docs/testing-fixtures.md)
262+
- [Guarantees and evaluation results](https://github.com/operatorstack/yield/blob/main/evals/README.md)
259263

260264
Run the main checks from the repository root:
261265

@@ -264,7 +268,7 @@ go test ./...
264268
npm run test:release
265269
```
266270

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

cmd/yskill/main_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ func TestScaffoldSkillWritesLanguageSpecificEntrypoints(t *testing.T) {
240240
if !strings.Contains(manifest, tt.pin) {
241241
t.Fatalf("manifest does not contain %q:\n%s", tt.pin, manifest)
242242
}
243+
if tt.language == "python" && strings.Contains(manifest, "--index-url") {
244+
t.Fatalf("public Python scaffold contains a private package index:\n%s", manifest)
245+
}
243246
})
244247
}
245248
if tidyCalls != 1 {

cmd/yskill/scaffold.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func scaffoldFiles(name, language, sdkPath string) map[string]string {
154154
case "python":
155155
return map[string]string{
156156
"main.py": mainPython,
157-
"requirements.txt": fmt.Sprintf("--index-url https://get.operatorstack.systems/pip/simple/\nyieldskill==%s\n", v),
157+
"requirements.txt": fmt.Sprintf("yieldskill==%s\n", v),
158158
"skill.json": "{\"version\":1,\"language\":\"python\",\"run\":[\"python\",\"main.py\"]}\n",
159159
}
160160
case "rust":

evals/results/latest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"schema_version": 2,
33
"methodology_version": "1.1",
4-
"generated_at": "2026-08-07T20:19:15.755Z",
5-
"source_digest": "1695983e8279e9580deefe615b29814c8cc2777ce043cc73dd47caf992d7f269",
4+
"generated_at": "2026-08-07T21:08:41.456Z",
5+
"source_digest": "e702eb9bc1362b9a4d77fd41c9860bad97405c7aab3b40aba3a6ba3046c43933",
66
"status": "passed",
77
"workflow_conformance": {
88
"passed": 40,

examples/env-doctor/main.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
from yieldskill import define_skill # noqa: E402
1010

1111

12+
# README_EXAMPLE_START
1213
def program(ctx):
13-
probe = ctx.run_command("probe-python", "python3 --version", timeout_seconds=60)
14+
probe = ctx.run_command("probe-python", "python3 --version || python --version", timeout_seconds=60)
1415

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

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

4546

4647
define_skill(program)
48+
# README_EXAMPLE_END

packaging/assemble.mjs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export function isPackageVersion(value) {
1313
return stableVersion.test(value) || canaryVersion.test(value);
1414
}
1515

16+
export function npmReadme(readme) {
17+
return readme.replace(/\s*<!-- npm-exclude:start -->[\s\S]*?<!-- npm-exclude:end -->/g, "");
18+
}
19+
1620
function parseArgs(argv) {
1721
const values = {};
1822
for (let index = 0; index < argv.length; index += 2) values[argv[index]?.replace(/^--/, "")] = argv[index + 1];
@@ -51,11 +55,12 @@ async function assembleNpm({ version, binaries, output }) {
5155
const main = join(npm, "yield");
5256
await cp(join(root, "sdk/typescript"), main, { recursive: true, filter: (source) => !source.includes("node_modules") && !source.includes("/dist") });
5357
await mkdir(join(main, "assets"), { recursive: true });
54-
await Promise.all([
55-
cp(join(root, "README.md"), join(main, "README.md")),
58+
const [readme] = await Promise.all([
59+
readFile(join(root, "README.md"), "utf8"),
5660
cp(join(root, "LICENSE"), join(main, "LICENSE")),
5761
cp(join(root, "assets/yield-mark.svg"), join(main, "assets/yield-mark.svg")),
5862
]);
63+
await writeFile(join(main, "README.md"), npmReadme(readme));
5964
const packageJson = await json(join(main, "package.json"));
6065
packageJson.version = version;
6166
packageJson.publishConfig = {

packaging/assemble.test.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ test("assembles one public npm package and six matching npm and Python runtimes"
4444
Object.fromEntries(targets.map((target) => [npmPackage(target), "1.2.3"])),
4545
);
4646
const assembledReadme = await readFile(join(output, "npm/yield/README.md"), "utf8");
47-
assert.equal(assembledReadme, await readFile(join(import.meta.dirname, "../README.md"), "utf8"));
47+
const repositoryReadme = await readFile(join(import.meta.dirname, "../README.md"), "utf8");
48+
assert.match(repositoryReadme, /pypi\.org\/project\/yieldskill/);
49+
assert.match(assembledReadme, /npmjs\.com\/package\/@operatorstack\/yield/);
50+
assert.doesNotMatch(assembledReadme, /pypi\.org|PyPI version|npm-exclude/);
4851
assert.equal(
4952
await readFile(join(output, "npm/yield/assets/yield-mark.svg"), "utf8"),
5053
await readFile(join(import.meta.dirname, "../assets/yield-mark.svg"), "utf8"),

scripts/readme.test.mjs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,56 @@ test("README release example matches the tested TypeScript source", async () =>
3131
assert.equal(readmeProgram, sourceMatch[1].trim());
3232
});
3333

34+
test("Python README example matches the tested environment doctor", async () => {
35+
const [readme, source] = await Promise.all([
36+
text("sdk/python/README.md"),
37+
text("examples/env-doctor/main.py"),
38+
]);
39+
40+
const readmeMatch = readme.match(
41+
/<!-- python-example:start -->\s*```python\n([\s\S]*?)\n```\s*<!-- python-example:end -->/,
42+
);
43+
assert.ok(readmeMatch, "Python README example markers are missing");
44+
45+
const sourceMatch = source.match(
46+
/# README_EXAMPLE_START\n([\s\S]*?)\n# README_EXAMPLE_END/,
47+
);
48+
assert.ok(sourceMatch, "Python source example markers are missing");
49+
50+
const readmeProgram = readmeMatch[1]
51+
.replace(/^from yieldskill import define_skill\n+/, "")
52+
.trim();
53+
assert.equal(readmeProgram, sourceMatch[1].trim());
54+
});
55+
56+
test("Python README presents a public five-step workflow", async () => {
57+
const readme = await text("sdk/python/README.md");
58+
const headings = [
59+
"### 1. Install Yield",
60+
"### 2. Create the workflow",
61+
"### 3. Test the workflow",
62+
"### 4. Register the skill",
63+
"### 5. Run the skill",
64+
];
65+
66+
let previous = -1;
67+
for (const heading of headings) {
68+
const current = readme.indexOf(heading);
69+
assert.ok(current > previous, `${heading} is missing or out of order`);
70+
previous = current;
71+
}
72+
73+
assert.match(readme, /python -m pip install yieldskill/);
74+
assert.match(readme, /python -m yieldskill init skills\/env-doctor/);
75+
assert.match(readme, /python -m yieldskill doctor skills\/env-doctor --test/);
76+
assert.match(readme, /python -m yieldskill register skills\/env-doctor/);
77+
assert.match(readme, /^\/env-doctor$/m);
78+
assert.match(readme, /https:\/\/github\.com\/operatorstack\/yield\/blob\/main\/docs\/reference\/cli\.md/);
79+
assert.doesNotMatch(readme, /get\.operatorstack\.systems\/pip/);
80+
assert.doesNotMatch(readme, /npmjs\.com|npm version/);
81+
assert.doesNotMatch(readme, /(?:href|src)="(?!https:\/\/)/);
82+
});
83+
3484
test("README agent claims match the pinned registry", async () => {
3585
const [readme, registryText] = await Promise.all([
3686
text("README.md"),
@@ -132,3 +182,12 @@ test("README and quickstart use the public documentation and package registries"
132182
assert.match(quickstart, /^\/review$/m);
133183
assert.match(agentSetup, /^## Run the registered skill$/m);
134184
});
185+
186+
test("root README links survive npm package rendering", async () => {
187+
const readme = await text("README.md");
188+
assert.match(readme, /https:\/\/github\.com\/operatorstack\/yield\/blob\/main\/docs\/skill-workflows\.md/);
189+
assert.match(readme, /https:\/\/github\.com\/operatorstack\/yield\/blob\/main\/evals\/README\.md/);
190+
assert.match(readme, /https:\/\/github\.com\/operatorstack\/yield\/tree\/main\/examples\/library\//);
191+
assert.doesNotMatch(readme, /\]\((?!https?:\/\/|#|mailto:)[^)]+\)/);
192+
assert.doesNotMatch(readme, /href="(?!https?:\/\/|#|mailto:)[^"]+"/);
193+
});

0 commit comments

Comments
 (0)