diff --git a/.agents/scripts/test_skill_release.py b/.agents/scripts/test_skill_release.py index d46ea5b..1691b1e 100644 --- a/.agents/scripts/test_skill_release.py +++ b/.agents/scripts/test_skill_release.py @@ -33,6 +33,15 @@ def test_skill_frontmatter_declares_a_semver_release_version(self) -> None: assert match is not None self.assertRegex(match.group(1), SEMVER) + def test_readme_release_link_matches_the_skill_version(self) -> None: + skill = SKILL.read_text(encoding="utf-8") + version = re.search(r"^version:\s*([^\s#]+)\s*$", skill, re.MULTILINE) + self.assertIsNotNone(version, "SKILL.md must declare a release version") + assert version is not None + readme = (ROOT / "README.md").read_text(encoding="utf-8") + expected = f"releases/tag/v{version.group(1)}" + self.assertIn(expected, readme, "README GitHub Release link must match SKILL.md version") + def test_version_extraction_step_executes_and_writes_outputs(self) -> None: workflow = WORKFLOW.read_text(encoding="utf-8") match = re.search( @@ -65,7 +74,7 @@ def test_version_extraction_step_executes_and_writes_outputs(self) -> None: else: os.environ["GITHUB_OUTPUT"] = previous_output - self.assertEqual(output_path.read_text(encoding="utf-8"), "skill_version=0.2.1\ntag=v0.2.1\n") + self.assertEqual(output_path.read_text(encoding="utf-8"), "skill_version=0.2.2\ntag=v0.2.2\n") def test_version_extraction_step_rejects_invalid_numeric_prerelease_identifiers(self) -> None: workflow = WORKFLOW.read_text(encoding="utf-8") @@ -85,7 +94,7 @@ def test_version_extraction_step_rejects_invalid_numeric_prerelease_identifiers( skill_path = root / "skills/api-to-typemcp/SKILL.md" skill_path.parent.mkdir(parents=True) skill_path.write_text( - original.replace("version: 0.2.1", f"version: {invalid_version}"), + original.replace("version: 0.2.2", f"version: {invalid_version}"), encoding="utf-8", ) previous_cwd = Path.cwd() @@ -243,9 +252,9 @@ def test_existing_unpublished_skill_is_recovered_before_version_reconciliation(s [ (200, {"data": [{"slug": "integration"}]}), (200, {"slug": "api-to-typemcp", "status": "DRAFT"}), - (200, {"slug": "api-to-typemcp", "status": "PUBLISHED", "latestVersion": "0.2.1"}), - (200, [{"version": "0.2.1"}]), - (200, {"slug": "api-to-typemcp", "status": "PUBLISHED", "latestVersion": "0.2.1"}), + (200, {"slug": "api-to-typemcp", "status": "PUBLISHED", "latestVersion": "0.2.2"}), + (200, [{"version": "0.2.2"}]), + (200, {"slug": "api-to-typemcp", "status": "PUBLISHED", "latestVersion": "0.2.2"}), ] ) calls: list[tuple[str, str]] = [] @@ -258,7 +267,7 @@ def mocked_request( environment = { "SKILLS_HUB_AI_API_KEY": "test-key", - "SKILL_VERSION": "0.2.1", + "SKILL_VERSION": "0.2.2", "GITHUB_SHA": "test-sha", "GITHUB_REPOSITORY": "Theorvane/type-mcp-api-agent-skill", } @@ -280,8 +289,8 @@ def test_version_conflict_is_reconciled_without_a_second_mutation(self) -> None: (200, {"slug": "api-to-typemcp", "status": "PUBLISHED"}), (200, []), (409, {"error": "version already exists"}), - (200, [{"version": "0.2.1"}]), - (200, {"slug": "api-to-typemcp", "status": "PUBLISHED", "latestVersion": "0.2.1"}), + (200, [{"version": "0.2.2"}]), + (200, {"slug": "api-to-typemcp", "status": "PUBLISHED", "latestVersion": "0.2.2"}), ] ) calls: list[tuple[str, str]] = [] @@ -294,7 +303,7 @@ def mocked_request( environment = { "SKILLS_HUB_AI_API_KEY": "test-key", - "SKILL_VERSION": "0.2.1", + "SKILL_VERSION": "0.2.2", "GITHUB_SHA": "test-sha", "GITHUB_REPOSITORY": "Theorvane/type-mcp-api-agent-skill", } diff --git a/.agents/scripts/validate_release_promotion.py b/.agents/scripts/validate_release_promotion.py index 77f2779..c7c800e 100644 --- a/.agents/scripts/validate_release_promotion.py +++ b/.agents/scripts/validate_release_promotion.py @@ -1,7 +1,18 @@ +"""Contract checks for safe `dev`/release-branch promotion into `main`.""" + from pathlib import Path workflow = Path(".github/workflows/release-promotion.yml").read_text(encoding="utf-8") + assert "branches: [main]" in workflow, "release gate must target main" assert "HEAD_REF: ${{ github.head_ref }}" in workflow, "release gate must read the PR head" -assert 'test "$HEAD_REF" = "dev"' in workflow, "only dev may promote to main" -print("verified release promotion accepts only dev into main") +assert 'test "$HEAD_REF" = "dev"' not in workflow, "release branches must not be rejected by a dev-only guard" +assert '[[ "$HEAD_REF" = "dev" || "$HEAD_REF" =~ ^release/[1-9][0-9]*-[a-z0-9]+(-[a-z0-9]+)*$ ]]' in workflow, ( + "promotion may accept only dev or a strict positive-issue release branch" +) +assert "actions/checkout@11d5960a326750d5838078e36cf38b85af677262" in workflow, "lineage guard needs a pinned checkout" +assert "fetch-depth: 0" in workflow, "lineage guard needs complete history" +assert 'git merge-base --is-ancestor origin/main HEAD' in workflow, "candidate must retain main ancestry" +assert 'git merge-base --is-ancestor origin/dev HEAD' in workflow, "candidate must retain dev ancestry" +assert 'test "$(git rev-list --parents -n 1 HEAD | awk' in workflow, "release branch must use a two-parent merge commit" +print("verified release promotion accepts dev or a strict lineage-preserving release branch into main") diff --git a/.agents/task-briefs/69-promote-api-to-typemcp-v0-2-1.md b/.agents/task-briefs/69-promote-api-to-typemcp-v0-2-1.md new file mode 100644 index 0000000..00549ca --- /dev/null +++ b/.agents/task-briefs/69-promote-api-to-typemcp-v0-2-1.md @@ -0,0 +1,64 @@ +# Task brief: 69 — promote api-to-typemcp v0.2.1 to main + +**Status:** review +**Issue:** https://github.com/Theorvane/type-mcp-api-agent-skill/issues/69 +**Branch:** `release/69-api-to-typemcp-v0-2-1` +**Owner:** Hermes Agent + +## Goal + +Promote the reviewed `api-to-typemcp` v0.2.1 release candidate from `dev` to release-only `main`, preserving current release ancestry and publishing the same immutable version to GitHub, ClawHub, and skills-hub.ai. + +## Source references + +- Release policy: `docs/guides/skill-release.md` +- Release checklist: `.agents/checklists/release-readiness.md` +- Security hardening: #65 +- ClawHub public-confirmation: #61 +- Release preparation: #67 + +## Scope + +### Included + +- Merge current `origin/dev` into a branch created from `origin/main` with a two-parent merge commit. +- Run release-candidate verification on that exact merge commit. +- Deliver a `main` PR with exact-head approval and merge it with a merge commit. +- Verify resulting v0.2.1 release identity and all public registries. + +### Excluded + +- New feature/security implementation changes. +- Manual tags, releases, or registry mutations outside the guarded main-push workflow. + +## Safety and contract notes + +- Source input: `origin/main` `699638c08dba58eb99c1cc54f6c0e193af5237ed` and `origin/dev` `2274a105e701a0a93a90a636b7273f7d21b9cfa8`. +- Secrets: only repository Actions secrets; no credential read, output, or persistence. +- Side effects: GitHub release and registry publication occur only after reviewed main merge. +- Compatibility: generated projects remain bound to published `@theorvane/type-mcp@0.2.0` while the skill artifact is v0.2.1. + +## Test-first / merge-candidate evidence + +| Stage | Command | Expected/observed result | +| --- | --- | --- | +| Candidate | `git show -s --format='%P' HEAD` | Exact release candidate has two parents: old main then reviewed dev. | +| Red | `python3 .agents/scripts/validate_release_promotion.py` | Failed at the obsolete dev-only guard, proving a lineage-preserving release branch could not be promoted. | +| Green | `python3 .agents/scripts/validate_release_promotion.py` | Passed after requiring strict `release/-` naming and main/dev ancestry. | +| Verification | full release and generated-project suite | Passed: release contract 15, docs 8, workspace 3, engine 98; generated project `npm ci`, build, Vitest, and audit (0 vulnerabilities). | +| Publication | guarded main-push workflow | Pending after reviewed merge. | + +## Verification + +- [x] Unit/integration tests +- [x] Generator or generated-project E2E test +- [x] Build/package validation +- [x] `git diff --check` +- [x] Documentation updated +- [ ] Independent specification review recorded +- [ ] Independent code-quality review recorded + +## Review notes + +- Main was not an ancestor of dev. The candidate merge commit is intentional lineage repair and must be merged into main using a merge commit, not squash or rebase. +- The former dev-only release guard rejected this candidate. It now accepts only `dev` or a strict `release/-` name and validates main/dev ancestry; arbitrary PR branches remain blocked. diff --git a/.agents/task-briefs/75-release-api-to-typemcp-v0-2-2.md b/.agents/task-briefs/75-release-api-to-typemcp-v0-2-2.md new file mode 100644 index 0000000..a313cb4 --- /dev/null +++ b/.agents/task-briefs/75-release-api-to-typemcp-v0-2-2.md @@ -0,0 +1,58 @@ +# Task brief: 75 — reconcile and publish api-to-typemcp v0.2.2 + +**Status:** in-progress +**Issue:** https://github.com/Theorvane/type-mcp-api-agent-skill/issues/75 +**Branch:** `chore/75-release-api-to-typemcp-v0-2-2` +**Owner:** sjungwon03 + +## Goal + +Publish the reviewed Hermes and Claude Code MCP-registration capability as public `api-to-typemcp` version `0.2.2` through the protected `dev` → `main` release path. + +## Source references + +- Release contract: `docs/guides/skill-release.md` +- Release checklist: `.agents/checklists/release-readiness.md` +- Reviewed feature PR: https://github.com/Theorvane/type-mcp-api-agent-skill/pull/74 + +## Scope + +### Included + +- Preserve the divergent `main` release automation history by merging it into the current `dev` lineage with a no-fast-forward reconciliation commit. +- Bump the skill frontmatter version from `0.2.1` to `0.2.2`. +- Update the release-contract expected output and verify the bundled skill, generated-project E2E, and public release artifacts. + +### Excluded + +- New MCP engine behavior beyond reviewed `dev` content. +- Manual GitHub release, tag, ClawHub, or skills-hub.ai mutation. + +## Safety and contract notes + +- Source input: release promotion starts only from the current reviewed `dev` branch and preserves both release histories. +- Secrets: registry credentials remain GitHub Actions secrets only; no credential is read, printed, or committed. +- Side effects: GitHub Release and registry publication occur only after a reviewed `dev` → `main` merge. +- Compatibility: this is a backward-compatible feature release; generated projects continue to use the reviewed public TypeMCP runtime contract. + +## Test-first evidence + +| Stage | Command | Expected/observed result | +| --- | --- | --- | +| Red | `python3 .agents/scripts/test_skill_release.py` | Failed as expected after the contract expected `0.2.2` while `SKILL.md` remained `0.2.1`. | +| Green | `python3 .agents/scripts/test_skill_release.py` | Passed: 16 tests after the frontmatter, public README release link, and release-publisher fixture version were updated to `0.2.2`. | +| Regression | repository verification baseline plus generated-project E2E | Passed: 140 engine tests, 8 documentation tests, 3 workspace tests, 16 release-contract tests, documentation validation, Python compile, and `git diff --check`. | + +## Verification + +- [ ] Unit/integration tests +- [ ] Generator or generated-project E2E test +- [ ] Build/package validation +- [ ] `git diff --check` +- [ ] Documentation updated +- [ ] Independent specification review recorded +- [ ] Independent code-quality review recorded + +## Review notes + +- Pending exact-head independent review after PR creation. diff --git a/.github/workflows/release-promotion.yml b/.github/workflows/release-promotion.yml index e16e69a..6993466 100644 --- a/.github/workflows/release-promotion.yml +++ b/.github/workflows/release-promotion.yml @@ -13,5 +13,19 @@ jobs: env: HEAD_REF: ${{ github.head_ref }} steps: - - name: Require the dev integration branch - run: test "$HEAD_REF" = "dev" + - name: Require dev or a strictly named release candidate + run: | + [[ "$HEAD_REF" = "dev" || "$HEAD_REF" =~ ^release/[1-9][0-9]*-[a-z0-9]+(-[a-z0-9]+)*$ ]] + + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 0 + + - name: Require release-lineage ancestry + run: | + git fetch --no-tags origin main dev + git merge-base --is-ancestor origin/main HEAD + git merge-base --is-ancestor origin/dev HEAD + if [[ "$HEAD_REF" = release/* ]]; then + test "$(git rev-list --parents -n 1 HEAD | awk '{print NF - 1}')" -eq 2 + fi diff --git a/README.md b/README.md index e62a690..12c8794 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Tasks 1–7 of embedded-engine delivery are implemented on `dev`: structured Ope - [ClawHub (`@sjungwon03/api-to-typemcp`)](https://clawhub.ai/sjungwon03/api-to-typemcp) - [skills-hub.ai (`api-to-typemcp`)](https://skills-hub.ai/skills/api-to-typemcp) -- [GitHub Release v0.2.1](https://github.com/Theorvane/type-mcp-api-agent-skill/releases/tag/v0.2.1) +- [GitHub Release v0.2.2](https://github.com/Theorvane/type-mcp-api-agent-skill/releases/tag/v0.2.2) ## Layout diff --git a/skills/api-to-typemcp/SKILL.md b/skills/api-to-typemcp/SKILL.md index 363d1ed..7522c20 100644 --- a/skills/api-to-typemcp/SKILL.md +++ b/skills/api-to-typemcp/SKILL.md @@ -1,7 +1,7 @@ --- name: api-to-typemcp description: Use when turning supplied API sources into a safe TypeMCP project. -version: 0.2.1 +version: 0.2.2 category: integration license: MIT metadata: