--- name: vc-publish description: Use when publishing harness improvements to the remote kit repo. Diffs managed files, shows what changed, bumps version, and pushes. Counterpart to vc-update (pull). trigger_keywords: publish kit, push harness, release kit, update remote layer: contract metadata: author: vibecode version: "3.0.0" --- # vc-publish > **Output style:** Follow `process/development-protocols/communication-standards.md` — answer-first, plain language, no unexplained jargon, TL;DR on long responses. Push harness improvements from the current development repo to the remote kit repository (`vibecode-pro-max-kit`). This is the **maintainer** counterpart to `vc-update`. - `vc-update` = **user** pulls latest harness INTO their project FROM the remote - `vc-publish` = **maintainer** pushes improvements FROM the development repo TO the remote kit repo ## Prerequisites - Local checkout of the kit repo (`git clone git@github.com:withkynam/vibecode-pro-max-kit.git`) - `.vc-publish-config` file in the current repo root (see Configuration below) - Git push access to the remote kit repo ## Configuration Create `.vc-publish-config` in the repo root: ```json {"kitRepoPath": "/path/to/vibecode-pro-max-kit"} ``` If this file is missing, ask the user for the kit repo checkout path and offer to create it. ## Workflow ### Step 1: Load Configuration 1. Read `.vc-publish-config` from the current repo root. 2. If missing, ask the user for the kit repo local checkout path. 3. Verify the path exists and contains `vc-manifest.json`. 4. Verify the kit repo worktree is clean (`git -C status --porcelain`). If dirty, warn and ask whether to proceed or abort. ### Step 2: Read Manifest 5. Read `vc-manifest.json` from the kit repo checkout. 6. Extract the current `version`. 7. Before computing a version bump, check if the current kit version already matches the intended target version (e.g. `3.0.0`). If the kit `version` already equals the target: **skip the bump step entirely** and proceed directly to Step 3 with a `tag-as-is` note — do not increment the version. Record in the publish summary that the version was unchanged. **Catalog-regen (pre-publish):** Before resolving files in Steps 3–4, regenerate the skills catalog in the dev repo: ```bash node .claude/skills/vc-audit-context/scripts/generate-skills-catalog.mjs --write ``` This ensures `process/context/generated-skills-catalog.json` is current before it is copied into the kit repo. ### Step 3: Resolve Kit File Set 7. Run the resolver against the **kit repo** to get the kit file list: ```bash node /resolve-manifest.mjs --root --json ``` Extract `files` (kit managed files) and `kitOnly` (kit-exclusive files). **Note:** `resolve-manifest.mjs` reads `vc-manifest.json` from its `--root` directory and also scans files from that same root. `vc-manifest.json` is NOT installed into dev/user projects by `install.sh`, so the resolver must always be pointed at the kit repo checkout (which does have it). There is no separate dev-repo resolver call — the dev-side file comparison happens inside `compute-sync-plan.mjs` in Step 4. ### Step 4: Compute Diff 9. **Computation via `compute-sync-plan.mjs`:** Use the shared computation core to produce the diff between the dev repo's managed files and the kit repo's current managed files. > **Direction note:** `compute-sync-plan.mjs` loads `vc-manifest.json` from `--kit-root` and runs the resolver with `--root `. Since `vc-manifest.json` lives in the kit repo (not the dev repo), `--kit-root` must always be the kit repo checkout. `--root` is the dev repo (the project being compared). This is the same direction as a normal install — vc-publish uses it to see what a fresh install FROM dev INTO the kit would change. ```bash # --root = dev repo (the "project" being compared against the kit source) # --kit-root = kit repo (where vc-manifest.json lives; source of truth for file lists) # --resolver overrides the resolver path because compute-sync-plan # would otherwise look for resolve-manifest.mjs inside --kit-root, # which IS the kit repo here, so --resolver is optional but explicit for clarity. node /compute-sync-plan.mjs \ --root \ --kit-root \ --resolver /resolve-manifest.mjs \ --json ``` Parse the JSON output: `{ toAdd, toModify, toDelete, toPreserve, staleWarnings }`. - `toAdd` — files to copy from dev to kit (present in dev, not yet in kit). - `toModify` — files to overwrite in kit (tracked in both, content differs). - `toDelete` — files to remove from kit (no longer in dev managed set). - `toPreserve` — files to leave untouched (merge/copyIfMissing survivors, unchanged files). - `staleWarnings` — paths that failed the namespace guard — print to user; do NOT delete. The ownedPaths for the publish direction are the dev repo's resolved `ownedPaths`. CLAUDE.md and AGENTS.md are always in the `merge` category — they require special stripping regardless of diff status (see Step 7). ### Step 5: Print Diff Summary 10. Print a summary table: ``` vc-publish diff: current repo -> kit repo (v2.1.0) ================================================ FILES: [modified] .claude/agents/vc-execute-agent.md (+8 -3) [modified] .claude/hooks/lib/scout-checker.cjs (+2 -1) [new] .claude/skills/vc-new-skill/SKILL.md [merge] CLAUDE.md (needs content review) [merge] AGENTS.md (needs content review) [unchanged] .claude/settings.json ... (350 more unchanged) Total changes: 4 files modified, 1 new, 0 removed ``` ### Step 6: STOP -- Confirm Publish 11. **STOP** and ask the user: - Confirm they want to publish these changes. - Specify version bump type: **patch**, **minor**, or **major**. - Optionally provide **release notes** (1–3 sentences for the GitHub Release body). Leave blank to auto-generate from the diff summary (e.g. "4 modified, 1 new, 0 removed."). - Or abort. Version bump semantics: - **Patch** (2.1.0 -> 2.1.1): hook fixes, skill doc updates, minor agent prompt tweaks - **Minor** (2.1.0 -> 2.2.0): new skills, new agents, new development protocols - **Major** (2.1.0 -> 3.0.0): CLAUDE.md structure changes, manifest schema changes, breaking workflow changes ### Step 7: Apply Changes 12. On confirm: - Copy all **modified** and **new** managed files from current repo to kit repo checkout. - For each **removed** file: delete it from the kit repo checkout. - **CLAUDE.md and AGENTS.md stripping**: Do NOT copy the current repo's project-specific versions directly. Instead: 1. Read the current repo's CLAUDE.md/AGENTS.md. 2. Read the kit repo's existing harness-only version as base. 3. Apply only methodology/structural changes from the dev repo to the kit's harness-only version. 4. Strip all project-specific content: - Technology stack details (frameworks, databases, versions) - Feature list / "Current features" entries - Project-specific context groups - Hardcoded package manager (replace with generic) - MCP server instructions (project-specific config) - Project-specific routing rules - Absolute paths (`/Users/...`) - Product name references (the project's product name and repo/directory name) 5. Verify the result is harness-only methodology with no project leaks. - **Manifest reconciliation** (`vc-manifest.json`) — the manifest is NOT a normally-copied managed file (it is dev-only on one side and kit-resolved on the other), so its fields do NOT auto-sync. Handle it explicitly: 1. **`version`**: bump per the chosen bump type. Kit-authoritative. 2. **`legacyDeletions`** (the deprecation ledger): **dev is authoritative and always the superset.** Whenever dev removes a skill/dir/file from the harness it appends the path here so downstream projects clean it up on their next `vc-update`. Set `kit.legacyDeletions = dev.legacyDeletions` (preserve dev order). This field is a literal path array, NOT a glob — it genuinely changes every time the harness deprecates something, so it MUST be reconciled at every publish. (Historically this was skipped, which silently stranded the kit with stale deletions — never skip it.) 3. **All OTHER non-version fields** (`include`, `exclude`, `strip`, `merge`, `copyIfMissing`, `symlinks`, `kitOnly`): these legitimately diverge — the kit carries packaging-only rules (e.g. excluding `**/*.test.mjs` and `__tests__/**` so tests are not shipped to users; `kitOnly` tooling like `compute-sync-plan.mjs`). **Do NOT blindly overwrite them** — a blanket dev→kit copy would strip the kit's test-excludes and ship test files. Instead run the **field-level drift report** below and reconcile any unexpected drift by hand. Drift-report command (run during Step 4 summary AND here before writing): ```bash node -e ' const d=require("/vc-manifest.json"); const k=require("/vc-manifest.json"); let drift=0; for(const key of new Set([...Object.keys(d),...Object.keys(k)])){ if(key==="version") continue; if(JSON.stringify(d[key])!==JSON.stringify(k[key])){ drift++; console.log("DRIFT field:",key); console.log(" dev:",JSON.stringify(d[key])); console.log(" kit:",JSON.stringify(k[key])); } } console.log(drift?("\n"+drift+" manifest field(s) drift — legacyDeletions auto-syncs dev→kit; reconcile the rest consciously."):"manifest in sync (besides version)"); ' ``` `legacyDeletions` appearing in the drift report is EXPECTED and is auto-resolved (dev→kit). Any OTHER field in the report is a conscious decision: confirm the kit value is the intended packaging rule, or update dev/kit so they converge. Never let a drift go unexamined. - Create symlinks if missing (`.agents/skills -> ../.claude/skills`). ### Step 8: Leak Detection 13. Verify no project-specific content leaked into the kit repo. This is a **resolved-set, two-check** gate that scans the full shipped TEXT surface, not just `CLAUDE.md`/`AGENTS.md`. **Resolve the shipped set** via the kit's resolver, then restrict to TEXT surfaces: ```bash node /resolve-manifest.mjs --root --json ``` Take the resolved `files` and keep only TEXT surfaces: - `.claude/skills/**` matching `*.md`, `*.cjs`, `*.mjs`, `*.py`, `*.js`, `*.json` - `.claude/agents/**` matching `*.md` - `.codex/**` - `process/development-protocols/**` - plus `CLAUDE.md`, `AGENTS.md` Exclude binaries and `**/node_modules/**`. **Check (a) -- product-name grep over the resolved text set.** Scan for the product names in the grep below ONLY. `tRPC`/`Prisma` are DROPPED from this skill-prose scan to avoid false positives in legitimate generic test guidance; the hosted-database product name is KEPT (see the pattern): ```bash grep -rIin "flowser\|CloakBrowser\|OpenClaw\|Supabase" ``` Allowlist the Bucket-4 lines that MUST keep the literal to function (otherwise the gate flags itself): `author: flowser` frontmatter; the `isFlowserActivePlanPath` identifier; this skill's OWN scrub-grep pattern lines below; the new validator's own pattern strings; and the one internal plan-generation validation comment in `session-init.cjs`. **Check (b) -- non-portable context-path grep:** any concrete backticked `process/context/...` file reference in the resolved text set, MINUS the shipped/seeded survivors, is a dangling-link leak → FAIL with file:line. Survivors (allowed): `process/context/all-context.md`, `process/context/tests/all-tests.md`. Portable directory refs (e.g. `process/context/tests/`) and the `process/context/...` placeholder are fine. **Keep the existing narrow `CLAUDE.md`/`AGENTS.md` grep** (this stays as-is on just those two files; `tRPC`/`Prisma` plus the hosted-database product name all REMAIN here, as shown in the pattern below): ```bash # Must return empty -- any matches indicate leaked content grep -ri "flowser\|tRPC\|Prisma\|Supabase\|CloakBrowser\|OpenClaw" CLAUDE.md AGENTS.md # Must return empty -- no absolute paths grep -r "/Users/" . ``` **Check (c) -- README badge counts:** Verify the kit README.md badge counts match actual agent and skill counts: ```bash actual_agents=$(ls /.claude/agents/*.md | wc -l | tr -d ' ') actual_skills=$(ls -d /.claude/skills/vc-*/ | wc -l | tr -d ' ') readme_agents=$(grep -oE '[0-9]+-Agents' /README.md | grep -oE '[0-9]+') readme_skills=$(grep -oE '[0-9]+-Skills' /README.md | grep -oE '[0-9]+') echo "Agents: actual=$actual_agents badge=$readme_agents" echo "Skills: actual=$actual_skills badge=$readme_skills" [ "$actual_agents" = "$readme_agents" ] && [ "$actual_skills" = "$readme_skills" ] && echo "PASS" || echo "FAIL: badge counts mismatch" ``` If FAIL: update README.md badges to match actual counts before committing. NOTE: the brand grep matches product names ONLY. It does NOT match `.ck.json`/`.ckignore` -- those Phase-2 legacy-fallback literals are intentional and must NOT be flagged. Do not add `ck`/`ckignore` to any leak grep. The standing `validate-kit-portability.mjs` validator (run by `vc-audit-vc`) mirrors checks (a) and (b) for between-release drift; this Step-8 gate is the publish-time enforcement. 14. If leak detection fails: - Print the offending lines (file:line). - Revert the changes in the kit repo (`git -C checkout .`). - STOP and report the leak. Do NOT commit or push. ### Step 9a: Commit and Tag 15. In the kit repo. If the version was already at the target (skip-bump path from Step 2): use `tag-as-is` and commit with the existing version number. Otherwise: bump the version in `vc-manifest.json` and commit with the new version. ```bash cd git add -A git commit -m "Release vX.Y.Z" git tag vX.Y.Z ``` ### Step 9b: STOP — Explicit Push Approval (separate from Step 6 confirm) Leak detection passed. The commit and tag are ready locally. Before running `git push`, you **MUST** stop and get explicit user approval: > "Leak detection passed. The commit is ready locally (`git log --oneline -1` shows the new commit). > **Type 'push' to publish to remote, or 'abort' to keep the commit local.**" Do NOT run `git push` or `git push --tags` until the user types 'push' (or a clear affirmative). This is a **separate gate** from the publish-confirm at Step 6 — even if the user approved publishing in Step 6, they must re-confirm before the actual remote push. If the user says 'abort': - The local commit and tag are preserved. - Print: "Commit and tag preserved locally. Run `git push origin main && git push --tags` when ready." - Stop. Only on explicit 'push': proceed to Step 10 (`git push origin main && git push --tags`). ### Step 10: Push 16. Push to remote (only after Step 9b approval): ```bash git push origin main && git push --tags ``` 17. If push fails (e.g., rejected, auth error), report the error. The commit and tag are preserved locally for retry. ### Step 11: Create GitHub Release 18. After a successful push, create a GitHub Release so watchers are notified and the release appears in the Releases tab: ```bash gh release create vX.Y.Z \ --repo / \ --title "vX.Y.Z: " \ --notes "" ``` - If the user provided release notes at Step 6, use them verbatim. - If left blank, auto-generate: `"N modified, M new, P removed. See commit log for details."` - The `--title` one-liner should be the first sentence of the notes (truncate at 72 chars if longer). - If `gh` is unavailable or the push to remote failed, skip this step and note it in the summary. ### Step 12: Post-Publish Remote Verify After a successful push, clone the kit from remote to a temp dir and verify the catalog works on a fresh install: ```bash TS=$(date +%s) git clone /tmp/vc-kit-verify-$TS node /tmp/vc-kit-verify-$TS/.claude/skills/vc-context-discovery/scripts/discover-skills.mjs 2>&1 rm -rf /tmp/vc-kit-verify-$TS ``` Expected: exit 0 and expected skill count in output. If FAIL: note the error in the publish summary — the push succeeded but the remote install may have a catalog issue. ### Step 13: Print Summary 19. Print publish summary: ``` vc-publish complete =================== Version: v2.2.0 (was v2.1.0) Files changed: 4 Remote: git@github.com:withkynam/vibecode-pro-max-kit.git Tag: v2.2.0 Release: https://github.com///releases/tag/v2.2.0 ``` ## Key Changes from v1.0 - **Glob arrays are stable; the deprecation ledger is not.** The glob patterns in `include`/`exclude`/`kitOnly` are stable — adding a new skill or agent requires zero manifest edits (new files are auto-included by the globs). BUT `legacyDeletions` is a literal path array, not a glob: it grows every time the harness deprecates a skill/dir/file, and it MUST be reconciled dev→kit at every publish (see Step 7 Manifest reconciliation). Skipping it strands the kit with a stale deletion ledger so downstream `vc-update`s never clean up the newly deprecated dirs. So publish-time manifest edits are: (1) version bump, (2) `legacyDeletions` sync, (3) conscious reconciliation of any other field flagged by the drift report. - **Resolver-driven diffing.** The kit repo is resolved via `resolve-manifest.mjs` (which requires `vc-manifest.json` in its `--root`). The dev-side file comparison is handled inside `compute-sync-plan.mjs`, which reads the manifest from `--kit-root` (always the kit checkout). Dev repos do not carry `vc-manifest.json`. - **No `managed`/`managedDirs` arrays to update.** The old workflow of adding new files to these arrays is eliminated. ## Rules - **ALWAYS** run the full resolver diff (Steps 3-4) even when changes already exist in the kit repo. Direct kit edits (README, translations, community files) do not replace the dev→kit diff. Both change sources must be captured in the same publish. - **NEVER** copy project-specific files: `process/context/all-context.md` (with real content), `process/features/*`, `process/general-plans/*` (with real plans) - **ALWAYS** verify no project-specific content leaked before committing (Step 8) - **ALWAYS** show the diff summary before publishing (Step 5-6) - **NEVER auto-push.** `git push` must be preceded by a separate explicit 'push' confirmation (Step 9b), distinct from the publish-confirm at Step 6. This rule holds even when `VC_KIT_SOURCE` is a local path. - CLAUDE.md and AGENTS.md require special handling -- never copy the development repo's project-specific versions directly - Kit repo checkout path is stored in `.vc-publish-config` (add to `.gitignore`) - The only manifest edit at publish time is the version bump -- glob patterns are stable ## Reference See `references/vc-publish.md` for the detailed algorithm, CLAUDE.md/AGENTS.md stripping rules, error handling, and example outputs.