diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 3bc70bc..6d3b1f4 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,4 +1,5 @@ { + "description": "Vstack portable workflows and engineering principles for Claude Code.", "name": "vllnt-stack", "owner": { "name": "vllnt" diff --git a/CHANGELOG.md b/CHANGELOG.md index dd525c2..42f67ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +## 0.1.0-canary.1 + +- Publish the first canary for Claude Code verification; stable `0.1.0` follows once Claude Code, Codex, and Cursor are verified. Automatic source updates advance `-canary.N`. + +- Add a Claude marketplace description and Claude plugin homepage, repository, and keywords through the generator; document public Claude Code installation and record isolated local install evidence. + - Accept the user-requested, reviewed Skills update at `472c216f2f043f0add1f83540cc46ea53d20a7cd` as `0.1.0-dev.5`, including collaboration principles and bundled quality-validation guidance. Preserve automatic sensitive-change holds; this update uses a separate protected PR. - Record independently verified live source-push dispatch, automatic protected update/merge, post-merge no-op, and sensitive-change hold receipts; retain explicit host-runtime and scheduled-timer coverage limits. diff --git a/README.md b/README.md index 1d0fbb8..9be870e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,17 @@ One distribution repository for Vstack plugins for **Claude Code, Codex, and Cursor**. +## Install in Claude Code + +The Claude Code plugin is in canary (`0.1.0-canary.N`) while installation from GitHub is verified; Codex and Cursor follow. Stable `0.1.0` is released once all hosts pass. + +```text +/plugin marketplace add vllnt/stack +/plugin install vstack@vllnt-stack +``` + +Then start a new chat and invoke a namespaced skill such as `/vstack:plan-work`. Third-party marketplaces may not auto-update by default; enable auto-update for `vllnt-stack` in `/plugin`, or run `/plugin marketplace update vllnt-stack` to receive new versions. + [`vllnt/skills`](https://github.com/vllnt/skills) owns the portable workflows and principles. This repository pins an upstream commit and builds complete, committed host packages. Installed users need no Python, Git submodule, build step, or runtime source download. ## Ownership and layout @@ -9,7 +20,7 @@ One distribution repository for Vstack plugins for **Claude Code, Codex, and Cur | Path | Owner / purpose | | --- | --- | | `upstream.lock.json` | Trusted upstream repository and immutable commit | -| `VERSION` | Shared distribution version; initially an unreleased development version | +| `VERSION` | Shared distribution version | | `scripts/sync.py` | Standard-library generator and read-only drift check | | `plugins/claude/` | Generated Claude Code native plugin | | `plugins/codex/` | Generated portable Agent Plugin for Codex | @@ -28,9 +39,9 @@ Cursor receives an `alwaysApply` rule generated from the three canonical princip The plugin does not install frameworks, activate the optional Vstack stack profile, replace consumer permissions, or install hooks, MCP servers, or custom agents. Guidance is not a security sandbox. Users may need separate tools and credentials for individual workflows, supplied by their consumer projects. -## Local host testing (manual; not executed yet) +## Local host testing -These are development instructions based on the host documentation, not claims of tested runtime compatibility. Use a synthetic project and avoid overlapping standalone skill installations. The repository is initially private; GitHub-based distribution requires access. +These are development instructions based on the host documentation. Use a synthetic project and avoid overlapping standalone skill installations. Codex and Cursor steps have not been executed yet. ### Claude Code @@ -109,7 +120,7 @@ For a bad local update, disable the plugin and test a known-good distribution co `scripts/update_skills.py` compares complete Git objects and requires the lock to be an ancestor. Only non-executable Markdown under `workflows/` (excluding instruction files and hidden paths) and root README, changelog, and roadmap changes are ordinary. Every other path, including mandatory principles, license, code, configuration, and unknown types, stops with **MANUAL HOLD**, without publishing a candidate. A human reviews and updates sensitive changes through a separate PR. -Ordinary changes deterministically advance numeric `-dev.N`, or the patch of a stable version, regenerate all packages, and update the changelog. No pin change means no bump. The fixed `automation/update-skills` branch is published with an exact lease after isolated candidate tests. Existing candidates must exactly match reconstruction from their main-ancestor parent, pin, version, and changelog; human edits, forks, orphaned branches, draft PRs, `manual-review` labels, `MANUAL HOLD` in PR bodies, and closed unmerged automation PRs stop automation. These holds are never cleared by a newer upstream update. Closing an unmerged automation PR or deleting its branch alone intentionally keeps the stop signal. +Ordinary changes deterministically advance numeric `-dev.N` or `-canary.N`, or the patch of a stable version, regenerate all packages, and update the changelog. No pin change means no bump. The fixed `automation/update-skills` branch is published with an exact lease after isolated candidate tests. Existing candidates must exactly match reconstruction from their main-ancestor parent, pin, version, and changelog; human edits, forks, orphaned branches, draft PRs, `manual-review` labels, `MANUAL HOLD` in PR bodies, and closed unmerged automation PRs stop automation. These holds are never cleared by a newer upstream update. Closing an unmerged automation PR or deleting its branch alone intentionally keeps the stop signal. To retire a closed, unmerged automation PR after human review: @@ -137,7 +148,9 @@ Automatic repository synchronization was independently verified on 2026-09-21. A Live testing caught a generated-help false hold and post-push PR-head propagation timing; fixes were reviewed and merged through PRs #5 and #6, with 37 regression tests passing. The fresh PR #7 case required neither manual metadata repair nor manual merge. Daily fallback is configured; its timer was not waited for. Push delivery, manual resends, automatic merging, and manual no-op execution were observed. -Initialization provides generated packages and structural/regression tests. **Actual Claude Code, Codex, and Cursor installation, automatic skill selection, and startup/context-loss behavior remain unverified.** No public marketplace submission, release, host installation, or automatic Claude/Codex principle loading has been performed. +Claude Code 2.1.283 (2026-09-27): `claude plugin validate` passes for the catalog and plugin; a local-directory marketplace install into an isolated configuration installed `vstack@vllnt-stack` 0.1.0-canary.1 with all 22 skills, and a maintainer session with the local install listed every `vstack:*` skill. Installation from the public GitHub source, automatic skill selection, and the README acceptance checklist remain unverified. + +**Codex and Cursor installation, and startup/context-loss behavior in every host, remain unverified.** No public marketplace submission, release tag, or automatic Claude/Codex principle loading has been performed. Host format sources consulted during initialization: diff --git a/VERSION b/VERSION index 93d301f..5e06194 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0-dev.5 +0.1.0-canary.1 diff --git a/plugins/claude/.claude-plugin/plugin.json b/plugins/claude/.claude-plugin/plugin.json index ccf67b4..c2f50f6 100644 --- a/plugins/claude/.claude-plugin/plugin.json +++ b/plugins/claude/.claude-plugin/plugin.json @@ -3,7 +3,15 @@ "name": "vllnt" }, "description": "Vstack portable workflows and engineering principles.", + "homepage": "https://github.com/vllnt/stack", + "keywords": [ + "workflows", + "engineering", + "principles", + "skills" + ], "license": "MIT", "name": "vstack", - "version": "0.1.0-dev.5" + "repository": "https://github.com/vllnt/stack", + "version": "0.1.0-canary.1" } diff --git a/plugins/claude/SOURCE.json b/plugins/claude/SOURCE.json index f216ea8..ff50159 100644 --- a/plugins/claude/SOURCE.json +++ b/plugins/claude/SOURCE.json @@ -1,6 +1,6 @@ { "files": { - ".claude-plugin/plugin.json": "6451f853284b73851d66d22b81907f8c99153221c0420b93ab549d83df4864bc", + ".claude-plugin/plugin.json": "029df1dbf5659bdedbc95b13a5c14c41957984dfee6b77b86e6b54cb742a2de1", "LICENSE": "24ba061a4603738ddd5105ef4abc198eeded5ac7efce23b360561816f4dd6559", "skills/build-landing-page/README.md": "46a280eadac8962180cf3f0c42eb0e845a3b0156a644569182d932881e1d51f8", "skills/build-landing-page/SKILL.md": "b08e4963d6149f3647b28a0d9dccdc8793812abab95a1d081d93699e142de0f0", diff --git a/plugins/codex/SOURCE.json b/plugins/codex/SOURCE.json index 2eb00aa..7af8334 100644 --- a/plugins/codex/SOURCE.json +++ b/plugins/codex/SOURCE.json @@ -1,7 +1,7 @@ { "files": { "LICENSE": "24ba061a4603738ddd5105ef4abc198eeded5ac7efce23b360561816f4dd6559", - "plugin.json": "dbda3fe5dbdb27421df463703f999096a733ab47bf73109df4fda62bd4902d59", + "plugin.json": "01f8bb9782de6474631228eaff7938845536ec3e87b5d8ec8fe3008dea678448", "skills/build-landing-page/README.md": "46a280eadac8962180cf3f0c42eb0e845a3b0156a644569182d932881e1d51f8", "skills/build-landing-page/SKILL.md": "b08e4963d6149f3647b28a0d9dccdc8793812abab95a1d081d93699e142de0f0", "skills/build-landing-page/references/copywriting-formulas.md": "6d19f6166e1badcfdda0af4f8b303160e94ee6e6149321157e12849654cc013e", diff --git a/plugins/codex/plugin.json b/plugins/codex/plugin.json index b039975..346a29f 100644 --- a/plugins/codex/plugin.json +++ b/plugins/codex/plugin.json @@ -6,5 +6,5 @@ "description": "Vstack portable workflows and engineering principles.", "license": "MIT", "name": "vstack", - "version": "0.1.0-dev.5" + "version": "0.1.0-canary.1" } diff --git a/plugins/cursor/.cursor-plugin/plugin.json b/plugins/cursor/.cursor-plugin/plugin.json index ccf67b4..4fd93b1 100644 --- a/plugins/cursor/.cursor-plugin/plugin.json +++ b/plugins/cursor/.cursor-plugin/plugin.json @@ -5,5 +5,5 @@ "description": "Vstack portable workflows and engineering principles.", "license": "MIT", "name": "vstack", - "version": "0.1.0-dev.5" + "version": "0.1.0-canary.1" } diff --git a/plugins/cursor/SOURCE.json b/plugins/cursor/SOURCE.json index f4c2493..9dc4196 100644 --- a/plugins/cursor/SOURCE.json +++ b/plugins/cursor/SOURCE.json @@ -1,6 +1,6 @@ { "files": { - ".cursor-plugin/plugin.json": "6451f853284b73851d66d22b81907f8c99153221c0420b93ab549d83df4864bc", + ".cursor-plugin/plugin.json": "04856abc090a6a763556b93285974a550c3018ade4002f656c91b0e5623f1068", "LICENSE": "24ba061a4603738ddd5105ef4abc198eeded5ac7efce23b360561816f4dd6559", "rules/vstack-principles.mdc": "5234bc0506e0a328d6163cd83785d2f37dd1fc23ef84287d71e03b92459e064a", "skills/build-landing-page/README.md": "46a280eadac8962180cf3f0c42eb0e845a3b0156a644569182d932881e1d51f8", diff --git a/scripts/sync.py b/scripts/sync.py index e0c097a..8996b3a 100644 --- a/scripts/sync.py +++ b/scripts/sync.py @@ -20,6 +20,7 @@ ROOT = Path(__file__).resolve().parents[1] UPSTREAM = "https://github.com/vllnt/skills.git" +REPOSITORY = "https://github.com/vllnt/stack" HOSTS = ("claude", "codex", "cursor") PRINCIPLES = tuple(f"vllnt-{name}-principles" for name in ("thinking", "orchestration", "collaboration")) MAX_ARCHIVE = 32 * 1024 * 1024 @@ -221,6 +222,9 @@ def build(files: dict[str, bytes], lock: dict, version: str) -> dict[str, bytes] manifest = {"name": "vstack", "version": version, "description": "Vstack portable workflows and engineering principles.", "author": {"name": "vllnt"}, "license": "MIT"} + if host == "claude": + manifest.update({"homepage": REPOSITORY, "repository": REPOSITORY, + "keywords": ["workflows", "engineering", "principles", "skills"]}) if host == "codex": manifest["$schema"] = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json" manifest_path = {"claude": ".claude-plugin/plugin.json", "codex": "plugin.json", @@ -232,9 +236,11 @@ def build(files: dict[str, bytes], lock: dict, version: str) -> dict[str, bytes] path: hashlib.sha256(content).hexdigest() for path, content in sorted(package.items())}}) output.update({f"plugins/{host}/{path}": content for path, content in package.items()}) for host in ("claude", "cursor"): - output[f".{host}-plugin/marketplace.json"] = encoded({ - "name": "vllnt-stack", "owner": {"name": "vllnt"}, - "plugins": [{"name": "vstack", "source": f"./plugins/{host}"}]}) + catalog = {"name": "vllnt-stack", "owner": {"name": "vllnt"}, + "plugins": [{"name": "vstack", "source": f"./plugins/{host}"}]} + if host == "claude": + catalog["description"] = "Vstack portable workflows and engineering principles for Claude Code." + output[f".{host}-plugin/marketplace.json"] = encoded(catalog) output[".agents/plugins/marketplace.json"] = encoded({ "name": "vllnt-stack", "interface": {"displayName": "Vstack"}, "plugins": [{"name": "vstack", "source": {"source": "local", "path": "./plugins/codex"}, diff --git a/scripts/update_skills.py b/scripts/update_skills.py index 65a7244..837a671 100644 --- a/scripts/update_skills.py +++ b/scripts/update_skills.py @@ -70,11 +70,13 @@ def base_head(): def bump(version): - match = re.fullmatch(r"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-dev\.(0|[1-9]\d*))?", version) + match = re.fullmatch(r"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(dev|canary)\.(0|[1-9]\d*))?", version) if not match: raise Stop("VERSION needs human review") - major, minor, patch, dev = match.groups() - return f"{major}.{minor}.{patch}-dev.{int(dev)+1}" if dev is not None else f"{major}.{minor}.{int(patch)+1}" + major, minor, patch, channel, number = match.groups() + if channel is not None: + return f"{major}.{minor}.{patch}-{channel}.{int(number)+1}" + return f"{major}.{minor}.{int(patch)+1}" def ordinary(path): diff --git a/tests/test_sync.py b/tests/test_sync.py index a45a39b..548b641 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -59,6 +59,14 @@ def test_deterministic_standalone_packages_and_provenance(self): self.assertEqual(claude["plugins"][0]["source"], "./plugins/claude") self.assertEqual(codex["plugins"][0]["source"]["path"], "./plugins/codex") self.assertEqual(cursor["plugins"][0]["source"], "./plugins/cursor") + self.assertIn("description", claude) + self.assertNotIn("description", cursor) + manifests = {host: json.loads(first[path]) for host, path in ( + ("claude", "plugins/claude/.claude-plugin/plugin.json"), ("codex", "plugins/codex/plugin.json"), + ("cursor", "plugins/cursor/.cursor-plugin/plugin.json"))} + self.assertEqual(manifests["claude"]["repository"], "https://github.com/vllnt/stack") + self.assertNotIn("repository", manifests["codex"]) + self.assertNotIn("repository", manifests["cursor"]) def test_added_and_removed_workflows(self): files = source() diff --git a/tests/test_update_skills.py b/tests/test_update_skills.py index a4296cb..12e0cbc 100644 --- a/tests/test_update_skills.py +++ b/tests/test_update_skills.py @@ -117,7 +117,8 @@ class BoundaryTests(unittest.TestCase): def test_versions_and_sensitive_paths(self): self.assertEqual(u.bump("1.2.3"), "1.2.4") self.assertEqual(u.bump("0.1.0-dev.9"), "0.1.0-dev.10") - for version in ("1.2.3-rc.1", "01.2.3", "v1.2.3", "1.2"): + self.assertEqual(u.bump("0.1.0-canary.1"), "0.1.0-canary.2") + for version in ("1.2.3-rc.1", "0.1.0-canary", "01.2.3", "v1.2.3", "1.2"): with self.assertRaises(u.Stop): u.bump(version) for path in ("mandatory/x/SKILL.md", "LICENSE", "AGENTS.md", "workflows/x/AGENTS.md",