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
2 changes: 1 addition & 1 deletion .agents/skills/release-yield/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: release-yield
description: "Release Yield through its protected GitHub workflows and verify every public registry."
---

<!-- generated-by: yskill; source: skills/release-yield; digest: sha256:85ef5fa8c0d09817ced3213451a72e211bd92736a9e851b567b2fd4034cc5c4c; version: 0.1.38 -->
<!-- generated-by: yskill; source: skills/release-yield; digest: sha256:5193de687010c12767ddb81ef93d3d925b769ad46e67bf926d26bb66164a2bf6; version: 0.1.38 -->

This adapter exposes the canonical Yield workflow at `skills/release-yield`.
Read its SKILL.md, then run from the repository root:
Expand Down
5 changes: 5 additions & 0 deletions .changeset/immutable-release-and-onboarding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@operatorstack/yield": patch
---

Publish verified npm archives instead of extracted directories, require each workflow to declare the running Yield version, and keep Go and Rust workflow launchers local to the project.
2 changes: 1 addition & 1 deletion .claude/skills/release-yield/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: release-yield
description: "Release Yield through its protected GitHub workflows and verify every public registry."
---

<!-- generated-by: yskill; source: skills/release-yield; digest: sha256:85ef5fa8c0d09817ced3213451a72e211bd92736a9e851b567b2fd4034cc5c4c; version: 0.1.38 -->
<!-- generated-by: yskill; source: skills/release-yield; digest: sha256:5193de687010c12767ddb81ef93d3d925b769ad46e67bf926d26bb66164a2bf6; version: 0.1.38 -->

This adapter exposes the canonical Yield workflow at `skills/release-yield`.
Read its SKILL.md, then run from the repository root:
Expand Down
2 changes: 1 addition & 1 deletion .cursor/skills/release-yield/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: release-yield
description: "Release Yield through its protected GitHub workflows and verify every public registry."
---

<!-- generated-by: yskill; source: skills/release-yield; digest: sha256:85ef5fa8c0d09817ced3213451a72e211bd92736a9e851b567b2fd4034cc5c4c; version: 0.1.38 -->
<!-- generated-by: yskill; source: skills/release-yield; digest: sha256:5193de687010c12767ddb81ef93d3d925b769ad46e67bf926d26bb66164a2bf6; version: 0.1.38 -->

This adapter exposes the canonical Yield workflow at `skills/release-yield`.
Read its SKILL.md, then run from the repository root:
Expand Down
23 changes: 11 additions & 12 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,17 @@ jobs:
cp "$directory/target/package/${name}-${VERSION}.crate" dist/crates/
done
test "$(find dist/crates -maxdepth 1 -name '*.crate' | wc -l | tr -d ' ')" = 6
- name: Inspect npm tarballs
- name: Pack immutable npm release unit
shell: bash
run: |
set -euo pipefail
for directory in dist/packages/npm/*; do
(cd "$directory" && npm pack --dry-run)
done
node packaging/npm-release.mjs --source dist/packages/npm --output dist/release-unit/npm --source-sha "${{ needs.resolve.outputs.source_sha }}"
- name: Assemble immutable release unit
env:
CHANNEL: ${{ needs.resolve.outputs.channel }}
run: |
mkdir -p dist/release-unit
cp dist/packages/SHA256SUMS.json dist/release-unit/
cp -R dist/packages/npm dist/release-unit/npm
if [[ "$CHANNEL" == stable ]]; then
cp -R dist/pypi dist/release-unit/pypi
cp -R dist/packages/rust dist/release-unit/rust
Expand Down Expand Up @@ -186,12 +183,13 @@ jobs:
shell: bash
run: |
set -euo pipefail
for directory in dist/release-unit/npm/darwin-* dist/release-unit/npm/linux-* dist/release-unit/npm/windows-*; do
package="$(node -p "require('./${directory}/package.json').name")"
jq -c '.archives[] | select(.name != "@operatorstack/yield" and .name != "@operatorstack/create-yield")' dist/release-unit/npm/npm-release.json | while read -r archive; do
package="$(jq -r '.name' <<< "$archive")"
file="$(jq -r '.file' <<< "$archive")"
if npm view "${package}@${VERSION}" version >/dev/null 2>&1; then
echo "${package}@${VERSION} already exists"
else
(cd "$directory" && npm publish --tag "$DIST_TAG")
npm publish "dist/release-unit/npm/${file}" --tag "$DIST_TAG"
fi
done
- name: Publish SDK and CLI
Expand All @@ -204,7 +202,8 @@ jobs:
if npm view "@operatorstack/yield@${VERSION}" version >/dev/null 2>&1; then
echo "@operatorstack/yield@${VERSION} already exists"
else
(cd dist/release-unit/npm/yield && npm publish --tag "$DIST_TAG")
file="$(jq -r '.archives[] | select(.name == "@operatorstack/yield") | .file' dist/release-unit/npm/npm-release.json)"
npm publish "dist/release-unit/npm/${file}" --tag "$DIST_TAG"
fi
- name: Publish npm initializer
env:
Expand All @@ -216,16 +215,16 @@ jobs:
if npm view "@operatorstack/create-yield@${VERSION}" version >/dev/null 2>&1; then
echo "@operatorstack/create-yield@${VERSION} already exists"
else
(cd dist/release-unit/npm/create-yield && npm publish --tag "$DIST_TAG")
file="$(jq -r '.archives[] | select(.name == "@operatorstack/create-yield") | .file' dist/release-unit/npm/npm-release.json)"
npm publish "dist/release-unit/npm/${file}" --tag "$DIST_TAG"
fi
- name: Verify complete npm release unit
env:
VERSION: ${{ needs.resolve.outputs.version }}
shell: bash
run: |
set -euo pipefail
for directory in dist/release-unit/npm/*; do
package="$(node -p "require('./${directory}/package.json').name")"
jq -r '.archives[].name' dist/release-unit/npm/npm-release.json | while read -r package; do
for attempt in {1..12}; do
if [[ "$(npm view "${package}@${VERSION}" version 2>/dev/null || true)" == "$VERSION" ]]; then break; fi
test "$attempt" -lt 12
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/release-finalize.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,16 @@ jobs:
--source-sha "$SOURCE_SHA" \
--attempts 3 \
--delay-ms 10000
node evals/scripts/run.mjs --check
test "$(git rev-list -n 1 "$TAG")" = "$SOURCE_SHA"
assets="$RUNNER_TEMP/release-assets"
mkdir -p "$assets"
node packaging/package-contract.mjs \
--version "$version" \
--source-sha "$SOURCE_SHA" \
--npm-release "$RUNNER_TEMP/release-unit/npm/npm-release.json" \
--evaluation evals/results/latest.json \
--release-url "https://github.com/${GITHUB_REPOSITORY}/releases/tag/${TAG}" \
--output "$assets"
gh release upload "$TAG" "$assets/yield-package-contract.json" "$assets/yield-website-promotion.json" --repo "$GITHUB_REPOSITORY" --clobber
gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --draft=false
6 changes: 5 additions & 1 deletion .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ jobs:
sdk/rust
examples/data-migration
examples/library/rust
- run: npm ci --ignore-scripts
- name: Rerun first-party evaluations
working-directory: evals
run: |
Expand All @@ -178,7 +179,10 @@ jobs:
run: npm run test:conversion
- name: Run every example fixture
run: |
go build -o "$RUNNER_TEMP/yskill" ./cmd/yskill
plan="$RUNNER_TEMP/example-release-plan.env"
node scripts/release-plan.mjs --bump auto --output "$plan" --notes "$RUNNER_TEMP/example-release-notes.md"
. "$plan"
go build -ldflags "-X main.version=$version" -o "$RUNNER_TEMP/yskill" ./cmd/yskill
"$RUNNER_TEMP/yskill" test examples/investigate
"$RUNNER_TEMP/yskill" test examples/release-checklist
"$RUNNER_TEMP/yskill" test examples/env-doctor
Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ available for 73 more coding agents.

Run the command for your project:

| Language | Command |
| ---------- | --------------------------------------------------------------------------------- |
| TypeScript | `npm create @operatorstack/yield@latest` |
| Python | `uvx --from yieldskill yskill bootstrap --language python` |
| Rust | `cargo install yieldskill --locked`, then `yskill bootstrap --language rust` |
| Go | `go run github.com/operatorstack/yield/cmd/yskill@latest bootstrap --language go` |
| Language | Command |
| ---------- | -------------------------------------------------------------------------------------------------------------- |
| TypeScript | `npm create @operatorstack/yield@latest` |
| Python | `uvx --from yieldskill yskill bootstrap --language python` |
| Rust | `cargo install yieldskill --root .yield --locked`, then `.yield/bin/yskill bootstrap --root . --language rust` |
| Go | `go run github.com/operatorstack/yield/cmd/yskill@latest bootstrap --root . --language go` |

Yield detects the repository, language, and installed coding agents. It shows
every proposed file and dependency change. It asks before it writes. It then
Expand Down Expand Up @@ -159,6 +159,7 @@ workflow URL without publishing.

Every newly published canary runs the same contract tests in an isolated CI
lane. Stable release execution remains pinned to an exact public version.

<!-- npm-exclude:end -->

## Advanced: build manually
Expand Down
3 changes: 3 additions & 0 deletions cmd/yskill/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,9 @@ func verifyWorkflowSDKVersion(manifest skillManifest, skillDir, repoRoot, expect
if expected == "dev" {
return nil
}
if manifest.YieldVersion != expected {
return fmt.Errorf("workflow declares Yield %s, but the running supervisor is Yield %s", manifest.YieldVersion, expected)
}
var declared string
if manifest.Language == "typescript" {
root, err := findTypeScriptPackageRoot(skillDir, repoRoot)
Expand Down
14 changes: 7 additions & 7 deletions cmd/yskill/agents_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func TestEveryRegistryAgentGeneratesAContainedAdapter(t *testing.T) {
writeTestFile(t, filepath.Join(repo, "package.json"), `{"dependencies":{"@operatorstack/yield":"0.1.17"}}`)
skill := filepath.Join(repo, "workflows", "review")
writeTestFile(t, filepath.Join(skill, "SKILL.md"), "---\nname: review\ndescription: Review the branch when the user wants code checked before shipping.\n---\n")
writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"language":"typescript","run":["node","main.ts"]}`)
writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"yield_version":"0.1.23","language":"typescript","run":["node","main.ts"]}`)
writeTestFile(t, filepath.Join(skill, "main.ts"), "export {}\n")
registry, err := loadAgentRegistry()
if err != nil {
Expand Down Expand Up @@ -225,7 +225,7 @@ func TestRegisterAllPreflightsAndWritesEveryWorkflow(t *testing.T) {
for _, name := range []string{"review", "release"} {
skill := filepath.Join(repo, "skills", name)
writeTestFile(t, filepath.Join(skill, "SKILL.md"), "---\nname: "+name+"\ndescription: Run "+name+" when the matching project workflow is requested.\n---\n")
writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"language":"typescript","run":["node","main.ts"]}`)
writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"yield_version":"0.1.23","language":"typescript","run":["node","main.ts"]}`)
writeTestFile(t, filepath.Join(skill, "main.ts"), "export {}\n")
}
output := captureStdout(t, func() {
Expand Down Expand Up @@ -256,7 +256,7 @@ func TestRegisterAllPruneRemovesOnlyOwnedAdapterFile(t *testing.T) {
for _, name := range []string{"review", "release"} {
skill := filepath.Join(repo, "skills", name)
writeTestFile(t, filepath.Join(skill, "SKILL.md"), "---\nname: "+name+"\ndescription: Run "+name+" when the matching project workflow is requested.\n---\n")
writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"language":"typescript","run":["node","main.ts"]}`)
writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"yield_version":"0.1.23","language":"typescript","run":["node","main.ts"]}`)
writeTestFile(t, filepath.Join(skill, "main.ts"), "export {}\n")
}
if err := cmdRegisterAll([]string{filepath.Join(repo, "skills"), "--root", repo, "--agent", "codex"}); err != nil {
Expand Down Expand Up @@ -285,7 +285,7 @@ func TestRegisterAllRefusesCrossDirectoryNameCollisionBeforeWrites(t *testing.T)
makeSkill := func(parent string) string {
skill := filepath.Join(repo, parent, "review")
writeTestFile(t, filepath.Join(skill, "SKILL.md"), "---\nname: review\ndescription: Review the project when a user asks for a check.\n---\n")
writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"language":"typescript","run":["node","main.ts"]}`)
writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"yield_version":"0.1.23","language":"typescript","run":["node","main.ts"]}`)
writeTestFile(t, filepath.Join(skill, "main.ts"), "export {}\n")
return skill
}
Expand Down Expand Up @@ -442,7 +442,7 @@ func TestWorkflowSDKVersionMustMatchRuntime(t *testing.T) {

func TestGoWorkflowSDKVersionAcceptsGeneratedAndBlockForms(t *testing.T) {
repo := t.TempDir()
manifest := skillManifest{Version: 1, Language: "go", Run: []string{"go", "run", "."}}
manifest := skillManifest{Version: 1, YieldVersion: "0.1.23", Language: "go", Run: []string{"go", "run", "."}}
for name, goMod := range map[string]string{
"generated": "module review\n\ngo 1.26.5\n\nrequire github.com/operatorstack/yield v0.1.23\n",
"block": "module review\n\ngo 1.26.5\n\nrequire (\n\tgithub.com/operatorstack/yield v0.1.23\n)\n",
Expand All @@ -462,7 +462,7 @@ func TestDoctorReportsSDKRuntimeAndAdapterVersionProblemsTogether(t *testing.T)
writeTestFile(t, filepath.Join(repo, ".git", "keep"), "")
skill := filepath.Join(repo, "skills", "review")
writeTestFile(t, filepath.Join(skill, "SKILL.md"), "---\nname: review\ndescription: Review code before it is shipped.\n---\n")
writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"language":"go","run":["go","run","."]}`)
writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"yield_version":"0.1.23","language":"go","run":["go","run","."]}`)
writeTestFile(t, filepath.Join(skill, "go.mod"), "module review\n\ngo 1.26.5\n\nrequire github.com/operatorstack/yield v0.1.22\n")
writeTestFile(t, filepath.Join(skill, "main.go"), "package main\nfunc main() {}\n")
writeTestFile(t, localRuntimePath(repo), "runtime")
Expand Down Expand Up @@ -491,7 +491,7 @@ func createTypeScriptSkill(t *testing.T, repo, name string) string {
writeTestFile(t, filepath.Join(repo, "package.json"), `{"dependencies":{"@operatorstack/yield":"0.1.17"}}`)
skill := filepath.Join(repo, "skills", name)
writeTestFile(t, filepath.Join(skill, "SKILL.md"), "---\nname: "+name+"\ndescription: Review the branch when the user wants code checked before shipping.\n---\n")
writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"language":"typescript","run":["node","main.ts"]}`)
writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"yield_version":"0.1.23","language":"typescript","run":["node","main.ts"]}`)
writeTestFile(t, filepath.Join(skill, "main.ts"), "export {}\n")
return skill
}
Expand Down
9 changes: 7 additions & 2 deletions cmd/yskill/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
)
Expand Down Expand Up @@ -247,8 +248,12 @@ func printBootstrapPlan(plan bootstrapPlan) {
if plan.Dependency != "" {
fmt.Printf(" run %s\n", plan.Dependency)
}
fmt.Println(" run yskill doctor skills/yield-workflow-builder --test")
fmt.Println(" run yskill register skills/yield-workflow-builder")
launcher := "yskill"
if plan.Language == "go" || plan.Language == "rust" {
launcher = repositoryRuntimeLauncher(filepath.ToSlash(filepath.Join(".yield", "bin", "yskill")), runtime.GOOS)
}
fmt.Printf(" run %s doctor skills/yield-workflow-builder --root . --test\n", launcher)
fmt.Printf(" run %s register skills/yield-workflow-builder --root .\n", launcher)
}

func detectBootstrapLanguage(root string) (string, error) {
Expand Down
8 changes: 4 additions & 4 deletions cmd/yskill/bootstrap_templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,21 @@ log. It is not a generated destination file.
case "typescript":
files["main.ts"] = bootstrapTypeScript
files["package.json"] = fmt.Sprintf("{\n \"private\": true,\n \"type\": \"module\",\n \"dependencies\": { \"@operatorstack/yield\": \"%s\" }\n}\n", version)
files["skill.json"] = "{\"version\":1,\"language\":\"typescript\",\"run\":[\"node\",\"main.ts\"]}\n"
files["skill.json"] = fmt.Sprintf("{\"version\":1,\"yield_version\":%q,\"language\":\"typescript\",\"run\":[\"node\",\"main.ts\"]}\n", version)
dependency = "npm install --ignore-scripts --no-audit --no-fund (inside skills/yield-workflow-builder)"
case "python":
files["main.py"] = bootstrapPython
files["requirements.txt"] = fmt.Sprintf("yieldskill==%s\n", version)
files["skill.json"] = "{\"version\":1,\"language\":\"python\",\"run\":[\"python\",\"main.py\"]}\n"
files["skill.json"] = fmt.Sprintf("{\"version\":1,\"yield_version\":%q,\"language\":\"python\",\"run\":[\"python\",\"main.py\"]}\n", version)
case "go":
files["main.go"] = bootstrapGo
files["go.mod"] = fmt.Sprintf("module yield-workflow-builder\n\ngo 1.26.5\n\nrequire github.com/operatorstack/yield v%s\n", version)
files["skill.json"] = "{\"version\":1,\"language\":\"go\",\"run\":[\"go\",\"run\",\"-mod=readonly\",\".\"]}\n"
files["skill.json"] = fmt.Sprintf("{\"version\":1,\"yield_version\":%q,\"language\":\"go\",\"run\":[\"go\",\"run\",\"-mod=readonly\",\".\"]}\n", version)
dependency = "go mod tidy (inside skills/yield-workflow-builder)"
case "rust":
files["src/main.rs"] = bootstrapRust
files["Cargo.toml"] = fmt.Sprintf("[package]\nname = \"yield-workflow-builder\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nyieldskill = { version = \"=%s\" }\nserde_json = \"1\"\n", version)
files["skill.json"] = "{\"version\":1,\"language\":\"rust\",\"run\":[\"cargo\",\"run\",\"--quiet\"]}\n"
files["skill.json"] = fmt.Sprintf("{\"version\":1,\"yield_version\":%q,\"language\":\"rust\",\"run\":[\"cargo\",\"run\",\"--quiet\"]}\n", version)
default:
return nil, "", fmt.Errorf("unsupported language %q", language)
}
Expand Down
Loading