diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 37d49a7..dc11cac 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-marketplace.json", "name": "skill-feedback-engine", - "version": "0.1.0", + "version": "0.1.1", "description": "Openly Useful local-first, review-gated feedback for Agent Skills.", "owner": { "name": "Openly Useful", @@ -13,7 +13,7 @@ "name": "skill-feedback-engine", "source": "./", "description": "Capture reusable corrections and prepare sanitized, review-gated Agent Skill proposals.", - "version": "0.1.0", + "version": "0.1.1", "author": { "name": "Openly Useful", "email": "hello@openlyuseful.org", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 6ac73a5..28c83f4 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "skill-feedback-engine", - "version": "0.1.0", + "version": "0.1.1", "description": "Local-first, review-gated feedback for improving Agent Skills without exporting raw evidence.", "author": { "name": "Openly Useful", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index bc1bc04..102434b 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "skill-feedback-engine", - "version": "0.1.0", + "version": "0.1.1", "description": "Local-first, review-gated feedback for improving Agent Skills without exporting raw evidence.", "author": { "name": "Openly Useful", diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 899aa68..968e6d1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -33,7 +33,7 @@ body: label: Environment description: Include the release or commit, Python version, operating system, and installation method. placeholder: | - Skill Feedback Engine: v0.1.0 + Skill Feedback Engine: v0.1.1 Python: 3.12 OS: macOS 15 Install: pip editable checkout diff --git a/.project-status/manifest.json b/.project-status/manifest.json index f44e4bd..8231eb9 100644 --- a/.project-status/manifest.json +++ b/.project-status/manifest.json @@ -4,7 +4,7 @@ "initiative": { "id": "skill-feedback-engine", "name": "Skill Feedback Engine", - "release": "v0.1.0", + "release": "v0.1.1", "state": "complete" }, "source": { diff --git a/README.md b/README.md index fa7ab88..c72c065 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ It does not silently rewrite skills, publish conversation history, or merge pull ## Status -This repository contains the functional [`v0.1.0`](https://github.com/Openly-Useful/skill-feedback-engine/releases/tag/v0.1.0) core: +This repository contains the functional [`v0.1.1`](https://github.com/Openly-Useful/skill-feedback-engine/releases/tag/v0.1.1) core: - Standard-library Python CLI with no runtime dependencies. - Concurrent-safe SQLite state under `~/.local/share/skill-feedback-engine` by default. diff --git a/publisher/publisher.json b/publisher/publisher.json index 86b5c3b..d911ad6 100644 --- a/publisher/publisher.json +++ b/publisher/publisher.json @@ -36,7 +36,7 @@ "component": { "name": "skill-feedback-engine", "displayName": "Skill Feedback Engine", - "version": "0.1.0", + "version": "0.1.1", "repository": "https://github.com/Openly-Useful/skill-feedback-engine", "license": "MIT", "skillsPath": "./skills/", diff --git a/pyproject.toml b/pyproject.toml index 9e5cefd..1672ed1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "skill-feedback-engine" -version = "0.1.0" +version = "0.1.1" description = "A local-first, provider-neutral feedback loop for agent skills." readme = "README.md" requires-python = ">=3.9" diff --git a/scripts/validate_registration.py b/scripts/validate_registration.py index 3dace75..56f2b1a 100644 --- a/scripts/validate_registration.py +++ b/scripts/validate_registration.py @@ -11,6 +11,8 @@ ROOT = Path(__file__).resolve().parents[1] SEMVER = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$") +PYPROJECT_VERSION = re.compile(r'^version\s*=\s*"([^"]+)"\s*$', re.MULTILINE) +PYTHON_VERSION = re.compile(r'^__version__\s*=\s*"([^"]+)"\s*$', re.MULTILINE) def read_json(path: Path, errors: List[str]) -> Dict[str, Any]: @@ -79,6 +81,22 @@ def validate_registration(root: Path = ROOT) -> List[str]: if component.get("mcp") is not False: errors.append("Skill Feedback Engine must explicitly remain MCP-free") + component_version = str(component.get("version", "")) + version_sources = [ + ("package", root / "pyproject.toml", PYPROJECT_VERSION), + ("runtime", root / "src" / "skill_feedback_engine" / "__init__.py", PYTHON_VERSION), + ] + for label, path, pattern in version_sources: + try: + match = pattern.search(path.read_text(encoding="utf-8")) + except OSError: + match = None + if match is None or match.group(1) != component_version: + errors.append(f"{label} version must match publisher component version") + project_status = read_json(root / ".project-status" / "manifest.json", errors) + if project_status.get("initiative", {}).get("release") != f"v{component_version}": + errors.append("project status release must match publisher component version") + skill_artifacts = sorted(path.relative_to(root).as_posix() for path in (root / "skills").rglob("SKILL*.md")) if skill_artifacts != ["skills/skill-feedback-engine/SKILL.md"]: errors.append("duplicate or unexpected SKILL artifacts: " + ", ".join(skill_artifacts)) diff --git a/src/skill_feedback_engine/__init__.py b/src/skill_feedback_engine/__init__.py index 9d8d82c..a9ba948 100644 --- a/src/skill_feedback_engine/__init__.py +++ b/src/skill_feedback_engine/__init__.py @@ -1,3 +1,3 @@ """Skill Feedback Engine.""" -__version__ = "0.1.0" +__version__ = "0.1.1" diff --git a/tests/test_registration.py b/tests/test_registration.py index 329c9f5..7705983 100644 --- a/tests/test_registration.py +++ b/tests/test_registration.py @@ -7,25 +7,36 @@ from skill_feedback_engine.validation import validate_skill +REGISTRATION_FILES = [ + "publisher/publisher.json", + ".codex-plugin/plugin.json", + ".claude-plugin/plugin.json", + ".claude-plugin/marketplace.json", + ".agents/plugins/marketplace.json", + "skills/skill-feedback-engine/SKILL.md", + "pyproject.toml", + "src/skill_feedback_engine/__init__.py", + ".project-status/manifest.json", +] + + +def copy_registration_fixture(directory: str) -> Path: + fixture = Path(directory) + for relative in REGISTRATION_FILES: + source = ROOT / relative + target = fixture / relative + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(source.read_bytes()) + return fixture + + class RegistrationTests(unittest.TestCase): def test_repository_registration_is_current_and_mcp_free(self): self.assertEqual(validate_registration(), []) def test_validator_rejects_duplicate_skill_artifacts(self): with tempfile.TemporaryDirectory() as directory: - fixture = Path(directory) - for relative in [ - "publisher/publisher.json", - ".codex-plugin/plugin.json", - ".claude-plugin/plugin.json", - ".claude-plugin/marketplace.json", - ".agents/plugins/marketplace.json", - "skills/skill-feedback-engine/SKILL.md", - ]: - source = ROOT / relative - target = fixture / relative - target.parent.mkdir(parents=True, exist_ok=True) - target.write_bytes(source.read_bytes()) + fixture = copy_registration_fixture(directory) (fixture / "skills" / "skill-feedback-engine" / "SKILL 2.md").write_text( "---\nname: skill-feedback-engine\ndescription: duplicate\n---\n", encoding="utf-8", @@ -47,19 +58,7 @@ def test_portable_skill_validator_rejects_duplicate_sibling(self): def test_validator_rejects_premature_llc_activation(self): with tempfile.TemporaryDirectory() as directory: - fixture = Path(directory) - for relative in [ - "publisher/publisher.json", - ".codex-plugin/plugin.json", - ".claude-plugin/plugin.json", - ".claude-plugin/marketplace.json", - ".agents/plugins/marketplace.json", - "skills/skill-feedback-engine/SKILL.md", - ]: - source = ROOT / relative - target = fixture / relative - target.parent.mkdir(parents=True, exist_ok=True) - target.write_bytes(source.read_bytes()) + fixture = copy_registration_fixture(directory) publisher_path = fixture / "publisher" / "publisher.json" publisher = json.loads(publisher_path.read_text(encoding="utf-8")) publisher["plannedLegalEntity"]["status"] = "active" @@ -69,19 +68,7 @@ def test_validator_rejects_premature_llc_activation(self): def test_validator_rejects_non_founder_current_operator(self): with tempfile.TemporaryDirectory() as directory: - fixture = Path(directory) - for relative in [ - "publisher/publisher.json", - ".codex-plugin/plugin.json", - ".claude-plugin/plugin.json", - ".claude-plugin/marketplace.json", - ".agents/plugins/marketplace.json", - "skills/skill-feedback-engine/SKILL.md", - ]: - source = ROOT / relative - target = fixture / relative - target.parent.mkdir(parents=True, exist_ok=True) - target.write_bytes(source.read_bytes()) + fixture = copy_registration_fixture(directory) publisher_path = fixture / "publisher" / "publisher.json" publisher = json.loads(publisher_path.read_text(encoding="utf-8")) publisher["currentOperator"]["status"] = "llc-operated" @@ -91,19 +78,7 @@ def test_validator_rejects_non_founder_current_operator(self): def test_validator_rejects_publication_without_founder_authority(self): with tempfile.TemporaryDirectory() as directory: - fixture = Path(directory) - for relative in [ - "publisher/publisher.json", - ".codex-plugin/plugin.json", - ".claude-plugin/plugin.json", - ".claude-plugin/marketplace.json", - ".agents/plugins/marketplace.json", - "skills/skill-feedback-engine/SKILL.md", - ]: - source = ROOT / relative - target = fixture / relative - target.parent.mkdir(parents=True, exist_ok=True) - target.write_bytes(source.read_bytes()) + fixture = copy_registration_fixture(directory) publisher_path = fixture / "publisher" / "publisher.json" publisher = json.loads(publisher_path.read_text(encoding="utf-8")) publisher["publication"]["authorizationBasis"] = "planned-entity" @@ -111,6 +86,24 @@ def test_validator_rejects_publication_without_founder_authority(self): errors = validate_registration(fixture) self.assertTrue(any("founder-owner-direct" in error for error in errors)) + def test_validator_rejects_runtime_version_drift(self): + with tempfile.TemporaryDirectory() as directory: + fixture = copy_registration_fixture(directory) + runtime_path = fixture / "src" / "skill_feedback_engine" / "__init__.py" + runtime_path.write_text('__version__ = "9.9.9"\n', encoding="utf-8") + errors = validate_registration(fixture) + self.assertTrue(any("runtime version" in error for error in errors)) + + def test_validator_rejects_project_release_drift(self): + with tempfile.TemporaryDirectory() as directory: + fixture = copy_registration_fixture(directory) + status_path = fixture / ".project-status" / "manifest.json" + status = json.loads(status_path.read_text(encoding="utf-8")) + status["initiative"]["release"] = "v9.9.9" + status_path.write_text(json.dumps(status), encoding="utf-8") + errors = validate_registration(fixture) + self.assertTrue(any("project status release" in error for error in errors)) + if __name__ == "__main__": unittest.main()