@chris1807/claude-kit 2.2.3 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/package.json +1 -1
- package/templates/commands/cherry-pick.md +6 -4
- package/templates/commands/deploy-release.md +6 -4
- package/templates/commands/implement.md +185 -20
- package/templates/commands/promote.md +6 -4
- package/templates/commands/rework.md +170 -12
- package/templates/infrastructure/CLAUDE-WORKFLOW.md +46 -4
package/README.md
CHANGED
|
@@ -279,6 +279,7 @@ Claude automatically:
|
|
|
279
279
|
7. Generates a UAT checklist from acceptance criteria
|
|
280
280
|
8. **Pauses for you to manually test**
|
|
281
281
|
9. Creates the PR after you confirm
|
|
282
|
+
10. **Attaches any hand-run deployment script** to the work item, with the run command in the attachment comment, so `/promote`, `/cherry-pick`, and `/deploy-release` list it for every environment. Data fixes are planned as run-once migrations the app applies by itself wherever the project has that mechanism; a hand-run script is the fallback and the plan says why
|
|
282
283
|
|
|
283
284
|
**Implementing a whole Feature:** point `/implement` at a Feature work item and it implements the child User Stories in waves driven by the `Custom.Order` field — stories with the same order value are implemented in parallel (one agent per story, each in an isolated git worktree), and the next order value starts only after the previous wave is merged and green. Everything lands on one `feature/` branch with a single review, UAT pass, and PR that links every story.
|
|
284
285
|
|
|
@@ -494,15 +495,18 @@ git checkout main
|
|
|
494
495
|
```
|
|
495
496
|
|
|
496
497
|
Claude will:
|
|
497
|
-
1.
|
|
498
|
-
2.
|
|
499
|
-
3.
|
|
498
|
+
1. Set up the ticket's own git worktree next to your project (`../your-project-AB1234`). Every later step runs there, so other Claude sessions in the same repo aren't disturbed
|
|
499
|
+
2. Read the work item from Azure DevOps (extracts type, title, acceptance criteria)
|
|
500
|
+
3. **Summarize the work item** and ask you to confirm understanding. You can add context or corrections here
|
|
500
501
|
4. Explore the codebase and **present an implementation plan** showing which files will be created, modified, or deleted
|
|
501
502
|
5. **Wait for your approval** before writing any code
|
|
502
|
-
6.
|
|
503
|
+
6. Create the branch (e.g., `story/AB#1234-user-can-view-history`) and implement using backend and/or frontend agents
|
|
503
504
|
7. Run all quality checks (build, lint, tests, review)
|
|
504
505
|
8. Generate a UAT checklist and **pause for you to manually test**
|
|
505
506
|
9. After you confirm "testing passed", create a PR targeting `main`, close the child Task with its hours, and move the work item to `Code Review`
|
|
507
|
+
10. Remove the worktree once the branch is pushed. It's kept, with a note, if anything is uncommitted or unpushed. `/rework` recreates it from the branch
|
|
508
|
+
|
|
509
|
+
Say `/implement AB#1234 work in place` to skip the worktree and work in the current folder, as before.
|
|
506
510
|
|
|
507
511
|
The PR merges into `main`. **That merge deploys nothing** — `main` is the compare branch. The work reaches Dev when `main` is promoted:
|
|
508
512
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chris1807/claude-kit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Claude Code starter kit for Azure DevOps teams — agents, hooks, MCP servers, slash commands, and end-to-end work item → PR → release → deploy workflow automation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -52,14 +52,16 @@ Show them with the table above, before the confirmation:
|
|
|
52
52
|
```
|
|
53
53
|
📜 Scripts to run with this promotion:
|
|
54
54
|
|
|
55
|
-
| Work Item | Script |
|
|
56
|
-
|
|
57
|
-
| AB#1234 | 2026-09-11_add_delivery_index.sql |
|
|
58
|
-
| AB#1240 | backfill-tenant-flags.ps1 |
|
|
55
|
+
| Work Item | Script | How to run |
|
|
56
|
+
|-----------|--------|------------|
|
|
57
|
+
| AB#1234 | 2026-09-11_add_delivery_index.sql | sqlcmd -S <server> -d Compass -i 2026-09-11_add_delivery_index.sql |
|
|
58
|
+
| AB#1240 | backfill-tenant-flags.ps1 | — |
|
|
59
59
|
|
|
60
60
|
Download them from the work items. This command does not run them.
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
`How to run` is the attachment's comment (`attributes.comment` on the relation) — `/implement` and `/rework` write the run command there when they attach a script. Show `—` when it is empty.
|
|
64
|
+
|
|
63
65
|
If nothing carries an attachment, say `No deployment scripts attached` on one line and move on.
|
|
64
66
|
|
|
65
67
|
**Reported, never enforced.** A script does not block the promotion and does not change the gate check: the attachment may already have been run, may be a reference copy, may not be a script at all. Listing it is the whole job — the person deploying decides.
|
|
@@ -93,14 +93,16 @@ Show them with the gate check, before Step 4's confirmation:
|
|
|
93
93
|
```
|
|
94
94
|
📜 Scripts to run with this promotion:
|
|
95
95
|
|
|
96
|
-
| Work Item | Script |
|
|
97
|
-
|
|
98
|
-
| AB#1234 | 2026-09-11_add_delivery_index.sql |
|
|
99
|
-
| AB#1240 | backfill-tenant-flags.ps1 |
|
|
96
|
+
| Work Item | Script | How to run |
|
|
97
|
+
|-----------|--------|------------|
|
|
98
|
+
| AB#1234 | 2026-09-11_add_delivery_index.sql | sqlcmd -S <server> -d Compass -i 2026-09-11_add_delivery_index.sql |
|
|
99
|
+
| AB#1240 | backfill-tenant-flags.ps1 | — |
|
|
100
100
|
|
|
101
101
|
Download them from the work items. This command does not run them.
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
`How to run` is the attachment's comment (`attributes.comment` on the relation) — `/implement` and `/rework` write the run command there when they attach a script. Show `—` when it is empty.
|
|
105
|
+
|
|
104
106
|
If nothing carries an attachment, say `No deployment scripts attached` on one line and move on.
|
|
105
107
|
|
|
106
108
|
**Reported, never enforced.** A script does not block the promotion and does not change the gate check: the attachment may already have been run, may be a reference copy, may not be a script at all. Listing it is the whole job — the person deploying decides.
|
|
@@ -10,11 +10,121 @@ This command **can** orchestrate its analysis phases with the `Workflow` tool, b
|
|
|
10
10
|
When recommending a default in the Step 2 prompt, suggest **yes** for multi-subsystem / multi-AC work and **no** for trivial changes.
|
|
11
11
|
|
|
12
12
|
The remaining rules apply whenever Ultracode is in use:
|
|
13
|
-
- **Never fan out an interactive gate or a write.** Every user prompt (Steps 2, 3-approval, 8-decisions, 9) and every git / work-item mutation (Steps 4, 5, 10) stays in the **main loop**. Workflow agents here are **read-only analysts** — they use MCP read tools, `Read`, and `Grep`, and return structured findings. They do not write code, create/close work items, switch branches, or ask the user anything.
|
|
13
|
+
- **Never fan out an interactive gate or a write.** Every user prompt (Steps 2, 3-approval, 8-decisions, 9) and every git / work-item mutation (Steps 0, 4, 5, 10, 11) stays in the **main loop**. Workflow agents here are **read-only analysts** — they use MCP read tools, `Read`, and `Grep`, and return structured findings. They do not write code, create/close work items, switch branches, or ask the user anything.
|
|
14
|
+
- **Give every Workflow agent the ticket's worktree path** (Step 0) — that is where the code under analysis lives, not the folder the session started in.
|
|
14
15
|
- **Stay in the loop between phases** — one short workflow per phase, read its results, present/await the user, then continue.
|
|
15
16
|
|
|
16
17
|
If the `Workflow` tool is unavailable, run each phase sequentially in the main loop — the output is identical, just slower.
|
|
17
18
|
|
|
19
|
+
## Step 0: Set Up the Ticket's Workspace
|
|
20
|
+
|
|
21
|
+
Developers run several Claude Code sessions against the same repo at once, one per ticket. If every session `git checkout`s in the folder it started in, they clobber each other's working tree. So each ticket gets its own **git worktree** in a sibling folder, `../{repo}-AB{id}` (e.g. `../CSIPay-AB5373`), and every later step runs there. The folder the session started in is never checked out, committed to, or edited.
|
|
22
|
+
|
|
23
|
+
This step runs **first** — it needs only the ticket id — and before any git or file change. It is setup, not a gate: it prompts only where a case below says so.
|
|
24
|
+
|
|
25
|
+
**Opt-out.** If the user says **"work in place"** or **"no worktree"** — in the invocation (`/implement 1234 work in place`; strip the phrase before reading the id) or at any point before Step 4 — skip this step, skip Step 11, and ignore every worktree note below: the command runs exactly as it did before worktrees, branching in the current folder with `BASE_BRANCH` captured by Step 4's in-place rule. If Step 0 has already run, give back the worktree it made (it has no branch and no changes yet): `git worktree unlock "{WT}"`, then `git worktree remove "{WT}"`, then carry on in place.
|
|
26
|
+
|
|
27
|
+
Shell variables do not survive between Bash calls. Resolve each value once, then write the **literal absolute paths and branch names** into every later command.
|
|
28
|
+
|
|
29
|
+
### 0a. Resolve the repo and fetch
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
git worktree list --porcelain | sed -n '1s/^worktree //p' # MAIN — the main checkout
|
|
33
|
+
git fetch --prune origin
|
|
34
|
+
git worktree prune
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- **`MAIN`** is the first entry of `git worktree list` — the main checkout, even when the session started in a subfolder or inside another ticket's worktree (`--show-toplevel` would return that worktree, and the new one would land at `CSIPay-AB5375-AB5380`).
|
|
38
|
+
- **`REPO`** is `basename` of `MAIN`; **`WT`** is `{dirname of MAIN}/{REPO}-AB{id}` — always the absolute path.
|
|
39
|
+
- **`--prune`** matters: without it a remote branch deleted after its PR merged still looks alive locally, and 0c's "deleted on the remote" check never fires.
|
|
40
|
+
- **`git worktree prune`** forgets worktrees whose folders were deleted by hand, so a stale leftover can be recreated cleanly.
|
|
41
|
+
|
|
42
|
+
### 0b. Pick `BASE_BRANCH` — the branch this ticket is cut from and its PR targets
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git symbolic-ref --quiet --short HEAD # CURRENT — empty on a detached HEAD
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- **`CURRENT` is empty, or starts with a ticket prefix** — `feature/`, `story/`, `bugfix/`, `hotfix/`, `work/`, `cherry-pick/`, `release/`, `revert/` → **don't use it.** This folder was left on another ticket's branch; cutting from it would put that ticket's commits in this PR and aim the PR at the wrong branch. Use the PR target the project's CLAUDE.md names (e.g. CSIPay: "PRs always target `main`"). If CLAUDE.md doesn't name one, ask:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
This folder is on {CURRENT}, another ticket's branch. Which branch should AB#{id}
|
|
52
|
+
be cut from, and its PR target? (suggested: {origin's default branch — `git symbolic-ref --short refs/remotes/origin/HEAD`})
|
|
53
|
+
```
|
|
54
|
+
- **Otherwise** `BASE_BRANCH` is `CURRENT` — the same rule as before worktrees (start on `prod` for a production hot fix, and so on).
|
|
55
|
+
|
|
56
|
+
`origin/{BASE_BRANCH}` must exist — the worktree is cut from the remote branch, not from the local one, which may be behind or carry unpushed commits. If it doesn't, say so and ask.
|
|
57
|
+
|
|
58
|
+
### 0c. Tidy other tickets' worktrees
|
|
59
|
+
|
|
60
|
+
Look at every **other** worktree in `git worktree list --porcelain` whose folder is named `{REPO}-AB*`. Never touch `MAIN`, this ticket's `WT`, a worktree on this ticket's branch (`*/AB#{id}-*` — 0d reuses it), or any folder outside that naming pattern.
|
|
61
|
+
|
|
62
|
+
- **Locked → skip it.** A lock means another session is working in it right now; 0d locks every worktree it hands out.
|
|
63
|
+
- **Otherwise remove it only if all three hold:**
|
|
64
|
+
1. **Nothing uncommitted** — `git -C "{path}" status --porcelain` prints nothing.
|
|
65
|
+
2. **Nothing unpushed** — its branch has an upstream, and either `git -C "{path}" rev-list --count '@{u}..HEAD'` prints `0`, or the upstream is **gone** (`git -C "{path}" for-each-ref --format='%(upstream:track)' "refs/heads/{branch}"` prints `[gone]` — its PR merged and the remote branch was deleted). For a gone upstream, also confirm nothing was committed after the last push: find the branch's pull request (`repo_pull_request`, by source branch, any status) and check its `lastMergeSourceCommit` equals `git -C "{path}" rev-parse HEAD`. A branch that was **never pushed** (no upstream), a detached HEAD, a HEAD that differs from what the PR merged, or no PR found — all fail this check.
|
|
66
|
+
3. **Merged, or deleted on the remote** — the upstream is `[gone]`, or `git -C "{MAIN}" merge-base --is-ancestor "{branch}" "origin/{BASE_BRANCH}"` succeeds.
|
|
67
|
+
|
|
68
|
+
Remove with `git -C "{MAIN}" worktree remove "{path}"`, then `git -C "{MAIN}" branch -D "{branch}"`. **Never `--force`, never `rm` the folder.** If a removal fails because another session removed it first, ignore that and move on.
|
|
69
|
+
- **Anything else stays.** List what was kept in one line, with the reason — `Kept: ../CSIPay-AB5301 (2 uncommitted files), ../CSIPay-AB5310 (locked — another session, or abandoned: git worktree unlock "{path}")`.
|
|
70
|
+
|
|
71
|
+
### 0d. Create or reuse the ticket's worktree
|
|
72
|
+
|
|
73
|
+
Take the first case that matches:
|
|
74
|
+
|
|
75
|
+
1. **A worktree is already on this ticket's branch** — a `branch refs/heads/*/AB#{id}-*` line in `git worktree list --porcelain` → use that worktree as `WT`, wherever it is (a hand-made folder, or even `MAIN` itself). Don't create a second one, and don't fail because the branch is checked out elsewhere.
|
|
76
|
+
2. **`WT` is already a worktree** (an earlier run stopped before Step 4 and left it detached) → reuse it.
|
|
77
|
+
3. **`WT` exists but git doesn't know it** (not in the list even after 0a's prune) → stop and ask the user to delete or rename it. Its contents aren't in git, so there is no way to tell whether anything in it is worth keeping.
|
|
78
|
+
4. **A local branch `*/AB#{id}-*` exists** but isn't checked out anywhere → `git -C "{MAIN}" worktree add "{WT}" "{branch}"` (this is the old "if the branch already exists, switch to it" rule). If more than one branch matches, list them and ask which.
|
|
79
|
+
5. **Otherwise** create it **detached** at the base — the branch itself is created in Step 4, after the plan is approved, same as before worktrees:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git -C "{MAIN}" worktree add --detach "{WT}" "origin/{BASE_BRANCH}"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then **lock it** so another session's 0c leaves it alone — a new, clean worktree with no commits would otherwise pass every tidy check:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git -C "{MAIN}" worktree lock --reason "AB#{id} — /implement in progress" "{WT}"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Skip the lock when `WT` is `MAIN` (the main checkout can't be locked, and 0c never touches it). If the worktree is **already locked**, another session may still be working this ticket. Say so in one line — `AB#{id}'s worktree is locked ({reason}) — if another session is still on this ticket, both will edit the same files` — and carry on in it.
|
|
92
|
+
|
|
93
|
+
### 0e. Copy the local files git doesn't carry
|
|
94
|
+
|
|
95
|
+
A new worktree has only tracked files. Copy in `.claude/settings.local.json` (the user's local permission rules) and every gitignored `.env.local` / `*.local.json`. Copy nothing that already exists in the worktree:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
cd "{MAIN}" && {
|
|
99
|
+
[ -f .claude/settings.local.json ] && echo .claude/settings.local.json
|
|
100
|
+
git ls-files --others --ignored --exclude-standard -- ':(glob)**/.env.local' ':(glob)**/*.local.json' ':(exclude,glob)**/node_modules/**'
|
|
101
|
+
} | sort -u | while IFS= read -r f; do
|
|
102
|
+
[ -e "{WT}/$f" ] || { mkdir -p "{WT}/$(dirname "$f")" && cp "$f" "{WT}/$f"; }
|
|
103
|
+
done
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**None of these may ever be committed.** The `.env.local` / `*.local.json` files are gitignored by construction. `.claude/settings.local.json` may not be, so if `git -C "{WT}" check-ignore -q .claude/settings.local.json` fails, append `.claude/settings.local.json` to `{MAIN}/.git/info/exclude`. That file is local and shared by every worktree of the repo, so the exclude covers them all and is itself never committed.
|
|
107
|
+
|
|
108
|
+
`node_modules` is not copied. Step 5 installs it, only when the plan touches the frontend.
|
|
109
|
+
|
|
110
|
+
### 0f. Work only in the ticket's worktree
|
|
111
|
+
|
|
112
|
+
- **Every later step runs in `WT`** — exploration, implementation, build, tests, lint, commits, review. Claude Code can reset the shell to the session's folder between calls, so run git as `git -C "{WT}" …`, run everything else as `cd "{WT}" && …`, and use absolute paths for `Read`/`Edit`/`Write`.
|
|
113
|
+
- **Pass `WT` to every subagent and Workflow agent**, in so many words: `The code for AB#{id} is in {WT}. Read, edit, build, and test only there — never in {MAIN}.`
|
|
114
|
+
- **Diff against `origin/{BASE_BRANCH}`**, not `{BASE_BRANCH}`, wherever a later step compares with the base. The worktree was cut from the remote branch, and the local one may be behind.
|
|
115
|
+
- **Quote every branch name** — the `#` in `story/AB#5373-…` starts a comment or a glob in some shells.
|
|
116
|
+
- `WT` is outside the folder the session started in, so Claude Code asks before the first edit there. Tell the user once, in the report below.
|
|
117
|
+
- Sessions sharing a repo still share **local ports and the Dev database**. If this ticket's dev server or integration tests collide with another session's, run them one at a time.
|
|
118
|
+
|
|
119
|
+
Report the workspace in one short block, then go on to Step 1:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
Workspace for AB#{id}: {WT}
|
|
123
|
+
{new, detached at origin/{BASE_BRANCH} | reused, on {branch}} PR target: {BASE_BRANCH}
|
|
124
|
+
Tidied: {removed worktrees, or "nothing to tidy"} Kept: {kept worktrees + reason, or omit}
|
|
125
|
+
Edits there need approval once — run `/add-dir {WT}` to allow them for this session.
|
|
126
|
+
```
|
|
127
|
+
|
|
18
128
|
## Step 1: Read the Work Item
|
|
19
129
|
|
|
20
130
|
Read the work item from Azure DevOps via MCP. Extract:
|
|
@@ -98,7 +208,7 @@ Each question below is about a cost that is cheap now and expensive once the cod
|
|
|
98
208
|
| 2 | **Is each piece in the right layer?** Domain / Application / Infrastructure / API boundaries hold — no business rules in a controller, no Mongo or EF types in Domain, no HTTP concepts below API. | An `IMongoCollection<T>` parameter on a Domain method |
|
|
99
209
|
| 3 | **What is the simplest plan that still meets every AC?** State it, then adopt it or say in one line why the heavier one is needed. | An interface with exactly one implementation, added "for testability" |
|
|
100
210
|
| 4 | **What else touches the files being modified?** Name the callers. A change to a shared contract, DTO, or response shape is a breaking change until proven otherwise. | Editing a shared DTO with no note about its other consumers |
|
|
101
|
-
| 5 | **What happens to data that already exists?** New required fields, schema changes, and backfills each need an answer for rows written before this change. | A non-nullable field added with no default and no backfill |
|
|
211
|
+
| 5 | **What happens to data that already exists?** New required fields, schema changes, and backfills each need an answer for rows written before this change — and the answer should run itself: the project's run-once migration mechanism if it has one, not a script someone has to remember in every environment (see **Deployment Scripts** in CLAUDE.md). | A non-nullable field added with no default and no backfill; a `backfill.js` to run by hand on test, staging and prod when the API already runs one-time migrations at startup |
|
|
102
212
|
| 6 | **How does it fail?** Partial failure, concurrent callers, a retried request. Is the operation idempotent, and does it need to be? | A multi-step write with no story for a crash between steps |
|
|
103
213
|
| 7 | **Does it hold at real data volume?** Queries inside loops, unbounded result sets, a missing index, a list endpoint with no paging. | A `foreach` over accounts issuing one query each |
|
|
104
214
|
| 8 | **Is it safe?** Authorization on every new endpoint, and **no sensitive field** (TIN, SSN, EIN, TaxId, BankAccountNumber, RoutingNumber, any `Encrypted*`) read, logged, returned, or projected — see the sensitive-data rule in CLAUDE.md. | A new endpoint returning a whole entity because it was convenient |
|
|
@@ -131,6 +241,10 @@ Present the plan to the user:
|
|
|
131
241
|
### Files to Delete (if any)
|
|
132
242
|
- `path/to/old/file.cs` — {why it's being removed}
|
|
133
243
|
|
|
244
|
+
### Data Changes (if any)
|
|
245
|
+
- {what changes in existing data} — **automated** by {the project's run-once mechanism}: `{file}`
|
|
246
|
+
- {what changes in existing data} — **hand-run** `{script path}`, because {which Deployment Scripts reason applies}. Attached to the work item when the PR goes up.
|
|
247
|
+
|
|
134
248
|
### Unit Tests
|
|
135
249
|
- `path/to/new.tests.cs` — covers {scenario 1}, {scenario 2}, {edge case}
|
|
136
250
|
- `path/to/existing.tests.cs` — adds cases for {new behavior}
|
|
@@ -157,7 +271,7 @@ Approve this plan? (yes / no / suggest changes)
|
|
|
157
271
|
|
|
158
272
|
Only create the branch after the plan is approved.
|
|
159
273
|
|
|
160
|
-
Capture the
|
|
274
|
+
Capture the PR target — do NOT hardcode any branch name. **In a worktree** (the default), `BASE_BRANCH` is the branch Step 0b picked. **Working in place**, capture the current branch:
|
|
161
275
|
|
|
162
276
|
```bash
|
|
163
277
|
BASE_BRANCH=$(git symbolic-ref --short HEAD)
|
|
@@ -177,12 +291,17 @@ Construct the branch name as `{prefix}AB#{id}-{sanitized-title}`:
|
|
|
177
291
|
- Sanitize the title: lowercase, replace non-alphanumeric characters (except hyphens) with hyphens, collapse consecutive hyphens, truncate to 50 characters, trim leading/trailing hyphens
|
|
178
292
|
- Example: Feature AB#1234 "Add Payment History Export" → `feature/AB#1234-add-payment-history-export`
|
|
179
293
|
|
|
180
|
-
Create and switch to the branch:
|
|
294
|
+
Create and switch to the branch. **In a worktree** — Step 0 left it detached at `origin/{BASE_BRANCH}`, so the branch starts there, with no upstream until Step 10 pushes it:
|
|
181
295
|
```bash
|
|
182
|
-
git
|
|
296
|
+
git -C "{WT}" switch -c "<branch-name>"
|
|
183
297
|
```
|
|
184
298
|
|
|
185
|
-
|
|
299
|
+
**Working in place:**
|
|
300
|
+
```bash
|
|
301
|
+
git checkout -b "<branch-name>"
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
If the branch already exists, switch to it with `git checkout "<branch-name>"` instead of failing. In a worktree, Step 0d has already put the worktree on an existing branch for this ticket — there is nothing to create.
|
|
186
305
|
|
|
187
306
|
Remember the `BASE_BRANCH` — you will need it for the PR step.
|
|
188
307
|
|
|
@@ -285,7 +404,9 @@ Remember the Task ID. Step 10 closes it.
|
|
|
285
404
|
|
|
286
405
|
## Step 5: Implement
|
|
287
406
|
|
|
288
|
-
|
|
407
|
+
**In a worktree**, install frontend dependencies first — only if the approved plan touches the frontend. A new worktree has no `node_modules`, so run `npm install` in its client folder (`cd "{WT}/{client folder}" && npm install`). A backend-only plan skips the install; `dotnet build` restores its own packages. Steps 6–7 then skip the uninstalled client folder and report it as `frontend skipped — untouched by this change, not installed in the worktree` rather than failing on it.
|
|
408
|
+
|
|
409
|
+
1. **Implement** using backend and/or frontend agents according to the approved plan — each agent is told to work only in `WT` (Step 0f)
|
|
289
410
|
2. **Write the unit tests** listed in the plan's "Unit Tests" section alongside the implementation — not after
|
|
290
411
|
3. **Generate mockup** if there are UI changes
|
|
291
412
|
|
|
@@ -383,21 +504,33 @@ Wait for the user's response before proceeding. Do NOT create a PR until confirm
|
|
|
383
504
|
|
|
384
505
|
## Step 10: Push, Create PR, and Update Work Item
|
|
385
506
|
|
|
386
|
-
1. Push the branch: `git push -u origin HEAD`
|
|
507
|
+
1. Push the branch: `git push -u origin HEAD` (in a worktree: `git -C "{WT}" push -u origin HEAD`)
|
|
387
508
|
2. Create a PR via Azure DevOps MCP:
|
|
388
509
|
- **sourceRefName**: `refs/heads/{branch-name}`
|
|
389
510
|
- **targetRefName**: `refs/heads/{BASE_BRANCH}` (the branch captured in Step 4)
|
|
390
511
|
- **title**: `AB#{id}: {work item title}`
|
|
391
512
|
- **labels**: `["hotfix"]` if the work item type is Hot Fix
|
|
392
513
|
3. Link the PR to the work item via `wit_work_item_link_write` (action `link_to_pull_request`)
|
|
393
|
-
4. **
|
|
394
|
-
5. **
|
|
514
|
+
4. **Attach deployment scripts** — see "Attaching Deployment Scripts" below.
|
|
515
|
+
5. **Close related Tasks and log hours** — see "Closing Related Tasks" below.
|
|
516
|
+
6. **Move the work item to `Code Review`** via `wit_work_item_write` (action `update`):
|
|
395
517
|
- **path**: `/fields/System.State`
|
|
396
518
|
- **value**: `Code Review`
|
|
397
519
|
|
|
398
520
|
If the project's process template does not have a `Code Review` state (the update call returns an invalid-state error), fall back in this order: `Resolved` → `In Review` → leave the current state and warn the user that the state could not be advanced automatically. Do not silently swallow the error.
|
|
521
|
+
7. **Remove the ticket's worktree** — Step 11, once everything above is done.
|
|
399
522
|
|
|
400
|
-
> **Only the Task ever gets closed — never the parent.** The child Task is closed here, at PR creation (step
|
|
523
|
+
> **Only the Task ever gets closed — never the parent.** The child Task is closed here, at PR creation (step 5 above). When the PR is later completed/merged, do **not** enable Azure DevOps's "Complete associated work items" option: it transitions *every* linked work item, including the parent this PR is linked to. The parent User Story or Bug stays in `Code Review`: merging this PR into `main` deploys nothing, and promoting to `dev` deploys the code but **leaves the state alone** — the developer moves it to `Ready for Testing` once they have checked it on Dev. From there `/promote` and `/deploy-release` advance it to `Testing` → `Staging` → `Deployed` across `test → staging → prod` (see **Work Item States ↔ Environments** in CLAUDE.md).
|
|
524
|
+
|
|
525
|
+
### Attaching Deployment Scripts
|
|
526
|
+
|
|
527
|
+
A script that has to be run by hand — a SQL migration, a data backfill, a rename — is useless to the next environment if it only lives in the repo: `/promote`, `/cherry-pick` and `/deploy-release` find scripts on the **work item**, never in the diff. So every one this change adds or modifies is attached to the work item now, not left for someone to remember.
|
|
528
|
+
|
|
529
|
+
List the files the PR adds or modifies (`git diff --name-only --diff-filter=AM {BASE_BRANCH}...HEAD`) and pick out the ones a person runs by hand, once per environment. The test: if the UAT checklist, the PR, or your report tells someone to run it, it's one of these. App code, tests, build tooling and migrations the app or pipeline applies by itself are not. Each one should already be in the approved plan's **Data Changes** section with its reason for not being automated. A hand-run script that isn't there still gets attached, but say plainly that it wasn't in the plan.
|
|
530
|
+
|
|
531
|
+
Attach each one per **Deployment Scripts** in CLAUDE.md — upload through the REST API (the MCP server can only download), link it as an `AttachedFile` whose comment holds the repo path and the exact run command with the environment as a placeholder, and replace a stale same-name attachment rather than adding a second copy. Never attach a file with a connection string, key, password, or sensitive field value in it.
|
|
532
|
+
|
|
533
|
+
Report it on one line per script — `📜 Attached to AB#{id}: {file} — {how to run}` — or `No deployment scripts in this change` when there are none. A failed upload does not undo the PR; say `⚠️ NOT attached: {file} → AB#{id}` with the error and keep going.
|
|
401
534
|
|
|
402
535
|
### Closing Related Tasks
|
|
403
536
|
|
|
@@ -464,6 +597,33 @@ Confirm with a summary line per task: `Closed AB#xxxx — {hours}h logged (estim
|
|
|
464
597
|
|
|
465
598
|
**The Task closes now, at PR creation — not at merge.** The work is done and the hours are known; waiting until merge means the hours get logged days later, or not at all.
|
|
466
599
|
|
|
600
|
+
## Step 11: Remove the Ticket's Worktree
|
|
601
|
+
|
|
602
|
+
Skip this step when working in place.
|
|
603
|
+
|
|
604
|
+
This runs **last**: after the PR exists and every Step 10 prompt, the Task-closing ones included, is answered. A failed push therefore never loses work, and the attachment and diff steps still have the worktree to read from. The branch is safe on origin now, and `/rework AB#{id}` recreates the worktree from it.
|
|
605
|
+
|
|
606
|
+
1. **Unlock it** — this session is done with it: `git -C "{MAIN}" worktree unlock "{WT}"`. Unlock even if step 3 ends up keeping it, so a later Step 0c can tidy it once it's clean, pushed, and merged.
|
|
607
|
+
2. **Check it's safe to remove:**
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
git -C "{WT}" status --porcelain # must print nothing
|
|
611
|
+
git -C "{WT}" rev-list --count '@{u}..HEAD' # must print 0
|
|
612
|
+
```
|
|
613
|
+
3. **Both pass** → remove the worktree, then the local branch:
|
|
614
|
+
|
|
615
|
+
```bash
|
|
616
|
+
git -C "{MAIN}" worktree remove "{WT}"
|
|
617
|
+
git -C "{MAIN}" branch -D "{branch}"
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
Report: `Removed {WT} and local branch {branch} — the branch is on origin; /rework AB#{id} brings it back.`
|
|
621
|
+
|
|
622
|
+
**Either fails** → keep it, and say exactly what's left: the `git -C "{WT}" status --short` lines, or the unpushed commits (`git -C "{WT}" log --oneline '@{u}..HEAD'`). **Never `--force`, never `rm` the folder.**
|
|
623
|
+
4. **Never remove a worktree the session is standing in.**
|
|
624
|
+
- If the folder this session started in is `WT` or inside it, don't remove it — that would pull the folder out from under the session. Print the command for the user to run from another folder instead: `git -C "{MAIN}" worktree remove "{WT}" && git -C "{MAIN}" branch -D "{branch}"`.
|
|
625
|
+
- If `WT` is `MAIN` (Step 0d reused a branch already checked out there), there is nothing to remove.
|
|
626
|
+
|
|
467
627
|
## Feature Workflow (ordered story waves)
|
|
468
628
|
|
|
469
629
|
Used when the work item passed to `/implement` is a **Feature**. The Feature's child User Stories are implemented in **waves** driven by the custom order field (`Custom.Order`): all stories sharing the same order value run **in parallel** (one implementation agent each), and waves run sequentially in ascending order — wave 2 starts only after wave 1 is merged, built, and green, so later stories can build on earlier ones.
|
|
@@ -511,7 +671,7 @@ Include the Step 2 reasoning-effort recommendation (a multi-story Feature is alm
|
|
|
511
671
|
|
|
512
672
|
### F3: Create the Feature Branch
|
|
513
673
|
|
|
514
|
-
Capture `BASE_BRANCH` and create `feature/AB#{feature-id}-{sanitized-title}` per Step 4 rules. All story work merges into this branch; the single PR in F7 targets `BASE_BRANCH`.
|
|
674
|
+
Capture `BASE_BRANCH` and create `feature/AB#{feature-id}-{sanitized-title}` per Step 4 rules. Step 0 ran with the **Feature's** id, so the Feature has its own ticket worktree, `../{repo}-AB{feature-id}`, and the feature branch is created there. All story work merges into this branch; the single PR in F7 targets `BASE_BRANCH`.
|
|
515
675
|
|
|
516
676
|
### F4: Execute Waves
|
|
517
677
|
|
|
@@ -532,16 +692,16 @@ For each wave in ascending order:
|
|
|
532
692
|
|
|
533
693
|
Create these? (yes / edit N / skip N / skip all)
|
|
534
694
|
```
|
|
535
|
-
- **Single-story wave** → implement directly on the feature branch in the main loop (Step 5).
|
|
536
|
-
- **Multi-story wave** → isolate each story in its own worktree so parallel agents never clobber each other:
|
|
695
|
+
- **Single-story wave** → implement directly on the feature branch, in the Feature's worktree, in the main loop (Step 5).
|
|
696
|
+
- **Multi-story wave** → isolate each story in its own worktree so parallel agents never clobber each other. These branch from the feature worktree's branch and live in the scratchpad, not beside the repo, so Step 0c's tidy never touches them:
|
|
537
697
|
|
|
538
698
|
```bash
|
|
539
|
-
git worktree add "{scratchpad}/wt-{story-id}" -b "story/AB#{story-id}-{sanitized-title}" "{feature-branch}"
|
|
699
|
+
git -C "{WT}" worktree add "{scratchpad}/wt-{story-id}" -b "story/AB#{story-id}-{sanitized-title}" "{feature-branch}"
|
|
540
700
|
```
|
|
541
701
|
|
|
542
702
|
Launch **one implementation agent per story, all in a single message** so they run concurrently (`backend`/`frontend`/`general-purpose` per the approved plan; if a story needs both backend and frontend work, give one agent the whole story rather than splitting it). Each agent's prompt must include: the approved plan for its story, the story's full AC, its worktree path, and these rules — work **only** inside your worktree, implement the plan plus its unit tests, run the tests you added, commit to the story branch, and report what you changed. Agents never push, never create PRs, never touch work items, and never ask the user anything.
|
|
543
|
-
- **Merge back (main loop):** merge each story branch into the feature branch (`git merge --no-ff`), resolving conflicts yourself using both stories' plans as the guide. Then `git worktree remove` and delete the story branch. Merge in `Custom.Order`-then-ID order so conflict resolution is deterministic.
|
|
544
|
-
3. **Wave gate:** run Step 6 (build validation) and Step 7.1–7.2 (full test suite + lint) on the merged feature branch. Fix failures before starting the next wave — the next wave branches from this merged, green state.
|
|
703
|
+
- **Merge back (main loop):** merge each story branch into the feature branch in the Feature's worktree (`git -C "{WT}" merge --no-ff "story/…"`), resolving conflicts yourself using both stories' plans as the guide. Then `git worktree remove` and delete the story branch. Merge in `Custom.Order`-then-ID order so conflict resolution is deterministic.
|
|
704
|
+
3. **Wave gate:** run Step 6 (build validation) and Step 7.1–7.2 (full test suite + lint) on the merged feature branch, in the Feature's worktree. Fix failures before starting the next wave — the next wave branches from this merged, green state.
|
|
545
705
|
|
|
546
706
|
### F5: Feature-Level Quality and Review
|
|
547
707
|
|
|
@@ -559,6 +719,11 @@ Present **one combined UAT checklist grouped by story** (Step 9 rules). Wait for
|
|
|
559
719
|
|
|
560
720
|
1. Push the feature branch and create **one PR**: title `AB#{feature-id}: {feature title}`, source `feature/...`, target `BASE_BRANCH`.
|
|
561
721
|
2. Link the **Feature and every implemented story** to the PR.
|
|
562
|
-
3. Run **
|
|
563
|
-
4.
|
|
564
|
-
5.
|
|
722
|
+
3. Run **Attaching Deployment Scripts** (Step 10) over the whole feature diff. Each script goes on the **story** whose change needs it — never on the Feature, which promotions don't gate.
|
|
723
|
+
4. Run **Closing Related Tasks** (Step 10) once, covering the child Tasks of every implemented story — one combined table, then the usual per-task hour prompts. Every story that got a Task in F4 has one to close here; a story whose Task creation was skipped gets one created and closed now, as in Step 10.
|
|
724
|
+
5. Move each implemented story to `Code Review` (same fallback rules as Step 10). **Do not change the Feature's state** — the Feature is a parent container; it advances only when its child stories are verified/closed, not when the PR goes up for review.
|
|
725
|
+
6. The Step 10 closing rule applies unchanged: only child **Tasks** are ever closed — here at PR creation, never the stories and never the Feature. Don't enable "Complete associated work items" when the PR is merged; it would transition the stories and the Feature along with the Tasks.
|
|
726
|
+
|
|
727
|
+
### F8: Remove the Feature's Worktree
|
|
728
|
+
|
|
729
|
+
Run Step 11 on the Feature's worktree, `../{repo}-AB{feature-id}`, once F7 is done and its Task-closing prompts are answered. Same checks, same rules: remove it only if it's clean and fully pushed, otherwise keep it and say what's left, and never use `--force`. The per-story worktrees are already gone; F4 removes each one at merge-back.
|
|
@@ -98,14 +98,16 @@ Show them with the gate check, before the confirmation:
|
|
|
98
98
|
```
|
|
99
99
|
📜 Scripts to run with this promotion:
|
|
100
100
|
|
|
101
|
-
| Work Item | Script |
|
|
102
|
-
|
|
103
|
-
| AB#1234 | 2026-09-11_add_delivery_index.sql |
|
|
104
|
-
| AB#1240 | backfill-tenant-flags.ps1 |
|
|
101
|
+
| Work Item | Script | How to run |
|
|
102
|
+
|-----------|--------|------------|
|
|
103
|
+
| AB#1234 | 2026-09-11_add_delivery_index.sql | sqlcmd -S <server> -d Compass -i 2026-09-11_add_delivery_index.sql |
|
|
104
|
+
| AB#1240 | backfill-tenant-flags.ps1 | — |
|
|
105
105
|
|
|
106
106
|
Download them from the work items. This command does not run them.
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
`How to run` is the attachment's comment (`attributes.comment` on the relation) — `/implement` and `/rework` write the run command there when they attach a script. Show `—` when it is empty.
|
|
110
|
+
|
|
109
111
|
If nothing carries an attachment, say `No deployment scripts attached` on one line and move on.
|
|
110
112
|
|
|
111
113
|
**Reported, never enforced.** A script does not block the promotion and does not change the gate check: the attachment may already have been run, may be a reference copy, may not be a script at all. Listing it is the whole job — the person deploying decides.
|
|
@@ -12,7 +12,8 @@ The one exception is Step 2 (gathering feedback), which runs **before** the Step
|
|
|
12
12
|
When Ultracode is in use, these rules apply:
|
|
13
13
|
|
|
14
14
|
- **Fan out the read / analyze / verify work** — gathering feedback (Step 2), exploring the codebase (Step 4), and reviewing + verifying the diff and AC coverage (Steps 9–10) are run as `Workflow` scripts with one agent per independent unit (per comment, per subsystem, per review dimension, per acceptance criterion). Each agent returns **structured findings** via a `schema`; you synthesize the results in the main loop.
|
|
15
|
-
- **Never fan out an interactive gate or a write.** Every user prompt (Steps 3, 4-approval, 5, 10-decisions, 11) and every work-item or git mutation (Step 5 create, Step 6 branch, Step 7 implement, Step 12 push/close) stays in the **main loop**. Workflow agents here are **read-only analysts** — they use MCP read tools, `Read`, and `Grep`, and they return data. They do not create or close work items, switch branches, write code, or ask the user anything.
|
|
15
|
+
- **Never fan out an interactive gate or a write.** Every user prompt (Steps 3, 4-approval, 5, 10-decisions, 11) and every work-item or git mutation (Step 0 worktree, Step 5 create, Step 6 branch, Step 7 implement, Step 12 push/PR/close, Step 13 cleanup) stays in the **main loop**. Workflow agents here are **read-only analysts** — they use MCP read tools, `Read`, and `Grep`, and they return data. They do not create or close work items, switch branches, write code, or ask the user anything.
|
|
16
|
+
- **Give every Workflow agent the ticket's worktree path** (Step 0) — that is where the code under analysis lives, not the folder the session started in.
|
|
16
17
|
- **Stay in the loop between phases.** Run one `Workflow` per phase, read its results, present/await the user as the steps require, then launch the next phase's workflow. This is several short workflows in sequence — not one monolithic run that tries to swallow the approval gates.
|
|
17
18
|
- **Review uses the canonical find → adversarially-verify pipeline** (Step 10): fan out per dimension, then spawn skeptic verifiers per finding and drop findings the majority refute, so only confirmed issues reach the user.
|
|
18
19
|
|
|
@@ -20,7 +21,7 @@ If the `Workflow` tool is somehow unavailable, fall back to running each phase s
|
|
|
20
21
|
|
|
21
22
|
## Step 1: Find the Latest Pull Request
|
|
22
23
|
|
|
23
|
-
Handle `$ARGUMENTS` as either `1234` or `AB#1234` — strip the `AB#` prefix when calling the MCP API.
|
|
24
|
+
Handle `$ARGUMENTS` as either `1234` or `AB#1234` — strip the `AB#` prefix when calling the MCP API. If it also says "work in place" or "no worktree", strip that and remember it for Step 0.
|
|
24
25
|
|
|
25
26
|
Find the most recent PR linked to this work item:
|
|
26
27
|
|
|
@@ -28,7 +29,115 @@ Find the most recent PR linked to this work item:
|
|
|
28
29
|
2. For each linked PR, fetch its details via `repo_get_pull_request_by_id` and record the **creationDate**
|
|
29
30
|
3. Identify the **most recent PR** by creation date — this is the baseline for detecting new feedback
|
|
30
31
|
|
|
31
|
-
Save the PR's `creationDate` as `LAST_PR_DATE` — everything after this timestamp is new feedback.
|
|
32
|
+
Save the PR's `creationDate` as `LAST_PR_DATE` — everything after this timestamp is new feedback. Also save its **source branch** and **target branch** (`sourceRefName` / `targetRefName`, minus `refs/heads/`) — Step 0 needs both — and its **status** (`active`, `completed`, or `abandoned`). The status decides which branch the rework builds on (Step 0c) and whether Step 12 has to open a new PR.
|
|
33
|
+
|
|
34
|
+
## Step 0: Set Up the Ticket's Workspace
|
|
35
|
+
|
|
36
|
+
Developers run several Claude Code sessions against the same repo at once, one per ticket. If every session `git checkout`s in the folder it started in, they clobber each other's working tree. So each ticket gets its own **git worktree** in a sibling folder, `../{repo}-AB{id}` (e.g. `../CSIPay-AB5373`), and every later step runs there. The folder the session started in is never checked out, committed to, or edited.
|
|
37
|
+
|
|
38
|
+
It is Step 0 because it comes before anything touches the code. It runs **right after Step 1**, because it needs the branches of the PR Step 1 found, and Step 1 only reads Azure DevOps. It is setup, not a gate: it prompts only where a case below says so.
|
|
39
|
+
|
|
40
|
+
**Opt-out.** If the user says **"work in place"** or **"no worktree"** — in the invocation (`/rework 1234 work in place`; strip the phrase before reading the id) or at any point before Step 6 — skip this step, skip Step 13, and ignore every worktree note below: the command runs exactly as it did before worktrees, switching branches in the current folder in Step 6. If Step 0 has already run, give back the worktree it made (nothing has changed in it yet): `git worktree unlock "{WT}"`, then `git worktree remove "{WT}"`, then carry on in place.
|
|
41
|
+
|
|
42
|
+
Shell variables do not survive between Bash calls. Resolve each value once, then write the **literal absolute paths and branch names** into every later command. Below, `SOURCE` and `TARGET` are the latest PR's source and target branches from Step 1.
|
|
43
|
+
|
|
44
|
+
### 0a. Resolve the repo and fetch
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git worktree list --porcelain | sed -n '1s/^worktree //p' # MAIN — the main checkout
|
|
48
|
+
git fetch --prune origin
|
|
49
|
+
git worktree prune
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- **`MAIN`** is the first entry of `git worktree list` — the main checkout, even when the session started in a subfolder or inside another ticket's worktree (`--show-toplevel` would return that worktree, and the new one would land at `CSIPay-AB5375-AB5380`).
|
|
53
|
+
- **`REPO`** is `basename` of `MAIN`; **`WT`** is `{dirname of MAIN}/{REPO}-AB{id}` — always the absolute path.
|
|
54
|
+
- **`--prune`** matters: without it a branch deleted on the remote after its PR merged still looks alive locally, so 0c would never take the merged-branch fallback and 0b's "deleted on the remote" check would never fire.
|
|
55
|
+
- **`git worktree prune`** forgets worktrees whose folders were deleted by hand, so a stale leftover can be recreated cleanly.
|
|
56
|
+
|
|
57
|
+
### 0b. Tidy other tickets' worktrees
|
|
58
|
+
|
|
59
|
+
Look at every **other** worktree in `git worktree list --porcelain` whose folder is named `{REPO}-AB*`. Never touch `MAIN`, this ticket's `WT`, a worktree on `SOURCE` or on another `*/AB#{id}-*` branch (0c reuses it), or any folder outside that naming pattern.
|
|
60
|
+
|
|
61
|
+
- **Locked → skip it.** A lock means another session is working in it right now; 0c locks every worktree it hands out.
|
|
62
|
+
- **Otherwise remove it only if all three hold:**
|
|
63
|
+
1. **Nothing uncommitted** — `git -C "{path}" status --porcelain` prints nothing.
|
|
64
|
+
2. **Nothing unpushed** — its branch has an upstream, and either `git -C "{path}" rev-list --count '@{u}..HEAD'` prints `0`, or the upstream is **gone** (`git -C "{path}" for-each-ref --format='%(upstream:track)' "refs/heads/{branch}"` prints `[gone]` — its PR merged and the remote branch was deleted). For a gone upstream, also confirm nothing was committed after the last push: find the branch's pull request (`repo_pull_request`, by source branch, any status) and check its `lastMergeSourceCommit` equals `git -C "{path}" rev-parse HEAD`. A branch that was **never pushed** (no upstream), a detached HEAD, a HEAD that differs from what the PR merged, or no PR found — all fail this check.
|
|
65
|
+
3. **Merged, or deleted on the remote** — the upstream is `[gone]`, or `git -C "{MAIN}" merge-base --is-ancestor "{branch}" "origin/{TARGET}"` succeeds.
|
|
66
|
+
|
|
67
|
+
Remove with `git -C "{MAIN}" worktree remove "{path}"`, then `git -C "{MAIN}" branch -D "{branch}"`. **Never `--force`, never `rm` the folder.** If a removal fails because another session removed it first, ignore that and move on.
|
|
68
|
+
- **Anything else stays.** List what was kept in one line, with the reason — `Kept: ../CSIPay-AB5301 (2 uncommitted files), ../CSIPay-AB5310 (locked — another session, or abandoned: git worktree unlock "{path}")`.
|
|
69
|
+
|
|
70
|
+
### 0c. Put the worktree on the rework's branch
|
|
71
|
+
|
|
72
|
+
**First, pick `BRANCH`** — the branch this rework builds on:
|
|
73
|
+
|
|
74
|
+
- **The PR is `active` or `abandoned`, and `SOURCE` is still on origin** (`git -C "{MAIN}" rev-parse --verify --quiet "refs/remotes/origin/{SOURCE}"` succeeds) → `BRANCH` is `SOURCE`. The rework continues the unmerged work.
|
|
75
|
+
- **Otherwise** → `BRANCH` is a **new branch** from the PR's target. This covers a `completed` PR (merged), or `SOURCE` deleted. A completed PR's work is already in the target, so the rework starts there even if `SOURCE` still exists. Building on the merged branch would make the new PR show the old changes again after a squash merge. Name the new branch by `/implement` Step 4's convention (`{prefix}AB#{id}-{sanitized-title}`). If that name is still on origin (the merged branch was kept), append `-rework-{N}`, where `{N}` is the rework round Step 5 will use: existing `Rework AB#{id}` child Tasks + 1.
|
|
76
|
+
|
|
77
|
+
Then take the first case that matches:
|
|
78
|
+
|
|
79
|
+
1. **A worktree is already on `BRANCH`** — a `branch refs/heads/{BRANCH}` line in `git worktree list --porcelain` → use that worktree as `WT`, wherever it is (a hand-made folder, or even `MAIN` itself). Don't create a second one, and don't fail because the branch is checked out elsewhere. If `BRANCH` is `SOURCE`, bring it up to date with `git -C "{WT}" pull --ff-only`.
|
|
80
|
+
2. **`WT` is already a worktree, but on something else** (a detached HEAD, another branch) → show `git -C "{WT}" status -sb` and ask whether to switch it to `BRANCH` or stop.
|
|
81
|
+
3. **`WT` exists but git doesn't know it** (not in the list even after 0a's prune) → stop and ask the user to delete or rename it. Its contents aren't in git, so there is no way to tell whether anything in it is worth keeping.
|
|
82
|
+
4. **`BRANCH` is `SOURCE`** → check it out, tracking `origin/{SOURCE}`:
|
|
83
|
+
- **No local branch:**
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
git -C "{MAIN}" worktree add --track -b "{SOURCE}" "{WT}" "origin/{SOURCE}"
|
|
87
|
+
```
|
|
88
|
+
- **A local branch exists:** run `git -C "{MAIN}" worktree add "{WT}" "{SOURCE}"`, then `git -C "{WT}" branch --set-upstream-to "origin/{SOURCE}"`, then `git -C "{WT}" pull --ff-only`.
|
|
89
|
+
|
|
90
|
+
If `pull --ff-only` fails, the local branch and origin have diverged. Stop and show both sides (`git -C "{WT}" log --oneline --left-right '@{u}...HEAD'`). Never merge, rebase, or reset without asking.
|
|
91
|
+
5. **`BRANCH` is new** → create it from the PR's **target** — the rule from before worktrees, now inside the worktree:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
git -C "{MAIN}" worktree add --no-track -b "{BRANCH}" "{WT}" "origin/{TARGET}"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
It has no upstream until Step 12 pushes it with `-u`, and no PR until Step 12 opens one. If `-b` fails because a leftover local branch has that name, show what it holds that the target doesn't (`git -C "{MAIN}" log --oneline "origin/{TARGET}..{BRANCH}"`). Then ask whether to delete it (`git -C "{MAIN}" branch -D "{BRANCH}"`) and retry, or reuse it.
|
|
98
|
+
|
|
99
|
+
Then **lock it** so another session's 0b leaves it alone — a clean, fully pushed worktree would otherwise pass every tidy check the moment its PR merges:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
git -C "{MAIN}" worktree lock --reason "AB#{id} — /rework in progress" "{WT}"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Skip the lock when `WT` is `MAIN` (the main checkout can't be locked, and 0b never touches it). If the worktree is **already locked**, another session may still be working this ticket. Say so in one line — `AB#{id}'s worktree is locked ({reason}) — if another session is still on this ticket, both will edit the same files` — and carry on in it.
|
|
106
|
+
|
|
107
|
+
### 0d. Copy the local files git doesn't carry
|
|
108
|
+
|
|
109
|
+
A new worktree has only tracked files. Copy in `.claude/settings.local.json` (the user's local permission rules) and every gitignored `.env.local` / `*.local.json`. Copy nothing that already exists in the worktree:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
cd "{MAIN}" && {
|
|
113
|
+
[ -f .claude/settings.local.json ] && echo .claude/settings.local.json
|
|
114
|
+
git ls-files --others --ignored --exclude-standard -- ':(glob)**/.env.local' ':(glob)**/*.local.json' ':(exclude,glob)**/node_modules/**'
|
|
115
|
+
} | sort -u | while IFS= read -r f; do
|
|
116
|
+
[ -e "{WT}/$f" ] || { mkdir -p "{WT}/$(dirname "$f")" && cp "$f" "{WT}/$f"; }
|
|
117
|
+
done
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**None of these may ever be committed.** The `.env.local` / `*.local.json` files are gitignored by construction. `.claude/settings.local.json` may not be, so if `git -C "{WT}" check-ignore -q .claude/settings.local.json` fails, append `.claude/settings.local.json` to `{MAIN}/.git/info/exclude`. That file is local and shared by every worktree of the repo, so the exclude covers them all and is itself never committed.
|
|
121
|
+
|
|
122
|
+
`node_modules` is not copied. Step 7 installs it, only when the plan touches the frontend.
|
|
123
|
+
|
|
124
|
+
### 0e. Work only in the ticket's worktree
|
|
125
|
+
|
|
126
|
+
- **Every later step runs in `WT`** — exploration, implementation, build, tests, lint, commits, review. Claude Code can reset the shell to the session's folder between calls, so run git as `git -C "{WT}" …`, run everything else as `cd "{WT}" && …`, and use absolute paths for `Read`/`Edit`/`Write`.
|
|
127
|
+
- **Pass `WT` to every subagent and Workflow agent**, in so many words: `The code for AB#{id} is in {WT}. Read, edit, build, and test only there — never in {MAIN}.`
|
|
128
|
+
- **Diff against `origin/{TARGET}`**, not `{TARGET}`, wherever a later step compares with the PR's target — the local branch may be behind.
|
|
129
|
+
- **Quote every branch name** — the `#` in `story/AB#5373-…` starts a comment or a glob in some shells.
|
|
130
|
+
- `WT` is outside the folder the session started in, so Claude Code asks before the first edit there. Tell the user once, in the report below.
|
|
131
|
+
- Sessions sharing a repo still share **local ports and the Dev database**. If this ticket's dev server or integration tests collide with another session's, run them one at a time.
|
|
132
|
+
|
|
133
|
+
Report the workspace in one short block, then go on to Step 2:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Workspace for AB#{id}: {WT}
|
|
137
|
+
{reused, on {BRANCH} | new, on {SOURCE} from origin | new branch {BRANCH} from origin/{TARGET} — PR #{n} {was completed | lost its branch}}
|
|
138
|
+
Tidied: {removed worktrees, or "nothing to tidy"} Kept: {kept worktrees + reason, or omit}
|
|
139
|
+
Edits there need approval once — run `/add-dir {WT}` to allow them for this session.
|
|
140
|
+
```
|
|
32
141
|
|
|
33
142
|
## Step 2: Gather Rework Feedback
|
|
34
143
|
|
|
@@ -71,7 +180,8 @@ Present a summary of the rework feedback to the user. **Every feedback item must
|
|
|
71
180
|
```
|
|
72
181
|
## Rework for AB#{id}: {title}
|
|
73
182
|
|
|
74
|
-
**Last PR:** #{pr_id} (created {date})
|
|
183
|
+
**Last PR:** #{pr_id} (created {date}, {status})
|
|
184
|
+
{only if status is not active: "PR #{pr_id} is {status} — this rework goes up as a new PR."}
|
|
75
185
|
**New comments:** {count}
|
|
76
186
|
|
|
77
187
|
### Current Acceptance Criteria
|
|
@@ -123,7 +233,7 @@ Each question below is about a cost that is cheap now and expensive once the cod
|
|
|
123
233
|
| 2 | **Is each piece in the right layer?** Domain / Application / Infrastructure / API boundaries hold — no business rules in a controller, no Mongo or EF types in Domain, no HTTP concepts below API. | An `IMongoCollection<T>` parameter on a Domain method |
|
|
124
234
|
| 3 | **What is the simplest plan that still meets every AC?** State it, then adopt it or say in one line why the heavier one is needed. | An interface with exactly one implementation, added "for testability" |
|
|
125
235
|
| 4 | **What else touches the files being modified?** Name the callers. A change to a shared contract, DTO, or response shape is a breaking change until proven otherwise. | Editing a shared DTO with no note about its other consumers |
|
|
126
|
-
| 5 | **What happens to data that already exists?** New required fields, schema changes, and backfills each need an answer for rows written before this change. | A non-nullable field added with no default and no backfill |
|
|
236
|
+
| 5 | **What happens to data that already exists?** New required fields, schema changes, and backfills each need an answer for rows written before this change — and the answer should run itself: the project's run-once migration mechanism if it has one, not a script someone has to remember in every environment (see **Deployment Scripts** in CLAUDE.md). | A non-nullable field added with no default and no backfill; a `backfill.js` to run by hand on test, staging and prod when the API already runs one-time migrations at startup |
|
|
127
237
|
| 6 | **How does it fail?** Partial failure, concurrent callers, a retried request. Is the operation idempotent, and does it need to be? | A multi-step write with no story for a crash between steps |
|
|
128
238
|
| 7 | **Does it hold at real data volume?** Queries inside loops, unbounded result sets, a missing index, a list endpoint with no paging. | A `foreach` over accounts issuing one query each |
|
|
129
239
|
| 8 | **Is it safe?** Authorization on every new endpoint, and **no sensitive field** (TIN, SSN, EIN, TaxId, BankAccountNumber, RoutingNumber, any `Encrypted*`) read, logged, returned, or projected — see the sensitive-data rule in CLAUDE.md. | A new endpoint returning a whole entity because it was convenient |
|
|
@@ -158,6 +268,10 @@ Present the plan to the user:
|
|
|
158
268
|
### Files to Delete (if any)
|
|
159
269
|
- `path/to/old/file.cs` — {why it's being removed}
|
|
160
270
|
|
|
271
|
+
### Data Changes (if any)
|
|
272
|
+
- {what changes in existing data} — **automated** by {the project's run-once mechanism}: `{file}`
|
|
273
|
+
- {what changes in existing data} — **hand-run** `{script path}`, because {which Deployment Scripts reason applies}. Attached to the work item when the PR goes up.
|
|
274
|
+
|
|
161
275
|
### Unit Tests
|
|
162
276
|
- `path/to/new.tests.cs` — covers {scenario the rework adds or fixes}
|
|
163
277
|
- `path/to/existing.tests.cs` — updates assertions for {changed behavior}
|
|
@@ -275,13 +389,15 @@ If the user cancels, skip task creation and proceed — note "No rework task cre
|
|
|
275
389
|
|
|
276
390
|
## Step 6: Switch to Existing Branch
|
|
277
391
|
|
|
278
|
-
|
|
392
|
+
**In a worktree** (the default), there is nothing to switch. Step 0c has already put `WT` on the previous PR's branch and pulled it. If that PR was completed or its branch deleted, Step 0c put it on a new branch from the PR's target instead. Confirm with `git -C "{WT}" status -sb` and go on to "Move the Work Item to Active".
|
|
393
|
+
|
|
394
|
+
**Working in place**, if the PR is `active` or `abandoned`, the work item already has a branch from the previous PR. Switch to it:
|
|
279
395
|
|
|
280
396
|
1. Get the source branch name from the most recent PR
|
|
281
397
|
2. Switch to that branch: `git checkout <branch-name>`
|
|
282
398
|
3. Pull the latest: `git pull`
|
|
283
399
|
|
|
284
|
-
If the PR was completed
|
|
400
|
+
If the PR was completed, or its branch was deleted, create a new branch from the PR's target branch instead, following the same naming convention as `/implement` Step 4 (`git fetch --prune origin`, then `git checkout -b "<branch-name>" "origin/<target>"`). Don't build on a completed PR's branch even if it still exists. Add the `-rework-{N}` suffix when the name is still on origin. Step 0c explains both rules.
|
|
285
401
|
|
|
286
402
|
### Move the Work Item to Active
|
|
287
403
|
|
|
@@ -297,7 +413,9 @@ After the update, **read the work item back** (`wit_work_item`, action `get`) an
|
|
|
297
413
|
|
|
298
414
|
## Step 7: Implement
|
|
299
415
|
|
|
300
|
-
|
|
416
|
+
**In a worktree**, install frontend dependencies first — only if the approved plan touches the frontend. A new worktree has no `node_modules`, so run `npm install` in its client folder (`cd "{WT}/{client folder}" && npm install`). A backend-only plan skips the install; `dotnet build` restores its own packages. Steps 8–9 then skip the uninstalled client folder and report it as `frontend skipped — untouched by this rework, not installed in the worktree` rather than failing on it.
|
|
417
|
+
|
|
418
|
+
1. **Implement** the rework using backend and/or frontend agents according to the approved plan — each agent is told to work only in `WT` (Step 0e)
|
|
301
419
|
2. **Write the unit tests** listed in the plan's "Unit Tests" section alongside the implementation — not after. Include any regression test that would have caught the original issue
|
|
302
420
|
3. **Generate mockup** if there are UI changes
|
|
303
421
|
|
|
@@ -387,16 +505,29 @@ Wait for the user's response before proceeding. Do NOT push until confirmed.
|
|
|
387
505
|
|
|
388
506
|
## Step 12: Push and Update
|
|
389
507
|
|
|
390
|
-
1. Push the changes: `git push`
|
|
391
|
-
2. **
|
|
392
|
-
|
|
393
|
-
|
|
508
|
+
1. Push the changes: `git push` (in a worktree: `git -C "{WT}" push`). A **new branch** from Step 0c case 5, or from the in-place fallback in Step 6, has no upstream yet, so push it with `git push -u origin HEAD` (`git -C "{WT}" push -u origin HEAD`).
|
|
509
|
+
2. **Open a PR if none is active.** A push only reaches a reviewer through an active PR. Re-read the latest PR's status, since it may have changed during the rework:
|
|
510
|
+
- **Still `active`** → the push updated it. Nothing to create.
|
|
511
|
+
- **`completed` or `abandoned`, or the rework is on a new branch** → nothing is reviewing these commits yet. Create a PR the way `/implement` Step 10 does:
|
|
512
|
+
- **sourceRefName**: `refs/heads/{branch}` — the branch just pushed
|
|
513
|
+
- **targetRefName**: `refs/heads/{target}` — the latest PR's target
|
|
514
|
+
- **title**: `AB#{id}: {work item title} (rework round {N})`
|
|
515
|
+
- **description**: `Rework round {N} of AB#{id}. Follows PR #{old} ({status}).` Keep it to that one line; item 3 applies.
|
|
516
|
+
- **labels**: `["hotfix"]` if the work item type is Hot Fix
|
|
517
|
+
|
|
518
|
+
Link it to the work item via `wit_work_item_link_write` (action `link_to_pull_request`). Report `Opened PR #{new} → {target} (PR #{old} is {status})`. The next `/rework` finds this PR as the latest.
|
|
519
|
+
- **Completed during this rework** (it was `active` at Step 1, so the rework was built on the old branch) → open the new PR the same way, but warn first: if #{old} was squash-merged, the new PR shows its changes again. Offer to merge `origin/{target}` into the branch and push again before opening the PR, which clears them.
|
|
520
|
+
3. **Do not post a rework summary comment.** Do not add a summary of what changed to the work item Discussion (`wit_work_item_comment_write` (action `add`)) or as a PR thread. The pushed commits and the PR diff are the record of what changed — a prose summary duplicates them and clutters the work item. If the reviewer left specific PR comment threads, reply on those threads directly (that is what `/resolve-feedback` and `/fix-review` do); otherwise post nothing.
|
|
521
|
+
4. **Attach deployment scripts.** Run `/implement`'s **Attaching Deployment Scripts** step against this branch — diff it against the PR's target branch (`git diff --name-only --diff-filter=AM {target}...HEAD`), so a script the original PR added and never attached is caught too. A script this rework changed is already attached in its old form: replace that attachment, don't add a second copy — a promotion that lists both versions invites someone to run the stale one. Same rules and report lines as in `/implement` (see **Deployment Scripts** in CLAUDE.md).
|
|
522
|
+
5. **Close related Tasks and log hours** — see "Closing Related Tasks" below. This includes the rework Task created in Step 5 as well as any other child Tasks that became `Completed` as a result of this rework round.
|
|
523
|
+
6. **Move the work item back to `Code Review`** via `wit_work_item_write` (action `update`):
|
|
394
524
|
- **path**: `/fields/System.State`
|
|
395
525
|
- **value**: `Code Review`
|
|
396
526
|
|
|
397
527
|
Rework is triggered by reviewer feedback, so the item was likely in `Active` / `In Progress` / `Rework` while the fixes were being made. Pushing the rework hands it back to the reviewer, so it belongs in `Code Review` again.
|
|
398
528
|
|
|
399
529
|
If the project's process template does not have a `Code Review` state (the update call returns an invalid-state error), fall back in this order: `Resolved` → `In Review` → leave the current state and warn the user. Do not silently swallow the error.
|
|
530
|
+
7. **Remove the ticket's worktree** — Step 13, once everything above is done.
|
|
400
531
|
|
|
401
532
|
> **PR completion closes the Task only.** When the PR is later completed/merged, only the child **Task** may be closed — never the parent User Story or Bug. Azure DevOps's "Complete associated work items" option transitions *every* linked work item (including the parent the PR is linked to), so do **not** enable it when completing the PR. Close the child Task explicitly instead; the parent stays in `Code Review` until it is promoted through `dev → test → staging → prod` (see **Work Item States ↔ Environments** in CLAUDE.md).
|
|
402
533
|
|
|
@@ -459,3 +590,30 @@ Once the user has answered, update each task via `wit_work_item_write` (action `
|
|
|
459
590
|
- `System.State` → `Closed` (fall back to `Done` if the project's task template uses Agile; warn if neither is valid)
|
|
460
591
|
|
|
461
592
|
Confirm with a summary line per task: `Closed AB#xxxx — {hours}h logged`.
|
|
593
|
+
|
|
594
|
+
## Step 13: Remove the Ticket's Worktree
|
|
595
|
+
|
|
596
|
+
Skip this step when working in place.
|
|
597
|
+
|
|
598
|
+
This runs **last**: after the push and every Step 12 prompt, the Task-closing ones included, is answered. A failed push therefore never loses work, and the attachment and diff steps still have the worktree to read from. The branch is safe on origin now, and the next `/rework AB#{id}` recreates the worktree from it.
|
|
599
|
+
|
|
600
|
+
1. **Unlock it** — this session is done with it: `git -C "{MAIN}" worktree unlock "{WT}"`. Unlock even if step 3 ends up keeping it, so a later Step 0b can tidy it once it's clean, pushed, and merged.
|
|
601
|
+
2. **Check it's safe to remove:**
|
|
602
|
+
|
|
603
|
+
```bash
|
|
604
|
+
git -C "{WT}" status --porcelain # must print nothing
|
|
605
|
+
git -C "{WT}" rev-list --count '@{u}..HEAD' # must print 0
|
|
606
|
+
```
|
|
607
|
+
3. **Both pass** → remove the worktree, then the local branch:
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
git -C "{MAIN}" worktree remove "{WT}"
|
|
611
|
+
git -C "{MAIN}" branch -D "{branch}"
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Report: `Removed {WT} and local branch {branch} — the branch is on origin; /rework AB#{id} brings it back.`
|
|
615
|
+
|
|
616
|
+
**Either fails** → keep it, and say exactly what's left: the `git -C "{WT}" status --short` lines, or the unpushed commits (`git -C "{WT}" log --oneline '@{u}..HEAD'`). **Never `--force`, never `rm` the folder.**
|
|
617
|
+
4. **Never remove a worktree the session is standing in.**
|
|
618
|
+
- If the folder this session started in is `WT` or inside it, don't remove it — that would pull the folder out from under the session. Print the command for the user to run from another folder instead: `git -C "{MAIN}" worktree remove "{WT}" && git -C "{MAIN}" branch -D "{branch}"`.
|
|
619
|
+
- If `WT` is `MAIN` (Step 0c reused a branch already checked out there), there is nothing to remove.
|
|
@@ -97,7 +97,7 @@ Claude maintains persistent memory across sessions in `~/.claude/projects/.../me
|
|
|
97
97
|
### Development Workflow
|
|
98
98
|
|
|
99
99
|
1. **Pick a work item** from Azure DevOps (or describe what you need)
|
|
100
|
-
2. **Claude implements** using `/implement AB#<id>`
|
|
100
|
+
2. **Claude implements** using `/implement AB#<id>` — in the ticket's own worktree, on an auto-created branch
|
|
101
101
|
3. **Hooks guard** against secrets and bad patterns automatically
|
|
102
102
|
4. **PR merges** into `main` — the compare branch. **Nothing deploys yet.** The work item stays in `Code Review` until it is promoted
|
|
103
103
|
5. **Promote or release** — `/promote main dev` carries everything on `main` to Dev; `/create-release <N>` groups work items so `/deploy-release <N> test` → `staging` → `prod` can carry just those
|
|
@@ -107,6 +107,8 @@ Claude maintains persistent memory across sessions in `~/.claude/projects/.../me
|
|
|
107
107
|
9. **Track** work items via the Azure DevOps MCP server
|
|
108
108
|
10. **Learn** — Claude saves what worked for next time
|
|
109
109
|
|
|
110
|
+
**One worktree per ticket.** `/implement` and `/rework` never check out in the folder the session started in. Each ticket gets a sibling git worktree, `../{repo}-AB{id}` (e.g. `../CSIPay-AB5373`), where every build, test, and commit happens. It is removed once the branch is pushed, or kept with a note if anything is uncommitted or unpushed. That lets several sessions work different tickets in the same repo at once. Say **"work in place"** (or "no worktree") to opt a run out. Sessions sharing a repo still share **local ports and the Dev database** — two dev servers on one port, or two integration runs against Dev, will still collide.
|
|
111
|
+
|
|
110
112
|
### Story Points & Dev Ready Policy
|
|
111
113
|
|
|
112
114
|
These rules apply to **every** command or flow that creates or estimates work items:
|
|
@@ -185,10 +187,48 @@ How the commands use this table:
|
|
|
185
187
|
|
|
186
188
|
#### Deployment Scripts
|
|
187
189
|
|
|
188
|
-
A SQL migration
|
|
190
|
+
A SQL migration, a data backfill, a rename script — anything someone has to **run by hand** against an environment — is attached to the work item it belongs to, and it has to run in **every** environment that item's code lands in — test, then staging, then prod. The promotion carries the code, never the script.
|
|
189
191
|
|
|
190
192
|
`/promote`, `/cherry-pick` and `/deploy-release` therefore read the carried items' attachments (`wit_work_item` `action: get`, `expand: "Relations"`) and list any that look like something to run: an `AttachedFile` whose extension is **not** a document, image or video (`.docx .xlsx .csv .pdf .png .jpg .mp4` and friends). Everything else counts — the rule is loose on purpose, because a filename glanced at and dismissed costs seconds while a migration nobody ran costs an environment. The list appears before the confirmation, in the PR description, and again once the pipeline is green, which is when it actually has to be run.
|
|
191
193
|
|
|
194
|
+
**Automate first — a hand-run script is the fallback.** When a change needs existing data corrected, backfilled or renamed, Claude plans it through the project's own run-once migration mechanism if it has one: a seeder that records finished migrations (COMPASS's `DataSeeder` and its `dataMigrations` collection), EF Core migrations applied at startup, DbUp, a migration step the pipeline already runs. Those run by themselves on every environment's deploy, after the new code is live, with the app's own connection string — nothing to remember, nothing to attach. A hand-run script is right only when the change can't live there, and the plan names which reason applies:
|
|
195
|
+
|
|
196
|
+
- the project has no such mechanism — building one is its own work item, never folded into this one;
|
|
197
|
+
- the script creates the database or tenant the app would run in (provisioning);
|
|
198
|
+
- it touches something outside the app's own data stores (Azure resources, another system);
|
|
199
|
+
- a person has to check the result before it is applied.
|
|
200
|
+
|
|
201
|
+
**Claude always attaches the hand-run script it writes.** Committing it to the repo is not enough: the promotion commands look for scripts on the work item, never in the repo, so an unattached script is reported as `No deployment scripts attached` and goes unrun in staging or prod. Whenever Claude writes one — in `/implement`, `/rework`, or any other session — or tells someone to run one that isn't attached yet, it attaches it before finishing:
|
|
202
|
+
|
|
203
|
+
- **Which files.** A file in the change that a person runs by hand, once per environment: `.sql`, a Mongo script, a `.sh` / `.ps1` / `.py` / `.js` data fix. The test: if the UAT checklist, the PR, or Claude's report tells someone to run it, it gets attached. Not app code, not tests or build tooling, not a migration the app or the pipeline applies by itself.
|
|
204
|
+
- **Which work item.** The one whose change needs it. In a Feature run, the child story — never the Feature.
|
|
205
|
+
- **The comment says how to run it.** The attachment comment carries the repo path and the exact command, with the environment as a placeholder (e.g. `scripts/tenant-provisioning/rename-manager-roles.sh — after each environment's deploy: RMR_CONFIRM='apply <Env>' scripts/…/rename-manager-roles.sh apply <Env>`). Whoever deploys to prod next month won't have this session.
|
|
206
|
+
- **No secrets in the file.** Never attach a script with a connection string, key, password, or sensitive field value in it — the script reads those from the environment. If it can't, stop and tell the user instead of attaching.
|
|
207
|
+
- **Replace, don't duplicate.** If an attachment with the same filename is already on the item, compare it with the repo copy (`curl` the attachment's `url` into `diff`). Identical → leave it. Different → swap it in one PATCH (`remove` the old relation by its index, then `add` the new one), so a promotion never lists two versions of one script.
|
|
208
|
+
|
|
209
|
+
The Azure DevOps MCP server can only *download* attachments, so the upload goes through the REST API with an `az` token. The org and project GUID come from the work item's own `url` (`https://dev.azure.com/{org}/{projectId}/_apis/wit/workItems/{id}`):
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
TOKEN=$(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv)
|
|
213
|
+
BASE="https://dev.azure.com/{org}/{projectId}/_apis/wit"
|
|
214
|
+
|
|
215
|
+
# 1. Upload the bytes — the response JSON carries the attachment "url"
|
|
216
|
+
curl -sS -X POST "$BASE/attachments?fileName={file name}&api-version=7.1" \
|
|
217
|
+
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/octet-stream" \
|
|
218
|
+
--data-binary @"{repo path}"
|
|
219
|
+
|
|
220
|
+
# 2. Link it to the work item. The body goes in a scratch file, because the run command
|
|
221
|
+
# in the comment usually has quotes that break inline JSON:
|
|
222
|
+
# [{"op":"test","path":"/rev","value":{rev from a fresh get}},
|
|
223
|
+
# {"op":"add","path":"/relations/-","value":{"rel":"AttachedFile","url":"{attachment url}",
|
|
224
|
+
# "attributes":{"comment":"{repo path} — {how to run}"}}}]
|
|
225
|
+
curl -sS -X PATCH "$BASE/workitems/{id}?api-version=7.1" \
|
|
226
|
+
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json-patch+json" \
|
|
227
|
+
--data-binary @"{scratch dir}/attach-patch.json"
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Never print the token. If `az` has no session, swap the bearer header for `-u ":$AZURE_DEVOPS_PAT"`; if neither works, ask the user to run `! az login`. A failed attachment never blocks the PR, and it is never skipped quietly — the summary says `⚠️ NOT attached: {file} → AB#{id}` so it can be fixed before the first promotion.
|
|
231
|
+
|
|
192
232
|
**Reported, never enforced.** A script never blocks a promotion and never changes the gate check — it may already have been run, or be a reference copy. The person deploying decides; the commands only make sure nobody finds out afterwards.
|
|
193
233
|
|
|
194
234
|
#### Verifier Assignment
|
|
@@ -270,7 +310,7 @@ This runs pre-flight checks, commits, pushes the current branch, and triggers th
|
|
|
270
310
|
|
|
271
311
|
#### Branch Naming Convention
|
|
272
312
|
|
|
273
|
-
When `/implement AB#<id>` is run, a branch is created off the current branch
|
|
313
|
+
When `/implement AB#<id>` is run, a branch is created in the ticket's worktree (`../{repo}-AB{id}`, removed again once the branch is pushed — "work in place" opts out). It is cut off the current branch, or off the project's PR target when the current branch is another ticket's (see **PR Targeting**). The prefix comes from the Azure DevOps work item type:
|
|
274
314
|
|
|
275
315
|
| Work Item Type | Branch Prefix | Example |
|
|
276
316
|
|---|---|---|
|
|
@@ -288,13 +328,15 @@ Format: `{prefix}AB#{id}-{sanitized-title}` (title lowercased, special chars rep
|
|
|
288
328
|
|
|
289
329
|
PRs always target the branch you were on when `/implement` was invoked. The base branch is captured dynamically at the start — no assumptions about branch names.
|
|
290
330
|
|
|
331
|
+
**Exception — another ticket's branch.** If that branch starts with a ticket prefix (`feature/`, `story/`, `bugfix/`, `hotfix/`, `work/`, `cherry-pick/`, `release/`, `revert/`), the new ticket is **not** cut from it. `/implement` uses the PR target this CLAUDE.md names instead, or asks. Without the exception, a folder left on one ticket's branch would put that ticket's commits in the next ticket's PR, and aim the PR at the wrong branch.
|
|
332
|
+
|
|
291
333
|
#### Hot Fix Workflow
|
|
292
334
|
|
|
293
335
|
Hot Fix work items follow the same automated checks (build, lint, tests, review) but skip manual UAT. An abbreviated confirmation is shown instead. Hot Fix PRs get a `hotfix` label. Hot Fixes target the current branch — for a production hot fix, start on `prod` so the PR merges (and deploys) straight there. **Then bring the fix back to `main`:** open a second PR from the same hotfix branch into `main` (or `/cherry-pick AB#<id> main`) so the next `main → dev` promotion doesn't overwrite it. A hot fix that lives only on `prod` is lost on the next release.
|
|
294
336
|
|
|
295
337
|
#### Feature Workflow (ordered story waves)
|
|
296
338
|
|
|
297
|
-
Running `/implement` on a **Feature** implements its child User Stories in **waves** driven by the `Custom.Order` field: stories sharing the same order value are implemented **in parallel** (one agent per story, each in an isolated git worktree), and waves run sequentially in ascending order so later stories build on earlier ones. All work merges into a single `feature/AB#<id>-...` branch; quality checks, code review, UAT, and one PR happen at the feature level, and every implemented story is linked to that PR. Stories without a `Custom.Order` value run in a final catch-all wave (flagged for confirmation first).
|
|
339
|
+
Running `/implement` on a **Feature** implements its child User Stories in **waves** driven by the `Custom.Order` field: stories sharing the same order value are implemented **in parallel** (one agent per story, each in an isolated git worktree), and waves run sequentially in ascending order so later stories build on earlier ones. All work merges into a single `feature/AB#<id>-...` branch; quality checks, code review, UAT, and one PR happen at the feature level, and every implemented story is linked to that PR. Stories without a `Custom.Order` value run in a final catch-all wave (flagged for confirmation first). The Feature itself gets one ticket worktree, `../{repo}-AB{feature-id}`; the per-story worktrees branch from it and are removed as each story merges back.
|
|
298
340
|
|
|
299
341
|
**Work item states:** `/implement` moves the work item to `Active` when implementation starts — for a single work item (User Story, Bug, Hot Fix) right after the branch is created; for a Feature, each child story goes `Active` as its wave begins. When the PR is created, each implemented child **User Story** moves to `Code Review` — the **Feature's state is never changed**. The Feature is a parent container; it advances only as its child stories are verified/closed. Only child **Tasks** are ever closed — never the stories or the Feature. From `Code Review` onward the stories follow the **Work Item States ↔ Environments** table above as they are promoted.
|
|
300
342
|
|