rules/ship-workflow.md

Ship Workflow

Source of truth cho slash command /ship. Khi muốn sửa hành vi của /ship, edit file này — KHÔNG sửa .claude/commands/ship.md.

/ship chạy pipeline document → commit → push → re-index xuyên qua mọi git repo trong workspace. Yêu cầu user xác nhận đúng 1 lần ở đầu, sau đó chạy hết một mạch không hỏi lại.


Phase 1 — Plan (auto, BEFORE asking the user)

Phát hiện workspace động — KHÔNG hardcode tên repo hay branch.

Quan trọng — không dùng shell loop: viết for repo in ...; do ...; done triggers permission prompt MỚI mỗi biến thể (harness coi mỗi loop khác nhau là command riêng) + có thể đụng zsh reserved variable (status, path, signals đều là read-only — dùng st, p, sig thay thế nếu phải dùng shell). Thay vào đó: gọi từng command riêng, parallel hoá qua nhiều Bash tool calls trong CÙNG message — pre-allowed patterns đã được add vào .claude/settings.local.json.

  1. Liệt kê các git repo: 1 command duy nhất find . -maxdepth 3 -name .git -type d 2>/dev/null | sed 's|/.git$||' | sed 's|^\./||' | sort (depth 3 đủ cho monorepo + nested repos).
  2. Cho mỗi repo phát hiện được, gửi 2-3 Bash tool calls SONG SONG trong 1 message:
    • git -C <abs-path> status --short
    • git -C <abs-path> rev-parse --abbrev-ref HEAD
    • git -C <abs-path> diff --stat HEAD (chỉ nếu status có dirty file)
  3. Bỏ qua repo sạch — không có dirty file thì skip hoàn toàn.
  4. Cho mỗi repo dirty được index bởi GitNexus:
    • mcp__gitnexus__detect_changes({ repo: <name>, scope: "unstaged" })
    • Nếu có symbol HIGH/CRITICAL trong affected_processes → chạy mcp__gitnexus__impact upstream cho symbol đó. Nếu impact trả về HIGH/CRITICAL → abort /ship và bắt user xác nhận thủ công, KHÔNG dùng single-confirm gate.
  5. Đọc diff để draft:
    • Commit message cho mỗi repo, format Conventional Commits (feat(scope):, fix(scope):, docs(scope):, …). Body ≤72 cols, motivation + change. Không Claude/AI attribution.
    • Docs delta (chỉ áp dụng cho repo chứa docs/progress/):
      • Append row mới vào bảng Timeline ở docs/progress/changelog.md (date = today YYYY-MM-DD, văn phong khớp các row gần nhất — Việt-first, kỹ thuật chi tiết).
      • Append sub-section ✅ DONE (YYYY-MM-DD) vào docs/progress/features.md ở Phase/Epic phù hợp (đoán bằng cách grep tên feature trong file).
      • Nếu bug fix trên cùng feature trong ngày → thêm row mới cùng date thay vì rewrite row cũ.

Không cần update docs/progress/testing.md mặc định — nó là snapshot thủ công, chỉ user tự chạy /test-coverage mới đụng.


Phase 2 — Single confirmation

Hiển thị 1 block preview ngắn gọn (KHÔNG kèm full diff — diff đã có ở git diff):

🚢 SHIP PLAN

<repo-name> (branch: <current-branch>)
  files: <count>
  risk:  <LOW/MEDIUM/HIGH from gitnexus, hoặc N/A nếu repo không index>
  msg:   <subject line>

<repo-name> (branch: <current-branch>)
  files: <count>
  …

Docs (sẽ append):
  - docs/progress/changelog.md  (1 row mới, date YYYY-MM-DD)
  - docs/progress/features.md   (1 sub-section)

Pre-push hooks: <liệt kê từ .husky/.lefthook/package.json scripts của mỗi repo>
GitNexus re-index: fan-out background trong <N> repo

Sau đó hỏi đúng 1 câu:

Proceed? (yes / yes-fg / no / edit-msg / skip-docs)

Phản hồi Hành vi
yes Chạy Phase 3 → 4 trong background subagent — main loop free, có notification khi xong
yes-fg Chạy Phase 3 inline (foreground) — user xem từng step, main loop bận tới khi xong
no Abort, không thay đổi gì
edit-msg User chỉnh tay 1+ commit message → quay lại preview → confirm lại
skip-docs Bỏ qua bước update changelog/features.md, các bước khác chạy bình thường

Sau yes, default: spawn 1 general-purpose Task agent với run_in_background: true rồi return ngay về main loop. Lý do:

  • User đang chờ → muốn làm việc khác trong lúc commit + push + reindex chạy. Background subagent giải phóng main loop.
  • Subagent kế thừa permission allowlist của parent → không bounce-back per-command (miễn là pattern đã add vào .claude/settings.local.json).
  • Khi subagent xong, harness gửi task-notification về main loop → mình report cho user lúc đó.

Spawn pattern:

Agent({
  description: "Ship pipeline (background)",
  subagent_type: "general-purpose",
  run_in_background: true,
  prompt: "<entire Phase 3 instructions copied verbatim from this rule file, with the concrete plan from Phase 1: list of repos, branches, commit messages, docs delta>"
})

Sau khi spawn → trả 1 line "🚢 Ship đang chạy nền (task ID: XXX) — em sẽ báo khi xong" rồi sẵn sàng nhận task khác.

Khi nào KHÔNG dùng background: nếu user gõ yes-fg (foreground) thay vì yes ở Phase 2 — chạy thẳng inline để user theo dõi từng step. Mặc định luôn background.

Bên trong subagent (hoặc inline khi foreground), chạy tuần tự. TUYỆT ĐỐI không prompt user giữa chừng. Không retry tự động — fail bước nào thì stop, viết exact error vào output file. CHỈ spawn 1 agent — không nest.

3a — Update docs (skip nếu user chọn skip-docs)

Edit 2 file ở repo chứa docs/progress/:

  • docs/progress/changelog.md — insert row mới ở đầu bảng Timeline.
  • docs/progress/features.md — append sub-section dưới Phase/Epic phù hợp.

Stage 2 file đó: git -C <docs-repo> add docs/progress/changelog.md docs/progress/features.md.

3b — Commit từng repo dirty

Với mỗi repo dirty (theo thứ tự bất kỳ — không có repo nào bắt buộc đi trước):

git -C <repo> add -A
git -C <repo> commit -m "<conventional-commits message>"

Multi-line body dùng HEREDOC. Pre-commit hook trong .claude/hooks/check-progress-docs.sh sẽ tự chạy — để nó chạy, không dùng --no-verify. Nếu hook fail → fix nguyên nhân, re-stage, commit MỚI (không amend commit đã có).

3c — Push từng repo

git -C <repo> push

Push branch hiện tại của mỗi repo — không hardcode main/feat/.... Pre-push hook (lint / test / build) chạy tự động; nếu hook fail thì push abort, report exact stderr cho user, các repo chưa push thì giữ nguyên (không revert commit đã tạo — user tự quyết tiếp).

Thứ tự push gợi ý (không bắt buộc): repo có pre-push nặng nhất push sau cùng để nếu nó fail thì các repo còn lại đã safely landed. Phát hiện hook nặng bằng cách đọc .husky/pre-push hoặc lefthook.yml của repo. Nếu không chắc thì cứ push theo thứ tự alphabet.

3d — GitNexus re-index

⚠️ Sequential, KHÔNG parallel — mỗi npx gitnexus analyze cần lock .gitnexus/relations.csv trong workspace root. Khi spawn 3 background ( cd <repo> && npx gitnexus analyze ) & cùng lúc, các process đụng cùng lock → 2/3 fail (ENOENT relations.csv, VECTOR extension load failed) — chỉ task cuối cùng survive ghi. Bug đã quan sát trong /ship 2026-05-05.

Chạy tuần tự, một repo xong mới sang repo kế tiếp:

cd <repo-1> && npx gitnexus analyze
cd <repo-2> && npx gitnexus analyze
cd <repo-3> && npx gitnexus analyze

Mỗi job 1-30s tuỳ size repo. Subagent đợi tất cả xong rồi mới ghi SHIP_RESULT_END. Phase 4 đọc kết quả từ output file.


Phase 4 — Report

Foreground: in bảng tóm tắt ngay sau Phase 3. Background: khi nhận <task-notification status="completed">, đọc output file của subagent và in bảng cùng nội dung. Nếu subagent fail giữa chừng, output file chứa exact error → relay nguyên văn cho user, list rõ repo nào đã commit/push thành công và repo nào dừng giữa chừng.

In 1 bảng tóm tắt:

✅ Shipped

| Repo            | Branch         | Commit  | Push | GitNexus     |
|-----------------|----------------|---------|------|--------------|
| <repo-name>     | <branch>       | abc1234 | ✓    | reindexing   |
| <repo-name>     | <branch>       | def5678 | ✓    | reindexing   |

Đính kèm test count nếu pre-push hook chạy test (vd: web vitest 173/173, api jest 1596/1598).


Hard rules

Rule Tại sao
One confirmation only ở cuối Phase 2. Sau đó không hỏi lại bất cứ thứ gì cho tới khi xong. User chạy /ship chính là để khỏi phải confirm từng bước
Không destructive ops ngay cả khi user đã yes: cấm --force, --force-with-lease, reset --hard, branch -D, rebase -i, commit --amend (commit đã push). Nếu cần phục hồi từ hook fail mà phải dùng những lệnh này → stop và hỏi user. Single-confirm chỉ cover happy path, không cover thao tác phá huỷ
Không retry khi fail. Surface exact stderr. Retry mù che đậy bug
Không switch branch. Push branch nào đang checkout. Tránh push nhầm branch
Không hardcode tên repo/branch. Phát hiện động qua find -name .git + rev-parse. Workflow phải work cho mọi project, không chỉ booking-system hiện tại
Không add Claude/AI attribution vào commit message. Đã disable global ở ~/.claude/settings.json, không add lại

Cách tweak

Sửa trực tiếp file này. Thay đổi có hiệu lực ngay lần /ship kế tiếp — không cần reload Claude Code, không cần restart MCP.

Examples:

  • Đổi format preview Phase 2 → sửa block ASCII trong Phase 2.
  • Thêm step mới (vd: post-push notification Slack) → chèn 3e sau 3d, mô tả command và rule "background, không đợi".
  • Tắt GitNexus re-index → xoá Phase 3d.
  • Thêm response option mới ở confirm gate (vd: dry-run) → thêm row vào bảng Phase 2.