Skip to content

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-build

Replaces 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:

  1. Audits the code for production readiness (security, quality, performance)
  2. Moves files to the correct deployable location
  3. Updates imports and references
  4. Adds traceability links (SPEC- references)
  5. Verifies nothing is broken

When to Use

  • After /vt-d-pd-6-handoff when 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.yaml must exist (defines partitions)
  • Source files must be in a sandbox partition path
  • Target must be a deployable partition 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.

  1. Read .review-gate.md from the project root
  2. If missing:
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    BLOCKED: No Review Gate Found
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
    Promotion to production requires a passing code review.
    Run /vt-d-4-review first, then retry /vt-d-promote.
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
    STOP. Do not proceed with promotion.

⚠ Order is load-bearing. These two sit BEFORE the status: FAIL branch 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 printed degraded_reason (the whole content of SC-2), and the MANDATORY validator call below never ran at all. workflow-5-finalize had this right and carried the warning; this file and finalization-orchestrator.md did 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.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
STOP. Do not proceed with promotion.

⚠ 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.md carried zero occurrences of completeness and never ran the validator, while workflow-5-finalize had both. A gate with status: 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.md argued 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:

bash "$CLAUDE_PLUGIN_ROOT/../vt-base/bin/validate-review-gate.sh" .review-gate.md

⚠ 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=yes requires completeness: COMPLETE and status: PASS, so it is false for EVERY FAIL gate and for every gate with no status. 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 read branch.

  1. If status: FAIL — reached from step 2c's STOP path, not by falling through (H-4):

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    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.
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
    STOP. Do not proceed with promotion.

  2. If branch does not match the current git branch:

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    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.
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
    STOP. Do not proceed with promotion.

Why this BLOCKS where /vt-d-5-finalize only 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.

  1. If status: PASS and the branch matches: Display confirmation and continue.

    ✓ Review gate: PASS — proceeding with promotion.
    

  2. If status is anything else — including ABSENT: STOP. Reached from step 2c's STOP path, not by falling through (H-4) — the validator reports this shape as status=n/a finalizable=no, so 2c stops first and prints this block.

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    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.
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
    STOP. Do not proceed with promotion.

Why this branch exists (SPEC-192 pass 5, SEC-3). Steps 3 and 5 enumerated FAIL and PASS and nothing else, so a gate with no status key 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 as status=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:

  1. Read .test-gate.md from the project root
  2. If missing:

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    BLOCKED: No Test Gate Found
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
    Promotion requires passing tests. Run /vt-d-4-review first, then retry /vt-d-promote.
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
    STOP. Do not proceed with promotion.

  3. If status: FAIL:

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    BLOCKED: Test Gate FAILED
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
    Tests are failing. Fix tests and re-run /vt-d-4-review before promoting.
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
    STOP. Do not proceed with promotion.

  4. If branch does not match the current git branch:

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    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.
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
    STOP. Do not proceed with promotion.

Why this BLOCKS where /vt-d-5-finalize only 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.

  1. If status: PASS and the branch matches: Display confirmation and continue.
    ✓ Test gate: PASS — tests verified.
    

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:

Found promotable code in 04-prototyp/:

Which components should be promoted to src/?

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:

  1. Determine target path:

    04-prototyp/components/Dashboard.tsx → src/components/Dashboard.tsx
    04-prototyp/lib/api-client.ts → src/lib/api-client.ts
    

  2. Create target directory if it doesn't exist

  3. Move file from sandbox to deployable partition

  4. Update imports in the moved file:

  5. Adjust relative import paths for new location
  6. Replace mock data imports with real data references

  7. Add traceability comment at the top of each moved file:

    // Promoted from 04-prototyp/ — SPEC-002: Dashboard Layout
    // Original: 04-prototyp/components/Dashboard.tsx
    

  8. Update references in other files that imported the moved file

Step 8: Post-Promotion Verification

# Run type check
npx tsc --noEmit

# Run tests if they exist
npm test

# Run linter
npx eslint src/

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:

DRY RUN — No files will be moved

Would promote:
  04-prototyp/components/Dashboard.tsx → src/components/Dashboard.tsx
  04-prototyp/lib/api-client.ts → src/lib/api-client.ts

Would add traceability: SPEC-002
Would update 3 import references

Run /vt-d-promote without --dry-run to execute.