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
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .project-status/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"initiative": {
"id": "skill-feedback-engine",
"name": "Skill Feedback Engine",
"release": "v0.1.0",
"release": "v0.1.1",
"state": "complete"
},
"source": {
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion publisher/publisher.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
18 changes: 18 additions & 0 deletions scripts/validate_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion src/skill_feedback_engine/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Skill Feedback Engine."""

__version__ = "0.1.0"
__version__ = "0.1.1"
97 changes: 45 additions & 52 deletions tests/test_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -91,26 +78,32 @@ 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"
publisher_path.write_text(json.dumps(publisher), encoding="utf-8")
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()
Loading