diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index aa251ac..371410e 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ { "name": "commitproof", "source": "./", - "description": "Know which commits AI wrote, and prove the record wasn't rewritten. Start with /ai-history (no setup), then /receipt-init for signed, hash-chained receipts verified offline." + "description": "Works in Claude Code. Know which commits AI wrote, and prove the record wasn't rewritten. Start with /ai-history (no setup), then /receipt-init for signed, hash-chained receipts verified offline." } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index d2c8dad..beee20f 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "commitproof", "displayName": "CommitProof: AI Audit Trail", "version": "1.3.1", - "description": "An audit trail for AI-written code. /ai-history answers 'how much of this repo did Claude help write?' in seconds from git history, no setup. /receipt-init then gives every commit a tamper-evident receipt (AI-assisted or not, model, Claude Code version, file hashes): hash-chained, signed, verified offline. /receipt-report turns the chain into change-control evidence for SOC 2, HIPAA or PCI audits.", + "description": "Works in Claude Code. An audit trail for AI-written code. /ai-history answers 'how much of this repo did Claude help write?' in seconds from git history, no setup. /receipt-init then gives every commit a tamper-evident receipt (AI-assisted or not, model, Claude Code version, file hashes): hash-chained, signed, verified offline. /receipt-report turns the chain into a change-control report you can give SOC 2, HIPAA or PCI auditors as supporting evidence.", "author": { "name": "Rehan Masood", "email": "rmasood524@gmail.com", diff --git a/README.md b/README.md index d353d79..4b194f2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +Works in Claude Code. To install, run these two commands in a Claude Code session: + +``` +/plugin marketplace add Rmasood1122/commitproof +/plugin install commitproof@commitproof +``` + # CommitProof [![CI](https://github.com/Rmasood1122/commitproof/actions/workflows/ci.yml/badge.svg)](https://github.com/Rmasood1122/commitproof/actions/workflows/ci.yml) @@ -11,8 +18,9 @@ Teams using Claude Code get asked two questions: from the git history you already have. No setup, no key, writes nothing. 2. **"Can you prove that record hasn't been edited since?"** `/receipt-init` gives every commit from then on a tamper-evident receipt: hash-chained, - signed, verifiable offline. `/receipt-report` turns the chain into the - change-control evidence a SOC 2, HIPAA or PCI auditor asks for. + signed, verifiable offline. `/receipt-report` turns the chain into a + change-control report you can give a SOC 2, HIPAA or PCI auditor as + supporting evidence. ## Try it in 30 seconds diff --git a/tests/test_install_routing.py b/tests/test_install_routing.py new file mode 100644 index 0000000..13505c1 --- /dev/null +++ b/tests/test_install_routing.py @@ -0,0 +1,52 @@ +"""The README and the directory listing send installers to Claude Code. + +This plugin runs in Claude Code, so the first thing a reader sees says +"Works in Claude Code" and gives the two commands that install it there, and every +install command in the README must point at this repo's own marketplace by the +names the manifests declare, so a copied command installs this plugin. +""" +import json +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +REPO_SLUG = "Rmasood1122/commitproof" # the public repo users install from +PREFIX = "Works in Claude Code" + +PLUGIN = json.loads((ROOT / ".claude-plugin" / "plugin.json").read_text(encoding="utf-8")) +MARKET = json.loads((ROOT / ".claude-plugin" / "marketplace.json").read_text(encoding="utf-8")) +README = (ROOT / "README.md").read_text(encoding="utf-8") +INSTALL = [f"/plugin marketplace add {REPO_SLUG}", + f"/plugin install {PLUGIN['name']}@{MARKET['name']}"] + + +def _block_after_first_line(lines): + """Lines of the fenced code block that directly follows line 1 (blank lines allowed between).""" + i = 1 + while i < len(lines) and not lines[i].strip(): + i += 1 + assert i < len(lines) and lines[i].startswith("```"), "line 1 must be followed by the install code block" + end = next((j for j in range(i + 1, len(lines)) if lines[j].startswith("```")), None) + assert end is not None, "the install block after line 1 is never closed" + return [l.strip() for l in lines[i + 1:end] if l.strip()] + + +def test_readme_opens_with_claude_code_and_its_install_commands(): + lines = README.splitlines() + assert lines[0].startswith(PREFIX), lines[0] + assert _block_after_first_line(lines) == INSTALL + + +def test_listing_descriptions_lead_with_claude_code(): + assert PLUGIN["description"].startswith(PREFIX) + for entry in MARKET["plugins"]: + assert entry["description"].startswith(PREFIX), entry["name"] + + +def test_every_readme_install_command_points_at_this_repo(): + # whole tokens, so a URL source or an install without @marketplace can't slip past + adds = re.findall(r"marketplace add ([^\s`]+)", README) + installs = re.findall(r"plugin install ([^\s`]+)", README) + assert adds and installs + assert set(adds) == {REPO_SLUG}, adds + assert set(installs) == {f"{PLUGIN['name']}@{MARKET['name']}"}, installs diff --git a/tests/test_listing_readiness.py b/tests/test_listing_readiness.py new file mode 100644 index 0000000..175d9ef --- /dev/null +++ b/tests/test_listing_readiness.py @@ -0,0 +1,43 @@ +"""Directory-listing readiness: the manifest checks the plugin directory applies. + +Mirrors the manifest part of eval-conductor's readiness battery, so a listing edit +(description, keywords, names) can't silently break what the directory accepts. +""" +import json +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +PLUGIN = json.loads((ROOT / ".claude-plugin" / "plugin.json").read_text(encoding="utf-8")) +MARKET = json.loads((ROOT / ".claude-plugin" / "marketplace.json").read_text(encoding="utf-8")) + + +def test_name_is_kebab_case(): + assert re.fullmatch(r"[a-z0-9]+(-[a-z0-9]+)*", PLUGIN["name"]), PLUGIN["name"] + + +def test_description_length_fits_the_directory(): + assert 30 <= len(PLUGIN["description"]) <= 2000, len(PLUGIN["description"]) + for entry in MARKET["plugins"]: + assert 30 <= len(entry.get("description", "")) <= 2000, entry["name"] + + +def test_keywords_are_a_nonempty_list_of_strings(): + kw = PLUGIN.get("keywords") + assert isinstance(kw, list) and kw and all(isinstance(k, str) and k for k in kw), kw + + +def test_marketplace_lists_exactly_this_plugin(): + assert [p["name"] for p in MARKET["plugins"]] == [PLUGIN["name"]] + + +def test_compliance_wording_offers_evidence_and_claims_no_requirement(): + """No SOC 2, HIPAA or PCI control requires AI-code provenance, so the README and + listing may offer the report as supporting evidence, never as what an auditor asks for.""" + readme = (ROOT / "README.md").read_text(encoding="utf-8") + for text in (readme, PLUGIN["description"]): + lowered = text.lower() + for overclaim in ("auditor asks for", "auditors ask for", "required by soc", "soc 2 requires"): + assert overclaim not in lowered, overclaim + if "soc 2" in lowered: + assert "supporting evidence" in lowered, "SOC 2 mentioned without the supporting-evidence framing"