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
1 change: 1 addition & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"description": "Vstack portable workflows and engineering principles for Claude Code.",
"name": "vllnt-stack",
"owner": {
"name": "vllnt"
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@

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

| 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 |
Expand All @@ -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

Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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:

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.0-dev.5
0.1.0-canary.1
10 changes: 9 additions & 1 deletion plugins/claude/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
2 changes: 1 addition & 1 deletion plugins/claude/SOURCE.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex/SOURCE.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
2 changes: 1 addition & 1 deletion plugins/cursor/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
2 changes: 1 addition & 1 deletion plugins/cursor/SOURCE.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
12 changes: 9 additions & 3 deletions scripts/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand All @@ -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"},
Expand Down
8 changes: 5 additions & 3 deletions scripts/update_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
8 changes: 8 additions & 0 deletions tests/test_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion tests/test_update_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading