nacl-tl-ship
SkillProductivityCommit, push, create PR, and update YouGile after development. Reads git strategy from config.yaml (direct vs feature-branch). Posts commit summary to YouGile task chat. Use when: ship code, commit and push, create PR, finish development, or the user says "/nacl-tl-ship".
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the nacl-tl-ship skill
What this skill tells your AI
The instructions your AI receives, as published by itsalt/nacl in nacl-tl-ship/SKILL.md and read by ahel’s review.
Contract
Inputs this skill consumes:
- Prior verification status from .tl/status.json or YouGile task chat (six-status vocabulary: PASS / BLOCKED / UNVERIFIED / NO_INFRA / RUNNER_BROKEN / REGRESSION)
- Local test-suite results (sanity check, NOT a substitute for upstream status)
- config.yaml (git strategy)
Outputs this skill produces:
- Headline one of: SHIP COMPLETE / SHIP APPLIED — UNVERIFIED (only when user explicitly overrides on --deploy with non-PASS upstream) / SHIP HALTED — {NO_INFRA | RUNNER_BROKEN | UNVERIFIED | BLOCKED} / SHIP INCOMPLETE — REGRESSION
- Commit + push to feature branch
- PR creation (gated on aggregated PASS or explicit override)
Downstream consumers of this output:
- nacl-tl-deliver
- nacl-tl-deploy
- nacl-tl-release
Contract change discipline:
If this skill's output contract changes, every downstream consumer listed above
must be audited and updated in the same release. The 0.10.0→0.10.1 regression
was caused by the absence of this discipline. nacl-tl-fix changed its output
contract (new status vocabulary, new header strings, new Status: field)
without auditing nacl-tl-reopened and nacl-tl-hotfix, which were the only
two skills that consume its output. Had a ## Contract section existed in
nacl-tl-fix, the update would have included a list of downstream consumers,
making the audit mandatory and visible.
TeamLead Ship — Commit + Push + PR + YouGile
Your Role
You are the shipping specialist. After development is complete (code written, tests pass, review approved), you commit, push, create a PR if needed, and notify YouGile. You bridge the gap between "code works locally" and "code is in the repo."
Key Principle
Read git strategy from config.yaml → commit → push → PR (if feature-branch) → YouGile update
Never push without passing tests first.
Safety Rules
ABSOLUTE RULE: When strategy == "feature-branch", you MUST NOT commit to base_branch. No exceptions. No rationalizations. Not "all recent commits are small fixes." Not "the user gave a bare message." Not "this is a tiny one-liner." If you are on base_branch and strategy is feature-branch, you MUST create a new branch BEFORE committing.
- NEVER commit to base_branch when strategy == "feature-branch". This is the highest-priority rule. It overrides ALL edge cases below. If you are about to
git commiton base_branch with feature-branch strategy — STOP, you have a bug in your reasoning. - NEVER switch branches. If on branch X (not base_branch), commit to X. Period.
- NEVER push to a branch other than the current one. No
git checkout main. No autonomous "this is a hotfix" decisions. - If you believe the current branch is wrong — ASK the user. Suggest
/nacl-tl-hotfixif critical. - The ONLY skill authorized to target
mainfrom another branch is/nacl-tl-hotfix. - MECHANICAL GUARD: Before every
git commit, run the base-branch assertion (Step 2.5). If it prints FATAL — do NOT proceed.
Invocation
/nacl-tl-ship UC028 # ship specific UC
/nacl-tl-ship --feature FR-001 # ship all tasks in a feature
/nacl-tl-ship "custom commit message" # ship with explicit message
/nacl-tl-ship # ship all uncommitted changes (auto-compose message)
/nacl-tl-ship --deploy # ship + deploy (method from config.yaml)
/nacl-tl-ship UC028 --deploy # ship UC + deploy
Configuration Resolution
IMPORTANT: Projects may use different config.yaml structures. Check BOTH formats:
| Data | Source priority (check in order, use first found) |
|---|---|
| Git strategy | git.strategy > modules.[name].git_strategy > fallback "feature-branch" |
| Base branch | git.main_branch > modules.[name].git_base_branch > fallback "main" |
| Branch prefix | git.branch_prefix > fallback "feature/" |
| Build command | modules.[name].build_cmd > fallback npm run build |
| Test command | modules.[name].test_cmd > fallback npm test |
| Module path | modules.[name].path > detect from package.json |
| YouGile dev_done | yougile.columns.dev_done |
| Deploy method (staging) | deploy.staging.method > deploy.method > fallback "github-actions" |
| Deploy script | deploy.staging.script > no default |
| Skip CI flag | deploy.staging.skip_ci > fallback false |
| Deploy env file | deploy.staging.env_file > no default |
Two config.yaml formats exist in projects:
Format A (per-module):
modules:
backend:
git_strategy: "feature-branch"
git_base_branch: "main"
Format B (top-level git section):
git:
strategy: "feature-branch"
main_branch: "main"
branch_prefix: "feature/"
Always check Format B first (top-level git: section), then Format A (modules.[name]). Never assume only one format exists.
If config.yaml missing → use all fallback defaults. If YouGile missing → skip task moves.
Workflow: 6 Steps
Step 1: PRE-FLIGHT CHECKS
-
Read prior verification status (BEFORE running local tests):
Check
.tl/status.jsonfor the task being shipped, or read the sub-skill output from the task chat. Look for the six-status vocabulary:Prior status Action PASS Proceed normally UNVERIFIED HALT. Post advisory: "Task dev status is UNVERIFIED — no test exercises the change. Local tests passing does NOT substitute for upstream verification. Confirm to ship unverified? [yes/no] Default: no". If the operator answers explicitly "yes" (NOT auto-confirmed by --yes), proceed withSHIP APPLIED — UNVERIFIEDheadline; PR description is annotated; auto-deploy via--deployis refused in this state — the operator must run/nacl-tl-deployseparately as an explicit deploy override. If no answer →SHIP HALTED — UNVERIFIED.BLOCKED HALT. Post: "Task dev status is BLOCKED — pre-existing failures. Confirm to ship blocked task? [yes/no] Default: no". If confirmed → proceed under SHIP APPLIED — UNVERIFIED (BLOCKED override); auto-deploy refused. If not →SHIP HALTED — BLOCKED.NO_INFRA HALT. Report: SHIP HALTED — NO_INFRA. Recommend fixing infra.RUNNER_BROKEN HALT. Report: SHIP HALTED — RUNNER_BROKEN. Escalate.REGRESSION HALT. Report: SHIP INCOMPLETE — REGRESSION. Do NOT ship.Unknown / not found (no status.json AND no Task node in graph) HALT. Report: SHIP HALTED — UNVERIFIED (upstream status unknown). The previous "warn and proceed" backward-compat path has been removed (P5 + 0.14.0 contract). The operator must populate the graph or.tl/status.json, or invoke an explicit user-initiated path that knowingly accepts the unknown state.Local tests passing does NOT override prior UNVERIFIED/BLOCKED/unknown status. Local tests are a sanity check, not a verification substitute.
Reaffirmed: ship never switches branches autonomously (P5). Ship commits to the current branch only. Hotfix-to-main is a separate user-initiated skill (
/nacl-tl-hotfix); a non-PASS upstream status does NOT cause this skill to pivot to a hotfix branch or tomain. The operator chooses. -
Read
config.yamlfor git strategy, module config, and deploy config -
Run tests for affected modules:
cd [module_path] && [test_cmd] -
Run build:
cd [module_path] && [build_cmd]- If
--deployflag is set anddeploy.staging.env_fileexists: load env vars from that file before building the frontend module. This injects staging-specific variables (e.g.NEXT_PUBLIC_VK_CLIENT_ID) into the static frontend build.
- If
-
If tests or build FAIL → report and stop. Do not push broken code.
-
Check for uncommitted changes:
git status -
If no changes → report "nothing to ship" and exit
Remote mode (multi-user shared graph): when config.yaml graph.mode: remote, step 0 above
reads the prior verification status from the graph Task node (authoritative), NEVER from the
local .tl/status.json (a per-clone cache — another developer may have verified the work, or yours
may be stale). After a successful push (end of Step 5/6), release your claim-lock:
node nacl-core/scripts/claim-task.mjs release --task <id> --dev "$NACL_DEVELOPER_ID" (run the
emitted Cypher via mcp__neo4j__write-cypher). Local mode is unchanged. See
nacl-tl-core/references/remote-mode-coordination.md.
Step 2: DETERMINE GIT STRATEGY
current_branch = git rev-parse --abbrev-ref HEAD
base_branch = config.yaml → git.main_branch (default: "main")
strategy = config.yaml → git.strategy (default: "feature-branch")
| Situation | Action |
|---|---|
Already on a feature/topic branch (current_branch != base_branch) | STAY here. Commit and push to this branch. |
On base_branch AND strategy == direct | Stay on base branch, push directly. |
On base_branch AND strategy == feature-branch | MUST create a new branch before committing. See naming below. |
IMPORTANT: If on a feature branch, ALWAYS commit there — regardless of whether
the change "matches" the branch name. If user wants it on main → /nacl-tl-hotfix.
NOTE (conductor-driven invocations): When this skill is invoked under
/nacl-tl-conductor, the feature branch is pre-created by the conductor and may
host multiple UCs in sequence. Do not require the branch name to match the UC ID —
a mismatch is expected and must not halt the ship.
Branch naming (when creating a new branch)
| Source | Branch name | Example |
|---|---|---|
| UC identifier provided | feature/UC028 | /nacl-tl-ship UC028 |
| FR identifier provided | feature/FR-001 | /nacl-tl-ship --feature FR-001 |
| Bare commit message | feature/[slug] | "fix: add lecture breadcrumb" → feature/fix-add-lecture-breadcrumb |
| Auto-composed message | feature/[slug] | Auto: "fix: cast lectureId" → feature/fix-cast-lectureid |
Slugification — branch.sh slug is the single authority (reproduces the historical
tr | sed | cut pipeline; equivalence pinned by nacl-core/scripts/branch.test.sh):
slug=$(bash nacl-core/scripts/branch.sh slug "$message")
git checkout -b "feature/${slug}" "$base_branch"
There is NO scenario where strategy == "feature-branch" and you commit directly to base_branch. If you cannot derive a branch name — ask the user. Do NOT fall back to base_branch.
Step 2.5: BASE-BRANCH GUARD (mandatory)
Run this BEFORE every git commit. Non-negotiable. The guard logic is the single
authority in nacl-core/scripts/branch.sh (equivalence pinned by nacl-core/scripts/branch.test.sh):
exit 1 prints FATAL … and blocks the commit, exit 0 prints GUARD OK ….
# base_branch and strategy were read from config.yaml in Step 1
bash nacl-core/scripts/branch.sh guard "$(git rev-parse --abbrev-ref HEAD)" "$base_branch" "$strategy"
If FATAL → go back to Step 2, create the branch. Do NOT skip or override this check.
Step 3: COMMIT
-
Pre-commit check: Verify you ran Step 2.5 and are NOT on base_branch with feature-branch strategy. If you are — STOP, go back to Step 2.
-
Stage relevant files (smart staging — exclude .env, node_modules, dist/, .tl/qa-screenshots/):
git add [relevant files] -
Compose commit message. Priority chain:
IF user provided a message in invocation → use it IF .tl/changelog.md has entries since last commit → compose from latest entries IF neither → auto-compose from git diff:- If
--deployflag is set anddeploy.staging.skip_ci == truein config.yaml: append[skip ci]to the first line of the commit message. This prevents GitHub Actions (both CI and deploy workflows) from running.
- Read `git diff --cached --stat` for changed files - Read `git diff --cached` for actual changes (first ~100 lines) - Compose: "fix/feat/refactor: [summary of changes]" - List changed files in bodyExample auto-composed message:
fix: switch to Gemini 2.5 Flash models + add prompt cache - backend/src/config/env.ts: updated model names - backend/src/services/interview-agent.service.ts: added in-memory cache Co-Authored-By: Claude <noreply@anthropic.com>Fix-traceability trailer (when this ship authors a fix commit). If the work being committed is a fix produced by
/nacl-tl-fix— the message startsfix:and the latest.tl/changelog.mdentry (or a handed-off Step 8 report) carries aLevel:andDecision:line — append to the commit body, ABOVECo-Authored-By:(same placement as theGoal-run-id:trailer):Fix-level: <L0|L1|L2|L3-spec-gap> Fix-decision: <DEC-NNN[, ...]|none>This is the deterministic link
/nacl-tl-release's type-aware pre-merge gate reads to verify a fix PR by its Decision node instead of a Task node. Normally/nacl-tl-fixPhase B already wrote it on the code-fix commit; this covers only the case where ship authors the commit (--auto-ship/ uncommitted changes). Omit both lines for a feature / non-fix commit. - If
-
Commit:
git commit -m "$(cat <<'EOF' [message] EOF )"
Step 4: PUSH
git push [-u origin feature/UC028] # feature-branch
# or
git push # direct
If push fails (reject, conflict):
- Pull and rebase:
git pull --rebase - If merge conflict → report to user, do not force-push
- Retry push after rebase
Post-push check: If strategy == "feature-branch" and you just pushed to base_branch — STOP. Something went wrong. Report the error to the user immediately.
Step 5: CREATE MERGE REQUEST / PR (feature-branch only)
If git_strategy is feature-branch:
Verification gate before PR creation:
- If prior task status (from Step 1.0) was PASS → create PR normally
- If prior task status was UNVERIFIED and user confirmed override in Step 1.0:
→ create PR with
**Verification status:** UNVERIFIEDnote in body - If prior task status was BLOCKED and user confirmed override in Step 1.0:
→ create PR with
**Verification status:** BLOCKED (user override)in body - If prior task status was REGRESSION → DO NOT create PR; report SHIP INCOMPLETE — REGRESSION
- If no status found (backward-compat) → create PR normally
Create Pull Request on GitHub:
gh pr create \
--title "feat: UC-028 Funnel event tracking" \
--body "$(cat <<'EOF'
## Summary
- POST /api/analytics/event with idempotent dedup
- FunnelEvent entity + migration
- Integration hooks across 7 existing pages
## Test Plan
- [x] 34 unit tests passing
- [ ] E2E verification via /nacl-tl-verify
**Verification status:** PASS
Generated with Claude Code
EOF
)" \
--base [git_base_branch]
Step 5.5: DEPLOY (config-driven, only with --deploy flag)
This step only runs when --deploy flag is provided.
Verification gate before deploy:
- If prior task status (from Step 1.0) was PASS → proceed with deploy.
- If prior task status was UNVERIFIED (operator-confirmed ship in Step 1.0) →
auto-deploy is refused. This skill emits an advisory:
Auto-deploy disabled — upstream status is UNVERIFIED. Run /nacl-tl-deploy --staging separately as an explicit deploy override.The PR is created and the report ends with theSHIP APPLIED — UNVERIFIEDheadline.--deploydoes NOT chain into deploy under unverified upstream. - If prior task status was BLOCKED (operator-confirmed ship) → same as UNVERIFIED: no auto-deploy, separate explicit operator action required.
- If prior task status was REGRESSION →
SHIP INCOMPLETE — REGRESSION; no PR; no deploy. - If upstream status was unknown → never reaches Step 5.5 (Step 1.0 already halted).
- Deploy never bypasses verification. Local tests passing does not substitute.
Read deploy strategy from config.yaml:
current_branch = git rev-parse --abbrev-ref HEAD
main_branch = config.yaml → git.main_branch (default: "main")
IF current_branch == main_branch:
SKIP — production deploys always go through CI pipeline.
Report: "Production deploy happens via GitHub Actions after push to main."
ELSE (feature/staging branch):
method = config.yaml → deploy.staging.method (default: "github-actions")
IF method == "direct":
script = config.yaml → deploy.staging.script
IF script exists:
Run: bash [script]
Report result (success/failure, duration)
ELSE:
Report error: "deploy.staging.script not found"
ELSE IF method == "github-actions":
Report: "Deploy will happen via GitHub Actions pipeline."
(Optionally monitor with /nacl-tl-deploy --staging)
ELSE:
Report error: "Unknown deploy method: [method]"
The skill does NOT know deployment specifics (SSH hosts, rsync paths, PM2 commands).
All of that lives in the project's deploy script referenced by deploy.staging.script.
The skill only resolves the config and executes the script.
Step 6: YOUGILE UPDATE
If config.yaml → yougile is configured:
-
Move task to DevDone column:
update_task(taskId, columnId: config.yougile.columns.dev_done) -
Post commit summary to task chat:
send_task_message(taskId, message: " 🚀 Shipped to repository Branch: feature/UC028 (PR #42) Commit: abc1234 Files: 12 changed, 450 insertions, 30 deletions Tests: 34 passing Build: OK Next: /nacl-tl-verify UC028 (bare fix, no UC id → /nacl-tl-release --pr 42 to merge) ")
If YouGile not configured → skip, just report locally.
Output
Per-task status table is the first block of every report — the headline summarizes
the aggregated status, then a Verification status: line surfaces the per-task value
that was consumed (PASS / UNVERIFIED / BLOCKED / NO_INFRA / RUNNER_BROKEN /
REGRESSION) so the reader sees the per-task status at a glance. Present to user
(in their language):
Next-step invariant: the Next step: block lists only concrete /nacl-...
commands (with current, real flags) — never prose. The block always names the
skill that performs the next action. When no UC/FR id is available (e.g. a bare
bug-fix), omit the id argument; do not substitute a prose description like
"review and merge the PR". See the Next-step resolution table below for how to
fill it.
Without --deploy (PASS case):
═══════════════════════════════════════════════
SHIP COMPLETE
═══════════════════════════════════════════════
UC-028: Funnel Event Tracking
Verification status: PASS
Git:
Branch: feature/UC028
Commit: abc1234
PR: #42 (https://github.com/org/repo/pull/42)
Push: origin (GitHub) — OK
Tests: 34 passing
Build: OK
YouGile: task moved to DevDone, summary posted
Next step:
/nacl-tl-verify UC028 — verify implementation (E2E/code)
/nacl-tl-release --pr 42 — merge PR #42 into main (after verify)
═══════════════════════════════════════════════
UNVERIFIED case (user confirmed override):
═══════════════════════════════════════════════
SHIP APPLIED — UNVERIFIED
═══════════════════════════════════════════════
UC-028: Funnel Event Tracking
Verification status: UNVERIFIED (user override — shipped without test coverage)
WARNING: No test exercises the change. Ship confirmed by user.
...
═══════════════════════════════════════════════
With --deploy (PASS — auto-deploy chained):
═══════════════════════════════════════════════
SHIP COMPLETE — DEPLOYED (direct)
═══════════════════════════════════════════════
UC-028: Funnel Event Tracking
Verification status: PASS
Git:
Branch: feature/UC028
Commit: abc1234 [skip ci]
PR: #42
Deploy (staging):
Method: direct (via deploy/staging-direct.sh)
Result: OK
Duration: 47s
Tests: 34 passing
Build: OK
YouGile: task moved to DevDone, summary posted
Next step:
/nacl-tl-verify UC028 — verify implementation (E2E/code)
/nacl-tl-release --pr 42 — merge PR #42 into main (after verify)
═══════════════════════════════════════════════
With --deploy (UNVERIFIED — auto-deploy refused):
═══════════════════════════════════════════════
SHIP APPLIED — UNVERIFIED (auto-deploy refused)
═══════════════════════════════════════════════
UC-028: Funnel Event Tracking
Verification status: UNVERIFIED (operator override at Step 1.0)
Git:
Branch: feature/UC028
Commit: abc1234
PR: #42 (annotated: SHIP APPLIED — UNVERIFIED)
Deploy (staging):
Method: SKIPPED — upstream UNVERIFIED. --deploy does not chain under
non-PASS status. Run /nacl-tl-deploy --staging as a separate
explicit operator action if you accept the risk.
Tests: 34 passing
Build: OK
YouGile: task moved to DevDone with UNVERIFIED note
Next step:
/nacl-tl-deploy --staging — explicit operator deploy (separate skill)
/nacl-tl-verify UC028 — restore verified status before re-shipping
═══════════════════════════════════════════════
Bare bug-fix (PASS, no UC id) — feature-branch:
When ship is invoked with a bare commit message (no UC/FR id) and upstream status
is already PASS (e.g. shipped after /nacl-tl-fix, which ran a RED→GREEN regression
test), the only remaining action is the merge. Name the merge skill — do NOT emit
prose like "Review and merge the PR":
═══════════════════════════════════════════════
SHIP COMPLETE
═══════════════════════════════════════════════
fix: <commit message>
Verification status: PASS
Git:
Branch: fix/<slug>
Commit: abc1234
PR: #5 (https://github.com/org/repo/pull/5)
Base: main-v2
Push: origin (GitHub) — OK
Tests: 28 passing
YouGile: not configured
Next step:
/nacl-tl-release --pr 5 — merge PR #5 into main-v2
(/nacl-tl-verify — optional: E2E-verify the fix before merge)
═══════════════════════════════════════════════
Headline selection (the only authoritative classifier is the consumed
Status: line — see Step 1.0):
SHIP COMPLETE
— upstream PASS, no skip flag, no health/CI failure.
SHIP COMPLETE — DEPLOYED (direct)
— PASS + --deploy succeeded.
SHIP APPLIED — UNVERIFIED
— upstream UNVERIFIED or BLOCKED with operator override; PR annotated;
auto-deploy refused regardless of --deploy.
SHIP HALTED — UNVERIFIED (upstream status unknown)
— no .tl/status.json AND no Task node in graph (P1 / P5).
SHIP HALTED — UNVERIFIED
— operator declined the unverified-ship prompt at Step 1.0.
SHIP HALTED — BLOCKED
— operator declined the BLOCKED-ship prompt at Step 1.0.
SHIP HALTED — NO_INFRA
— declared workspace command missing (P2).
SHIP HALTED — RUNNER_BROKEN
— runner cannot be exercised.
SHIP INCOMPLETE — REGRESSION
— upstream REGRESSION; no PR, no deploy.
Next-step resolution
Fill the Next step: block from the data this skill already has — strategy,
pr_number + resolved base_branch (git.main_branch), the consumed verification
status, and whether a UC/FR id was provided. <base> = resolved base_branch;
<N> = the PR number just created/found. The block always names a skill.
| Strategy | Verification status | Next step: block (named skills, in order) |
|---|---|---|
| feature-branch | PASS, UC/FR id present | /nacl-tl-verify <id> — verify implementation; then /nacl-tl-release --pr <N> — merge PR # into <base> |
| feature-branch | PASS, no id (bare fix) | /nacl-tl-release --pr <N> — merge PR # into <base>; (/nacl-tl-verify — optional E2E before merge) |
| feature-branch | UNVERIFIED / BLOCKED (operator override) | /nacl-tl-deploy --staging — explicit operator deploy; /nacl-tl-verify <id> — restore verified status before re-shipping |
| direct | PASS | /nacl-tl-deploy — monitor CI/deploy (no PR to merge) |
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 27
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Item type
- skill
- Key
nacl-tl-ship- Source
- github.com/itsalt/nacl