vt-d-promote¶
Sandbox-to-production promotion with production-readiness audit. Moves code from prototype/sandbox to deployable partition after security, quality, and performance checks.
Plugin: vt-product-dev
Category: Governance
Command: /vt-d-promote
/vt-d-promote — Sandbox-to-Production Promotion¶
Unified Repo Workflow: /vt-d-pd-6-handoff → /vt-d-1-bootstrap →
/vt-d-promote→ /vt-d-2-plan → /vt-d-3-buildReplaces manual copy-paste of prototype code into production paths.
Purpose¶
The "Promotion Path" from the Unified Repo Governance model. When code needs to move from a sandbox partition (e.g., 04-prototyp/) to the deployable partition (src/), this skill:
- Audits the code for production readiness (security, quality, performance)
- Moves files to the correct deployable location
- Updates imports and references
- Adds traceability links (SPEC- references)
- Verifies nothing is broken
When to Use¶
- After
/vt-d-pd-6-handoffwhen moving prototype components to production - When sandbox experiments are ready for production
- Anytime code needs to cross from sandbox to deployable partition
Prerequisites¶
.repo-manifest.yamlmust exist (defines partitions)- Source files must be in a
sandboxpartition path - Target must be a
deployablepartition path
Invocation¶
/vt-d-promote # Interactive — scan sandbox, ask what to promote
/vt-d-promote 04-prototyp/components/Dashboard # Promote specific component
/vt-d-promote --dry-run # Show what would happen without moving
Execution Steps¶
Step 1: Review Gate Check¶
Before promoting any code, verify that code review has passed.
- Read
.review-gate.mdfrom the project root - If missing:
STOP. Do not proceed with promotion.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BLOCKED: No Review Gate Found ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Promotion to production requires a passing code review. Run /vt-d-4-review first, then retry /vt-d-promote. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠ Order is load-bearing. These two sit BEFORE the
status: FAILbranch on purpose: that branch ends in STOP, so anything placed after it is unreachable. Measured in review pass 9 — on the modal FAIL + INCOMPLETE gate this spec produces, promote stopped at step 3 and never printeddegraded_reason(the whole content of SC-2), and the MANDATORY validator call below never ran at all.workflow-5-finalizehad this right and carried the warning; this file andfinalization-orchestrator.mddid not.⚠ And the rule bit twice. Pass 10 (H-4) measured that step 2c's own STOP had, in turn, made steps 3 and 6 unreachable — the same rule, one level along, introduced by the fix for the pass-9 finding. Reordering moves a STOP; it does not remove the reachability question. Step 2c now names the specific block to print before it stops, so both remain reachable. Before adding any STOP to this step, state which branches below it you have just made dead.
2b. If completeness: INCOMPLETE (SPEC-192 pass 6, C-2) — check this BEFORE status:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
BLOCKED: Review Gate INCOMPLETE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
The review did not finish, or finished without measuring.
Returned without measuring: [reviewers_unmeasured]
Never returned: [reviewers_outstanding]
Stood down for this pass: [reviewers_deferred]
Dispatched and did not complete: [reviewers_failed]
[print only the lists that are non-empty — at least one always is, or the gate is not INCOMPLETE]
[the gate's degraded_reason text, verbatim, when reviewers_unmeasured is non-empty]
Whatever `status` says, it rests on a pass that did not cover every reviewer.
Re-run /vt-d-4-review, then retry /vt-d-promote.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠ All FOUR uncovered states, not two (pass 14, document-quality H1 / data-integrity M-4).
reviewers_deferred and reviewers_failed have been uncovered states in the gate FILE since
pass 8 — validate-review-gate.sh derives uncovered over all four — but this message named
two, so a gate INCOMPLETE only because a reviewer was stood down or failed printed a BLOCKED
banner naming no cause at all. It still STOPped, so nothing was promoted wrongly; what broke
was the requirement that the gate be legible without the spec — on the irreversible path.
Why this branch exists (SPEC-192 pass 6, C-2). Measured: this file and
finalization-orchestrator.mdcarried zero occurrences ofcompletenessand never ran the validator, whileworkflow-5-finalizehad both. A gate withstatus: PASS+completeness: INCOMPLETE— the shape R4 exists to refuse, and a shipped fixture — was refused by the validator and accepted here, on the one path that is not reversible.spec.mdargued this was covered because "an INCOMPLETE pass cannot be PASS (R4)"; R4 lives in the validator, and promote never ran it. A rule is only in force where it is evaluated.
2c. Validate the ledger mechanically — MANDATORY:
⚠ NEVER by a repo-relative path such as plugins/vt-base/… (BUG-340). This runs in the
project being acted on, not in the toolkit, so that path exists only when the project IS the
toolkit. Measured 2026-09-29 in a real consumer project: "No such file", which reads as "the
toolkit has no validator". The ../vt-base/ hop is the same one Step 0c uses (BUG-037):
$CLAUDE_PLUGIN_ROOT names the CALLING plugin, vt-product-dev, and the script is in
vt-base. If it does not resolve, that is a toolkit defect — report FAIL with the path
tried, and do not skip this step.
⚠ This form is UNGRANTABLE, and that is accepted (SPEC-192 H-6). The settings guard
accepts a Bash rule only when it pins one tracked file, so no rule can match a
$CLAUDE_PLUGIN_ROOT path. Pass 12 briefly made the grantable repo-relative spelling
primary to fix exactly that — and BUG-340 had already MEASURED that spelling failing in a
real project the same day. Resolving beats being granted: a command that cannot run is
worse than one that prompts. Decision 9's grant therefore helps only when the toolkit is
itself the project.
Continue only when it exits 0 and its OK line says finalizable=yes. Every other
outcome is STOP, and print the validator's own output verbatim: exit 1 (it names the
reviewers that were dispatched and never accounted for, or the key it could not read), exit 0
with finalizable=no, or no output at all because the command did not run. Exit 0 alone means
the ledger is consistent, not that promotion may proceed — a consistent INCOMPLETE gate exits
0 too. Do not replace this call with a reading of the gate; reading it is what failed.
The ../vt-base/ hop is the only spelling, and it is UNGRANTABLE (BUG-340). A repo-relative
path resolves only when the project IS the toolkit; measured 2026-09-29 in a real consumer
project it gave "No such file", which reads as "the toolkit has no validator". The settings
guard accepts a Bash rule only when it names one tracked file, so no rule can ever match a
$CLAUDE_PLUGIN_ROOT path — this MANDATORY step therefore has no grant and will prompt.
Accepted: a command that cannot run is worse than one that prompts.
The hop is not optional and must not be "simplified" away. It is the only arm, not a
fallback behind a primary: nothing is tried before it. The repo-relative form
resolves ONLY inside a toolkit checkout: measured from a consumer project it is rc=127
(SPEC-192 pass 8, P8-C1), which this step treats as STOP — so on its own it would move the
failure rather than remove it. $CLAUDE_PLUGIN_ROOT/../vt-base/ is correct from BOTH plugins:
from vt-product-dev it hops sideways, and from vt-base it collapses to itself. If
$CLAUDE_PLUGIN_ROOT is unset, locate the script relative to this file at
../../../vt-base/bin/validate-review-gate.sh.
On that STOP, first print the most specific block below that applies — step 3 when the gate
says status: FAIL, step 6 when status is absent or anything else — and only fall back to the
validator's output alone when neither does. Those two steps are reached from here, not by
falling through to them.
⚠ Why that sentence exists (SPEC-192 pass 10, H-4).
finalizable=yesrequirescompleteness: COMPLETEandstatus: PASS, so it is false for EVERYFAILgate and for every gate with nostatus. A plain STOP here therefore made step 3 ("Review Gate FAILED") and step 6 ("Carries No PASS") dead branches — step 6 having been added by pass 5 for exactly the shape this STOP now swallowed. The pass-9 reorder that put this call before step 3 moved the defect instead of removing it, and the "Order is load-bearing" warning above is the rule it broke: a branch that ends in STOP makes everything after it unreachable. Step 4 (branch mismatch) stayed alive only because the validator does not readbranch.
-
If
status: FAIL— reached from step 2c's STOP path, not by falling through (H-4):STOP. Do not proceed with promotion.━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BLOCKED: Review Gate FAILED ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ The last code review did not pass. Fix the issues, re-run /vt-d-4-review, then retry /vt-d-promote. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -
If
branchdoes not match the current git branch:STOP. Do not proceed with promotion.━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BLOCKED: Review Gate Is For A Different Branch ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ The gate passed on branch '[gate-branch]', but you are on '[current-branch]'. The code you are about to promote has not been reviewed on this branch. `.review-gate.md` is gitignored, so it survives a branch switch untouched — a PASS from another branch is not evidence about this one. Run /vt-d-4-review on this branch, then retry /vt-d-promote. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Why this BLOCKS where
/vt-d-5-finalizeonly warns (BUG-193). Finalize is reversible; promotion is not. A warning on an irreversible action is one nobody is required to act on. Every other gate consumer —phase-checkpoint,workflow-5-finalize,workflow-3-build,complete— validates this field; promote was the only one that did not, and the only one that ships to production. The escape hatch is deliberately not an override flag: it is re-running the gate, which is the thing the gate asserts happened.
-
If
status: PASSand the branch matches: Display confirmation and continue. -
If
statusis anything else — including ABSENT: STOP. Reached from step 2c's STOP path, not by falling through (H-4) — the validator reports this shape asstatus=n/a finalizable=no, so 2c stops first and prints this block.STOP. Do not proceed with promotion.━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BLOCKED: Review Gate Carries No PASS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ The review gate does not record `status: PASS`. Promotion requires an explicit PASS, not the absence of a FAIL. Run /vt-d-4-review, then retry /vt-d-promote. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Why this branch exists (SPEC-192 pass 5, SEC-3). Steps 3 and 5 enumerated
FAILandPASSand nothing else, so a gate with nostatuskey matched neither and fell through — and SPEC-192 Step 2.9 item 4 deliberately tells the orchestrator to write exactly that shape when a dispatched reviewer never returned and no verdict could be reached. The validator reports it asstatus=n/a finalizable=no; promote read it as "not FAIL" and shipped. Promotion must require an explicit PASS, never infer one from the absence of a FAIL — an unreached verdict is not a passing one.
Provenance check (SPEC-189 FR-4) — attached to the PASS branch only. After reading
status, read validator. If it is not agent — including absent, which reads as unknown
and never as agent (FR-6) — display:
⚠ This gate's PASS was NOT produced by its prescribed validator (validator: <value>).
Did not run: <prescribed − validators_run>, from the writing skill's `requires_agents`.
When no prescribed set is declared, name what DID run instead and say so.
⚠ This is a BLOCKING region, so the notice carries no "Continuing" line — printing
"this is a notice, not a block" beside a BLOCKED/STOP would be worse than printing
nothing. FR-5 restated for this region: the notice changes no branch. It must never be
read as licence to soften a block into a warning.
Step 1.5: Test Gate Check¶
Also verify that automated tests have passed:
- Read
.test-gate.mdfrom the project root -
If missing:
STOP. Do not proceed with promotion.━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BLOCKED: No Test Gate Found ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Promotion requires passing tests. Run /vt-d-4-review first, then retry /vt-d-promote. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -
If
STOP. Do not proceed with promotion.status: FAIL: -
If
branchdoes not match the current git branch:STOP. Do not proceed with promotion.━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BLOCKED: Test Gate Is For A Different Branch ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ The gate passed on branch '[gate-branch]', but you are on '[current-branch]'. The code you are about to promote has not been tested on this branch. `.test-gate.md` is gitignored, so it survives a branch switch untouched — a PASS from another branch is not evidence about this one. Run /vt-d-4-review on this branch, then retry /vt-d-promote. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Why this BLOCKS where
/vt-d-5-finalizeonly warns (BUG-193). Finalize is reversible; promotion is not. A warning on an irreversible action is one nobody is required to act on. Every other gate consumer —phase-checkpoint,workflow-5-finalize,workflow-3-build,complete— validates this field; promote was the only one that did not, and the only one that ships to production. The escape hatch is deliberately not an override flag: it is re-running the gate, which is the thing the gate asserts happened.
- If
status: PASSand the branch matches: Display confirmation and continue.
Provenance check (SPEC-189 FR-4) — attached to the PASS branch only. After reading
status, read validator. If it is not agent — including absent, which reads as unknown
and never as agent (FR-6) — display:
⚠ This gate's PASS was NOT produced by its prescribed validator (validator: <value>).
Did not run: <prescribed − validators_run>, from the writing skill's `requires_agents`.
When no prescribed set is declared, name what DID run instead and say so.
⚠ This is a BLOCKING region, so the notice carries no "Continuing" line — printing
"this is a notice, not a block" beside a BLOCKED/STOP would be worse than printing
nothing. FR-5 restated for this region: the notice changes no branch. It must never be
read as licence to soften a block into a warning.
Step 2: Load Manifest¶
Read .repo-manifest.yaml to identify:
- partitions.sandbox.paths — source locations
- partitions.deployable.paths — target locations
If no manifest:
No .repo-manifest.yaml found. Cannot determine partition boundaries.
Run /vt-c-repo-init to initialize unified repo governance.
Step 3: Identify Promotable Code¶
Scan sandbox paths for code files.
04-prototyp/
├── src/app/
│ ├── shared/components/
│ │ ├── vt-button/vt-button.component.ts ← promotable
│ │ └── vt-card/vt-card.component.ts ← promotable
│ ├── features/
│ │ ├── dashboard/dashboard.component.ts ← promotable
│ │ └── auth/login.component.ts ← promotable
│ └── core/
│ └── theme/vt-preset.ts ← promotable
└── package.json ← reference only (not promoted directly)
Step 4: Select Components to Promote¶
If no argument provided, use AskUserQuestion:
Options (multi-select):
- components/Dashboard.tsx — Dashboard layout with charts
- components/UserProfile.tsx — User profile card
- components/LoginForm.tsx — Authentication form
- lib/api-client.ts — API client wrapper
- All of the above
Step 5: Pre-Promotion Audit¶
Invoke sub-agents to audit selected files for production readiness:
4a: Security Audit¶
Invoke security-sentinel agent with the selected files:
Check for:
- Hardcoded API keys, tokens, passwords
- XSS vulnerabilities (innerHTML, dangerouslySetInnerHTML, Angular [innerHTML] without DomSanitizer)
- SQL injection patterns
- Insecure HTTP calls (http:// instead of https://)
- Missing input validation
- CORS misconfiguration
4b: Code Quality Audit¶
Invoke code-simplicity-reviewer agent:
Check for:
- TODO, FIXME, HACK, XXX comments
- console.log, console.debug statements
- Mock data / hardcoded test data
- Commented-out code blocks
- Missing error handling
- Missing TypeScript types (any usage)
4c: Performance Audit¶
Invoke performance-oracle agent:
Check for: - N+1 query patterns - Missing memoization on expensive computations - Unbounded list rendering (no virtualization) - Missing loading states - Synchronous operations that should be async - Bundle size concerns (large imports)
Step 6: Show Audit Results¶
Pre-Promotion Audit Results
═══════════════════════════════════════════
Security: ⚠ 1 warning
- components/LoginForm.tsx:42 — Missing CSRF token in form submission
Quality: ❌ 3 issues
- components/Dashboard.tsx:15 — console.log("debug data")
- lib/api-client.ts:8 — TODO: add error handling
- components/Dashboard.tsx:67 — Mock data: const users = [...]
Performance: ✅ Clean
═══════════════════════════════════════════
3 issues must be resolved before promotion.
Fix now, or promote with warnings?
Use AskUserQuestion:
- Fix issues first (Recommended) — address findings, then re-run /vt-d-promote
- Promote with warnings — proceed despite warnings (issues tracked)
- Cancel — abort promotion
Step 7: Execute Promotion¶
For each selected file:
-
Determine target path:
-
Create target directory if it doesn't exist
-
Move file from sandbox to deployable partition
-
Update imports in the moved file:
- Adjust relative import paths for new location
-
Replace mock data imports with real data references
-
Add traceability comment at the top of each moved file:
-
Update references in other files that imported the moved file
Step 8: Post-Promotion Verification¶
Report results:
Post-Promotion Verification:
TypeScript: ✅ No errors
Tests: ✅ All passing
Lint: ⚠ 2 warnings (non-blocking)
IMPORTANT: If tests fail after promotion, BLOCK — do not proceed to Step 9.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
BLOCKED: Post-Promotion Tests Failed
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Promotion moved files but tests now fail. This must be fixed before
recording the promotion as complete.
Failing tests: [list]
Fix the issues (likely import paths or missing dependencies),
then re-run the verification step.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 9: Record Promotion¶
Update .repo-manifest.yaml with promotion history:
promotions:
- date: "2026-02-13T10:00:00"
spec_id: SPEC-002
files:
- from: 04-prototyp/components/Dashboard.tsx
to: src/components/Dashboard.tsx
- from: 04-prototyp/lib/api-client.ts
to: src/lib/api-client.ts
audit_result: warnings_accepted
audit_findings: 1
Step 10: Display Summary¶
Promotion Complete!
Promoted 2 files from 04-prototyp/ → src/
✅ components/Dashboard.tsx → src/components/Dashboard.tsx
✅ lib/api-client.ts → src/lib/api-client.ts
Traceability: SPEC-002 (Dashboard Layout)
Verification: TypeScript ✅ | Tests ✅ | Lint ⚠
Remaining in sandbox: 2 files
- components/UserProfile.tsx
- components/LoginForm.tsx
Next steps:
/vt-d-promote — promote more components
/vt-d-complete — mark current specs done
/vt-c-repo-audit — verify governance compliance
Dry Run Mode (--dry-run)¶
Shows what would happen without moving any files: