baldart 3.41.0 → 4.0.0
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/CHANGELOG.md +34 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/REGISTRY.md +72 -24
- package/framework/.claude/agents/api-perf-cost-auditor.md +12 -5
- package/framework/.claude/agents/code-reviewer.md +30 -23
- package/framework/.claude/agents/codebase-architect.md +47 -43
- package/framework/.claude/agents/coder.md +29 -18
- package/framework/.claude/agents/doc-reviewer.md +55 -28
- package/framework/.claude/agents/plan-auditor.md +77 -12
- package/framework/.claude/agents/prd-card-writer.md +43 -13
- package/framework/.claude/agents/prd.md +22 -3
- package/framework/.claude/agents/qa-sentinel.md +33 -15
- package/framework/.claude/agents/security-reviewer.md +65 -10
- package/framework/.claude/agents/senior-researcher.md +8 -1
- package/framework/.claude/agents/ui-expert.md +22 -7
- package/framework/.claude/commands/check.md +31 -11
- package/framework/.claude/commands/codexreview.md +48 -29
- package/framework/.claude/commands/new.md +29 -330
- package/framework/.claude/commands/qa.md +57 -37
- package/framework/.claude/skills/context-primer/SKILL.md +29 -8
- package/framework/.claude/skills/doc-writing-for-rag/SKILL.md +36 -36
- package/framework/.claude/skills/frontend-design/SKILL.md +10 -8
- package/framework/.claude/skills/new/SKILL.md +409 -302
- package/framework/.claude/skills/prd/SKILL.md +67 -38
- package/framework/.claude/skills/prd/assets/card-template.yml +22 -26
- package/framework/.claude/skills/prd/assets/epic-template.yml +5 -5
- package/framework/.claude/skills/prd/assets/state-template.md +25 -3
- package/framework/.claude/skills/prd/references/api-perf-gate.md +143 -33
- package/framework/.claude/skills/prd/references/audit-phase.md +48 -34
- package/framework/.claude/skills/prd/references/backlog-phase.md +38 -11
- package/framework/.claude/skills/prd/references/discovery-phase.md +121 -44
- package/framework/.claude/skills/prd/references/impact-analysis.md +127 -23
- package/framework/.claude/skills/prd/references/prd-add-phase.md +18 -214
- package/framework/.claude/skills/prd/references/prd-writing-phase.md +52 -42
- package/framework/.claude/skills/prd/references/research-phase.md +105 -19
- package/framework/.claude/skills/prd/references/ui-design-phase.md +20 -8
- package/framework/.claude/skills/prd/references/validation-phase.md +97 -72
- package/framework/.claude/skills/prd-add/SKILL.md +70 -20
- package/framework/.claude/skills/simplify/SKILL.md +22 -12
- package/framework/.claude/skills/ui-design/SKILL.md +26 -7
- package/framework/.claude/skills/webapp-testing/SKILL.md +6 -4
- package/framework/.claude/skills/worktree-manager/SKILL.md +206 -143
- package/framework/agents/coding-standards.md +85 -0
- package/framework/agents/skills-mapping.md +85 -82
- package/framework/agents/testing.md +6 -4
- package/framework/templates/baldart.config.template.yml +29 -7
- package/package.json +1 -1
- package/src/commands/configure.js +43 -9
- package/framework/.claude/skills/prd-add/references/impact-analysis.md +0 -233
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Manage fully independent git worktrees for parallel coding agents.
|
|
5
5
|
Commands: /nw (new worktree) creates an isolated workspace with its own
|
|
6
6
|
node_modules, .next cache, and dev server port; /mw (merge worktree) commits,
|
|
7
|
-
pushes, creates PR to
|
|
7
|
+
pushes, creates PR to the trunk branch (git.trunk_branch), merges, and cleans up; /lw (list worktrees)
|
|
8
8
|
shows status of all active worktrees; /cw (cleanup worktrees) removes stale
|
|
9
9
|
or already-merged worktrees without the full PR flow.
|
|
10
10
|
Also used programmatically by /new orchestrator for batch card worktree ops.
|
|
@@ -15,8 +15,10 @@ description: >
|
|
|
15
15
|
# Worktree Manager
|
|
16
16
|
|
|
17
17
|
**Project Context (resolved from `baldart.config.yml`)**:
|
|
18
|
-
- `git.
|
|
18
|
+
- `git.trunk_branch` — the integration trunk branch (resolved value, default autodetected). Every worktree branches from `origin/<git.trunk_branch>` and merges back into it. NEVER hardcode `develop` or `main`.
|
|
19
|
+
- `git.merge_strategy` — `pr` (default, GitHub PR via `gh`) or `local-push` (direct fast-forward to `origin/<git.trunk_branch>`). Drives `/mw` step 4c.
|
|
19
20
|
- `paths.backlog_dir` — used for syncing untracked backlog cards (`/nw` step 3b).
|
|
21
|
+
- `paths.metrics` — JSONL telemetry dir (default `docs/metrics`) used by the rebase conflict-resolution table.
|
|
20
22
|
- Protocol reference: `framework/agents/project-context.md`. Skills must ASK when a needed key is missing — never assume.
|
|
21
23
|
|
|
22
24
|
**IMMEDIATE EXECUTION**: When invoked via `/nw`, `/mw`, `/lw`, or `/cw`, do NOT explain the process. Start executing the matching command flow immediately:
|
|
@@ -61,11 +63,11 @@ What docs mode SKIPS vs the standard flow:
|
|
|
61
63
|
- No build / lint / tsc gates at merge time
|
|
62
64
|
|
|
63
65
|
What docs mode KEEPS:
|
|
64
|
-
- Pre-flight `git fetch origin
|
|
65
|
-
- `git worktree add` branched from `origin
|
|
66
|
+
- Pre-flight `git fetch origin <git.trunk_branch>` (read-only on the main repo).
|
|
67
|
+
- `git worktree add` branched from `origin/<git.trunk_branch>`.
|
|
66
68
|
- Registry entry with `kind: "docs"` so `/lw` and `/cw` can list and clean docs worktrees alongside code worktrees.
|
|
67
69
|
- Safety commit at merge time (uncommitted files are never lost).
|
|
68
|
-
- Rebase onto `origin
|
|
70
|
+
- Rebase onto `origin/<git.trunk_branch>` before merging (prevents PR/FF conflicts).
|
|
69
71
|
- The configured `git.merge_strategy` (`pr` or `local-push`).
|
|
70
72
|
- Cleanup of worktree + local branch + remote branch after merge.
|
|
71
73
|
|
|
@@ -108,8 +110,18 @@ if [ ! -f "$MAIN_ROOT/.gitignore" ] || ! grep -qE '^\.worktrees/?$' "$MAIN_ROOT/
|
|
|
108
110
|
exit 1
|
|
109
111
|
fi
|
|
110
112
|
|
|
113
|
+
# 0b. Resolve the trunk branch from config (R7) — no bare 'develop' literal.
|
|
114
|
+
# TRUNK MUST be non-empty before any fetch/branch.
|
|
115
|
+
TRUNK=$(grep -A40 '^git:' "$MAIN_ROOT/baldart.config.yml" 2>/dev/null \
|
|
116
|
+
| grep 'trunk_branch:' | head -1 \
|
|
117
|
+
| sed -E 's/.*trunk_branch:[[:space:]]*([A-Za-z0-9._\/-]+).*/\1/' || true)
|
|
118
|
+
if [ -z "$TRUNK" ]; then
|
|
119
|
+
echo "ERROR: git.trunk_branch not set in baldart.config.yml — ASK the user (never assume 'develop')." >&2
|
|
120
|
+
exit 1
|
|
121
|
+
fi
|
|
122
|
+
|
|
111
123
|
# 1. Pre-flight — read-only fetch, NEVER touches main repo HEAD
|
|
112
|
-
git fetch origin
|
|
124
|
+
git fetch origin "$TRUNK"
|
|
113
125
|
|
|
114
126
|
# 2. Duplicate check — read registry; if an entry with the same slug AND kind=docs
|
|
115
127
|
# exists, return the existing { path, branch, kind } instead of creating a new
|
|
@@ -120,16 +132,21 @@ git fetch origin develop
|
|
|
120
132
|
# 3. Create the worktree. The path returned MUST be ABSOLUTE — Claude Code's
|
|
121
133
|
# Write/Edit require absolute paths, and downstream skills (/prd, ui-design,
|
|
122
134
|
# prd-card-writer) interpolate this path verbatim.
|
|
135
|
+
# SLUG must be kebab-case + deterministic (R8): the same slug always maps to
|
|
136
|
+
# the same worktree path, so a resumed /prd run finds the existing worktree.
|
|
123
137
|
SLUG="<slug>"
|
|
124
138
|
BRANCH_PREFIX="${branchPrefix:-prd/}"
|
|
125
139
|
BRANCH="${BRANCH_PREFIX}${SLUG}"
|
|
126
|
-
WORKTREE_REL=".worktrees/prd-${SLUG}"
|
|
140
|
+
WORKTREE_REL=".worktrees/prd-${SLUG}" # deterministic: derived from slug only, no timestamp
|
|
127
141
|
WORKTREE_PATH="$MAIN_ROOT/$WORKTREE_REL" # absolute, anchored to main repo root
|
|
128
|
-
git -C "$MAIN_ROOT" worktree add "$WORKTREE_REL" -b "$BRANCH" origin
|
|
142
|
+
git -C "$MAIN_ROOT" worktree add "$WORKTREE_REL" -b "$BRANCH" "origin/$TRUNK"
|
|
129
143
|
|
|
130
144
|
# 4. Registry entry — kind: "docs", port: null, buildVerified: null
|
|
131
145
|
# Same registry file (.worktrees/registry.json) as code worktrees.
|
|
132
146
|
# The `path` field MUST hold $WORKTREE_PATH (absolute), not $WORKTREE_REL.
|
|
147
|
+
# R6: persist $MAIN_ROOT, slug, branch AND the resolved trunk on the entry
|
|
148
|
+
# (fields: path, slug, branch, trunkBranch) so every later consumer reads them
|
|
149
|
+
# from the registry with a presence guard — never relies on in-context vars.
|
|
133
150
|
```
|
|
134
151
|
|
|
135
152
|
**Registry entry shape for docs worktrees:**
|
|
@@ -141,6 +158,8 @@ git -C "$MAIN_ROOT" worktree add "$WORKTREE_REL" -b "$BRANCH" origin/develop
|
|
|
141
158
|
"groupParent": null,
|
|
142
159
|
"slug": "menu-nutritional-info",
|
|
143
160
|
"branch": "prd/menu-nutritional-info",
|
|
161
|
+
"trunkBranch": "develop",
|
|
162
|
+
"mainRoot": "/abs/repo-root",
|
|
144
163
|
"path": "/abs/.worktrees/prd-menu-nutritional-info",
|
|
145
164
|
"port": null,
|
|
146
165
|
"kind": "docs",
|
|
@@ -177,6 +196,19 @@ Output:
|
|
|
177
196
|
**Steps (run exactly these, nothing else):**
|
|
178
197
|
|
|
179
198
|
```bash
|
|
199
|
+
# Presence guard (R6): $WORKTREE_PATH and the resolved $TRUNK MUST be read from
|
|
200
|
+
# the registry entry (fields path / trunkBranch) and be non-empty before any op.
|
|
201
|
+
if [ -z "$WORKTREE_PATH" ]; then
|
|
202
|
+
echo "ERROR: \$WORKTREE_PATH unset — read it from the registry entry before mw-docs." >&2
|
|
203
|
+
exit 1
|
|
204
|
+
fi
|
|
205
|
+
# If $TRUNK is unset, read the `trunkBranch` field from this worktree's registry
|
|
206
|
+
# entry in .worktrees/registry.json (do NOT assume 'develop').
|
|
207
|
+
if [ -z "$TRUNK" ]; then
|
|
208
|
+
echo "ERROR: trunk branch unresolved — read trunkBranch from the registry entry (do NOT assume 'develop')." >&2
|
|
209
|
+
exit 1
|
|
210
|
+
fi
|
|
211
|
+
|
|
180
212
|
cd "$WORKTREE_PATH"
|
|
181
213
|
|
|
182
214
|
# 1. Safety commit (MANDATORY — same as /mw step 3, never skippable).
|
|
@@ -191,18 +223,17 @@ cd "$WORKTREE_PATH"
|
|
|
191
223
|
# - ${paths.references_dir}/project-status.md (if updated by Step 7)
|
|
192
224
|
# NEVER `git add .` or `git add -A`. NEVER `git stash` (see Safety Rules).
|
|
193
225
|
|
|
194
|
-
# 2. Rebase onto latest
|
|
195
|
-
# table (additive auto-strip for
|
|
196
|
-
#
|
|
226
|
+
# 2. Rebase onto the latest trunk branch ($TRUNK = git.trunk_branch). Reuses the
|
|
227
|
+
# /mw step 4b conflict-resolution table (additive auto-strip for
|
|
228
|
+
# docs/yaml/jsonl-in-${paths.metrics}; abort on structured registries when
|
|
229
|
+
# validation fails; abort on code/tests/fixtures).
|
|
197
230
|
#
|
|
198
|
-
#
|
|
199
|
-
# above guarantees a clean working tree
|
|
200
|
-
# /mw step 4b ("stash dirty files before rebase, pop after") is a no-op
|
|
201
|
-
# for mw-docs and must be SKIPPED. Running `git stash push` from a
|
|
231
|
+
# NO `git stash` in mw-docs (or anywhere in a worktree). The safety commit in
|
|
232
|
+
# step 1 above guarantees a clean working tree. Running `git stash push` from a
|
|
202
233
|
# worktree violates the Safety Rule "NEVER use git stash in worktrees"
|
|
203
234
|
# (refs/stash is shared across all worktrees via git.commondir — see the
|
|
204
|
-
# FEAT-0522 incident note). Two parallel
|
|
205
|
-
#
|
|
235
|
+
# FEAT-0522 incident note). Two parallel runs that both stash would race on the
|
|
236
|
+
# shared stash list and could pop into the wrong tree.
|
|
206
237
|
#
|
|
207
238
|
# Direct rebase, no stash. If the working tree is unexpectedly dirty here
|
|
208
239
|
# (someone wrote between step 1 and step 2), STOP and report — a second
|
|
@@ -214,14 +245,14 @@ if [ -n "$DIRTY" ]; then
|
|
|
214
245
|
echo "Investigate — never stash to recover (refs/stash shared across worktrees)." >&2
|
|
215
246
|
exit 1
|
|
216
247
|
fi
|
|
217
|
-
git -C "$WORKTREE_PATH" fetch origin
|
|
218
|
-
git -C "$WORKTREE_PATH" rebase origin
|
|
248
|
+
git -C "$WORKTREE_PATH" fetch origin "$TRUNK"
|
|
249
|
+
git -C "$WORKTREE_PATH" rebase "origin/$TRUNK"
|
|
219
250
|
# (apply /mw 4b conflict-resolution table if rebase reports conflicts)
|
|
220
251
|
|
|
221
|
-
# 3. Land on
|
|
222
|
-
# baldart.config.yml. NEVER `git checkout
|
|
252
|
+
# 3. Land on trunk — reuse /mw step 4c verbatim. Read git.merge_strategy from
|
|
253
|
+
# baldart.config.yml. NEVER `git checkout <trunk>` on the main repo.
|
|
223
254
|
# - pr → gh pr create + gh pr merge --merge --delete-branch
|
|
224
|
-
# - local-push → git push origin <branch
|
|
255
|
+
# - local-push → git push origin <branch>:$TRUNK (fast-forward only)
|
|
225
256
|
#
|
|
226
257
|
# Note: many consumers prefer `local-push` for docs worktrees specifically,
|
|
227
258
|
# because a PRD is rarely worth a GitHub PR roundtrip. The CLI does not
|
|
@@ -232,8 +263,8 @@ git -C "$WORKTREE_PATH" rebase origin/develop
|
|
|
232
263
|
# Skip /mw step 5 (post-merge `npm run build`) — docs worktrees have nothing
|
|
233
264
|
# to build.
|
|
234
265
|
|
|
235
|
-
# 5. Sync local
|
|
236
|
-
# repo HEAD is already
|
|
266
|
+
# 5. Sync local trunk ref — reuse /mw "Common" block: ff-only pull when main
|
|
267
|
+
# repo HEAD is already $TRUNK; otherwise just fetch.
|
|
237
268
|
|
|
238
269
|
# 6. Remove the registry entry for this worktree.
|
|
239
270
|
```
|
|
@@ -248,7 +279,7 @@ can investigate. Cleanup runs only after a successful merge.
|
|
|
248
279
|
|
|
249
280
|
The merge path depends on `git.merge_strategy` in `baldart.config.yml`:
|
|
250
281
|
- `pr` (default, since v3.4.0): merge via `gh pr create` + `gh pr merge`. `prNumber` is the PR number, `mergeCommit` is the SHA returned by the GitHub merge API.
|
|
251
|
-
- `local-push` (since v3.9.0): direct fast-forward push of the rebased feature branch to `origin
|
|
282
|
+
- `local-push` (since v3.9.0): direct fast-forward push of the rebased feature branch to `origin/<git.trunk_branch>` — no PR, no `gh`. `prNumber` is `null`, `mergeCommit` is the local SHA of the feature branch tip.
|
|
252
283
|
|
|
253
284
|
Neither path runs `git checkout` / `git switch` / `git branch` on the main repo. The orchestrator can branch on `strategy` if it needs PR linking (only meaningful in `pr` mode).
|
|
254
285
|
|
|
@@ -276,6 +307,8 @@ All worktree operations read/write `.worktrees/registry.json`:
|
|
|
276
307
|
"groupParent": "FEAT-0200",
|
|
277
308
|
"slug": "menu-ranking",
|
|
278
309
|
"branch": "feat/FEAT-0200-menu-ranking",
|
|
310
|
+
"trunkBranch": "develop",
|
|
311
|
+
"mainRoot": "/absolute/path",
|
|
279
312
|
"path": "/absolute/path/.worktrees/feat-FEAT-0200-menu-ranking",
|
|
280
313
|
"port": 3001,
|
|
281
314
|
"createdAt": "2026-03-09T10:00:00Z",
|
|
@@ -289,6 +322,8 @@ All worktree operations read/write `.worktrees/registry.json`:
|
|
|
289
322
|
- `card`: primary card ID (first card or group parent), or `null` for slug-only worktrees
|
|
290
323
|
- `cards`: all card IDs sharing this worktree (for batches), or `[]` for slug-only
|
|
291
324
|
- `groupParent`: from backlog card `group.parent` field, or `null` for standalone/slug-only
|
|
325
|
+
- `trunkBranch`: the resolved `git.trunk_branch` this worktree was created from (R6 — persisted so `/mw`/`/cw` never re-guess or assume `develop`)
|
|
326
|
+
- `mainRoot`: absolute path of the main repo root (R6 — persisted so `$MAIN` is read from the registry, not from in-context state that does not survive compaction)
|
|
292
327
|
- `buildVerified`: true if `npm run build` passed after setup
|
|
293
328
|
|
|
294
329
|
Create the file if it doesn't exist. Update it on every `/nw`, `/mw`, `/cw` operation.
|
|
@@ -305,14 +340,26 @@ Supports three modes:
|
|
|
305
340
|
### 1. Pre-flight checks
|
|
306
341
|
|
|
307
342
|
```bash
|
|
308
|
-
#
|
|
309
|
-
#
|
|
310
|
-
#
|
|
343
|
+
# Resolve $MAIN (main repo root) and $TRUNK (git.trunk_branch) up front, and
|
|
344
|
+
# persist both onto the registry entry created in step 6 (R6). Every later step
|
|
345
|
+
# reads them from the registry with a presence guard — never from in-context state.
|
|
346
|
+
MAIN="$(git rev-parse --show-toplevel)"
|
|
347
|
+
TRUNK=$(grep -A40 '^git:' "$MAIN/baldart.config.yml" 2>/dev/null \
|
|
348
|
+
| grep 'trunk_branch:' | head -1 \
|
|
349
|
+
| sed -E 's/.*trunk_branch:[[:space:]]*([A-Za-z0-9._\/-]+).*/\1/' || true)
|
|
350
|
+
if [ -z "$TRUNK" ]; then
|
|
351
|
+
echo "ERROR: git.trunk_branch not set in baldart.config.yml — ASK the user (never assume 'develop')." >&2
|
|
352
|
+
exit 1
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
# Fetch latest trunk from remote — read-only, does NOT touch the main repo's HEAD.
|
|
356
|
+
# We branch the new worktree from `origin/<trunk>` directly (step 3), so we never
|
|
357
|
+
# need to checkout the trunk on the main repo. This is the absolute rule:
|
|
311
358
|
# /nw must not run `git checkout`, `git switch`, `git checkout -b`, or
|
|
312
359
|
# `git branch` on the main repo (it's shared across parallel terminals; many
|
|
313
360
|
# consumer projects declare this as a hard rule in CLAUDE.md and the classifier
|
|
314
361
|
# will block the call preemptively).
|
|
315
|
-
git fetch origin
|
|
362
|
+
git fetch origin "$TRUNK"
|
|
316
363
|
```
|
|
317
364
|
|
|
318
365
|
(There is no need to check `git status --porcelain` on the main repo here — we
|
|
@@ -347,22 +394,24 @@ Branch name: `feat/<CARD-ID>-<slug>` (with card) or `feat/<slug>` (slug-only). U
|
|
|
347
394
|
|
|
348
395
|
### 3. Create the worktree
|
|
349
396
|
|
|
350
|
-
Base the new worktree on `origin
|
|
397
|
+
Base the new worktree on `origin/<$TRUNK>` (the remote tip we just fetched in step 1) rather than the local trunk branch — that way we never need the local trunk to be checked out or up to date on the main repo.
|
|
398
|
+
|
|
399
|
+
The worktree path is **deterministic** (R8): derived from the card ID + kebab-case slug only, never a timestamp — so a resumed run resolves to the same path and the duplicate check in step 1 is meaningful.
|
|
351
400
|
|
|
352
401
|
```bash
|
|
353
402
|
# With card ID:
|
|
354
403
|
WORKTREE_PATH=".worktrees/feat-<CARD-ID>-<slug>"
|
|
355
|
-
git worktree add "$WORKTREE_PATH" -b "feat/<CARD-ID>-<slug>" origin
|
|
404
|
+
git worktree add "$WORKTREE_PATH" -b "feat/<CARD-ID>-<slug>" "origin/$TRUNK"
|
|
356
405
|
|
|
357
406
|
# Slug-only (no card):
|
|
358
407
|
WORKTREE_PATH=".worktrees/feat-<slug>"
|
|
359
|
-
git worktree add "$WORKTREE_PATH" -b "feat/<slug>" origin
|
|
408
|
+
git worktree add "$WORKTREE_PATH" -b "feat/<slug>" "origin/$TRUNK"
|
|
360
409
|
```
|
|
361
410
|
|
|
362
411
|
### 3b. Sync untracked backlog cards from main repo
|
|
363
412
|
|
|
364
413
|
Backlog cards created during `/prd` sessions may exist as untracked files in the main repo
|
|
365
|
-
but are NOT on
|
|
414
|
+
but are NOT on the trunk branch yet. The worktree (branched from the trunk) won't have them.
|
|
366
415
|
|
|
367
416
|
```bash
|
|
368
417
|
MAIN_ROOT="$(git -C "$WORKTREE_PATH" rev-parse --show-superproject-working-tree 2>/dev/null || pwd)"
|
|
@@ -432,8 +481,8 @@ if [ -n "$HOOK_SRC" ]; then
|
|
|
432
481
|
case "$ACTIVE" in
|
|
433
482
|
"$HOOK_SRC"|"$HOOK_SRC"/*|.husky/_) : ;; # active dir serves the versioned hooks (incl. husky v9 .husky/_)
|
|
434
483
|
*)
|
|
435
|
-
echo "⚠️ WARNING: git hooks
|
|
436
|
-
echo "
|
|
484
|
+
echo "⚠️ WARNING: git hooks INACTIVE — the versioned hooks live in '$HOOK_SRC' but core.hooksPath = '$ACTIVE'." >&2
|
|
485
|
+
echo " Re-activate them with: git config core.hooksPath $HOOK_SRC" >&2
|
|
437
486
|
;;
|
|
438
487
|
esac
|
|
439
488
|
fi
|
|
@@ -451,11 +500,11 @@ npm run build
|
|
|
451
500
|
```
|
|
452
501
|
|
|
453
502
|
If build fails → STOP and report. Do NOT continue — the worktree is broken.
|
|
454
|
-
If only tsc/lint fails → report but continue (
|
|
503
|
+
If only tsc/lint fails → report but continue (the trunk branch should be clean, may be a transient issue).
|
|
455
504
|
|
|
456
505
|
### 6. Update registry
|
|
457
506
|
|
|
458
|
-
Add entry to `.worktrees/registry.json` with all fields: card, cards, groupParent, slug, branch, absolute path, port, `createdAt`, `envSyncedAt`, and `buildVerified`.
|
|
507
|
+
Add entry to `.worktrees/registry.json` with all fields: card, cards, groupParent, slug, branch, `trunkBranch` (the resolved `$TRUNK`), `mainRoot` (the resolved `$MAIN`), absolute path, port, `createdAt`, `envSyncedAt`, and `buildVerified`. Persisting `trunkBranch` + `mainRoot` (R6) lets `/mw`/`/cw` read them back with a presence guard instead of re-deriving or assuming `develop`.
|
|
459
508
|
|
|
460
509
|
### 7. Auto-start dev server (if `--dev` flag or `/nw --dev`)
|
|
461
510
|
|
|
@@ -503,8 +552,18 @@ Read `.worktrees/registry.json`. If multiple entries exist or user didn't specif
|
|
|
503
552
|
|
|
504
553
|
Fallback: `git worktree list` if registry is missing.
|
|
505
554
|
|
|
506
|
-
Determine:
|
|
555
|
+
Determine, reading them from the worktree's registry entry (R6):
|
|
507
556
|
- `WORKTREE_PATH`, `BRANCH`, `CARD` (or `CARDS` for batch worktrees)
|
|
557
|
+
- `TRUNK` (the entry's `trunkBranch`) and `MAIN` (the entry's `mainRoot`)
|
|
558
|
+
|
|
559
|
+
These were persisted at `/nw` time. Before any step below, presence-guard them — if `WORKTREE_PATH`, `TRUNK`, or `MAIN` is empty, HALT with a clear message (never assume `develop` or re-derive `$MAIN` silently):
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
if [ -z "$WORKTREE_PATH" ] || [ -z "$BRANCH" ] || [ -z "$TRUNK" ] || [ -z "$MAIN" ]; then
|
|
563
|
+
echo "ERROR: missing WORKTREE_PATH/BRANCH/TRUNK/MAIN — read them from the registry entry (fields path/branch/trunkBranch/mainRoot)." >&2
|
|
564
|
+
exit 1
|
|
565
|
+
fi
|
|
566
|
+
```
|
|
508
567
|
|
|
509
568
|
### 2. Env sync check
|
|
510
569
|
|
|
@@ -544,7 +603,7 @@ if [ -n "$CHANGED" ] || [ -n "$UNTRACKED" ]; then
|
|
|
544
603
|
|
|
545
604
|
Files were uncommitted when /mw started. This safety commit
|
|
546
605
|
prevents file loss during rebase. Squash-merge will fold this
|
|
547
|
-
into the final commit on
|
|
606
|
+
into the final commit on the trunk branch.
|
|
548
607
|
|
|
549
608
|
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>"
|
|
550
609
|
|
|
@@ -552,8 +611,8 @@ Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>"
|
|
|
552
611
|
fi
|
|
553
612
|
|
|
554
613
|
# 2. Run quality checks
|
|
555
|
-
# Lint only changed files (vs
|
|
556
|
-
npx eslint --max-warnings=0 $(git diff --name-only
|
|
614
|
+
# Lint only changed files (vs the trunk branch)
|
|
615
|
+
npx eslint --max-warnings=0 $(git diff --name-only "origin/$TRUNK" -- '*.ts' '*.tsx')
|
|
557
616
|
npx tsc --noEmit
|
|
558
617
|
|
|
559
618
|
# Full build (required before PR per AGENTS.md)
|
|
@@ -586,48 +645,51 @@ git commit -m "[<CARD-ID>] <description>"
|
|
|
586
645
|
|
|
587
646
|
**Programmatic mode**: The orchestrator handles per-card commits in Phase 4. `/mw` only needs to rebase and push (see steps 4b and 4c below).
|
|
588
647
|
|
|
589
|
-
### 4b. Rebase onto latest
|
|
648
|
+
### 4b. Rebase onto latest trunk (MUST — prevents PR merge conflicts)
|
|
590
649
|
|
|
591
|
-
Before pushing, ALWAYS rebase the feature branch onto the latest
|
|
650
|
+
Before pushing, ALWAYS rebase the feature branch onto the latest trunk branch to avoid merge conflicts at PR merge time. The trunk branch is `git.trunk_branch` from `baldart.config.yml` (resolved below).
|
|
592
651
|
|
|
593
652
|
```bash
|
|
653
|
+
# Presence guard (R6): $MAIN and $WORKTREE_PATH MUST already be set + non-empty
|
|
654
|
+
# (written to the registry at /nw time; re-read from there if absent). Never
|
|
655
|
+
# proceed with an undefined $MAIN — it would resolve git operations against the
|
|
656
|
+
# wrong repo.
|
|
657
|
+
if [ -z "$MAIN" ] || [ -z "$WORKTREE_PATH" ]; then
|
|
658
|
+
echo "ERROR: \$MAIN and/or \$WORKTREE_PATH unset — read them from .worktrees/registry.json before step 4b." >&2
|
|
659
|
+
exit 1
|
|
660
|
+
fi
|
|
661
|
+
|
|
662
|
+
# Resolve the trunk branch from config (default: read by the caller; no bare
|
|
663
|
+
# 'develop' literal). TRUNK MUST be non-empty.
|
|
664
|
+
if [ -z "$TRUNK" ]; then
|
|
665
|
+
echo "ERROR: \$TRUNK (git.trunk_branch) unset — resolve it from baldart.config.yml before step 4b." >&2
|
|
666
|
+
exit 1
|
|
667
|
+
fi
|
|
668
|
+
|
|
594
669
|
cd "$WORKTREE_PATH"
|
|
595
670
|
|
|
596
|
-
# 1.
|
|
597
|
-
#
|
|
598
|
-
#
|
|
671
|
+
# 1. NO `git stash` in a worktree (refs/stash is shared across all worktrees via
|
|
672
|
+
# git.commondir — see FEAT-0522 incident + Safety Rules). The safety commit in
|
|
673
|
+
# step 3 already guarantees a clean working tree. If the tree is unexpectedly
|
|
674
|
+
# dirty here (someone wrote between step 3 and step 4b), STOP and report — the
|
|
675
|
+
# right recovery is a SECOND safety commit, never a stash.
|
|
599
676
|
DIRTY=$(git status --porcelain 2>/dev/null)
|
|
600
|
-
STASHED="false"
|
|
601
677
|
if [ -n "$DIRTY" ]; then
|
|
602
|
-
echo "
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
678
|
+
echo "ERROR: working tree dirty in $WORKTREE_PATH after the step-3 safety commit." >&2
|
|
679
|
+
echo "Files: $DIRTY" >&2
|
|
680
|
+
echo "Make a second safety commit (explicit file names) — NEVER stash (refs/stash shared across worktrees)." >&2
|
|
681
|
+
exit 1
|
|
606
682
|
fi
|
|
607
683
|
|
|
608
|
-
# 2. Fetch latest
|
|
609
|
-
git fetch origin
|
|
684
|
+
# 2. Fetch latest trunk
|
|
685
|
+
git fetch origin "$TRUNK"
|
|
610
686
|
|
|
611
|
-
# 3. Rebase onto origin
|
|
612
|
-
git rebase origin
|
|
687
|
+
# 3. Rebase onto origin/<trunk>
|
|
688
|
+
git rebase "origin/$TRUNK" 2>&1
|
|
613
689
|
REBASE_EXIT=$?
|
|
614
690
|
```
|
|
615
691
|
|
|
616
|
-
**If rebase succeeds** (exit 0):
|
|
617
|
-
|
|
618
|
-
```bash
|
|
619
|
-
if [ "$STASHED" = "true" ]; then
|
|
620
|
-
STASH_NUM=$(git stash list | grep "$STASH_MSG" | sed -E 's/stash@\{([0-9]+)\}.*/\1/')
|
|
621
|
-
if [ -n "$STASH_NUM" ]; then
|
|
622
|
-
if ! git stash pop "stash@{$STASH_NUM}" 2>&1; then
|
|
623
|
-
echo "⚠️ STASH POP FAILED — files preserved in stash@{$STASH_NUM} ($STASH_MSG)"
|
|
624
|
-
echo "Run 'git stash show stash@{$STASH_NUM}' to see affected files"
|
|
625
|
-
echo "Run 'git stash pop stash@{$STASH_NUM}' to recover manually"
|
|
626
|
-
# DO NOT silently continue — report this to the user
|
|
627
|
-
fi
|
|
628
|
-
fi
|
|
629
|
-
fi
|
|
630
|
-
```
|
|
692
|
+
**If rebase succeeds** (exit 0): continue straight to push — there is no stash to restore.
|
|
631
693
|
|
|
632
694
|
**If rebase fails with conflicts**: apply the auto-resolution protocol below.
|
|
633
695
|
|
|
@@ -645,8 +707,8 @@ internal structure (tables, key/value blocks, code).
|
|
|
645
707
|
| Category | Patterns | Action | Note |
|
|
646
708
|
|----------|----------|--------|------|
|
|
647
709
|
| **Structured registries** | `project-status.md`, `ssot-registry.md`, `field-registry.*`, `traceability-matrix.md`, `REGISTRY.md` | Strip markers, then **structural validation** — if valid, accept; if invalid, abort | The auto-strip "keep both sides" usually produces an additive result (new bullet rows appended to a table or section). The validation step catches the rare case where the two sides modified the same row. See `validate_structured_md` below. |
|
|
648
|
-
| **JSONL logs under
|
|
649
|
-
| **Other `.jsonl`** | `*.jsonl` outside
|
|
710
|
+
| **JSONL logs under `${paths.metrics}/`** | `${paths.metrics}/*.jsonl`, `${paths.metrics}/**/*.jsonl` | Auto-resolve: strip markers, keep both sides | JSONL is line-oriented and additive — duplicating both branches' lines yields a valid file. (`paths.metrics` default `docs/metrics`.) |
|
|
711
|
+
| **Other `.jsonl`** | `*.jsonl` outside `${paths.metrics}/` | STOP — abort and report | Fixtures, seeds, training data: auto-strip can corrupt them. The user must inspect. |
|
|
650
712
|
| **Docs/config** | `docs/**/*.md`, `backlog/*.yml`, `*.md` outside `src/`, `*.json`/`*.yml` outside `src/` (excluding rows above) | Auto-resolve: strip markers, keep both sides | The canonical additive case. |
|
|
651
713
|
| **Code** | `src/**/*.ts`, `*.tsx`, `*.js`, `*.jsx` | STOP — abort rebase, report to user | |
|
|
652
714
|
| **Tests** | `*.test.*`, `*.spec.*` | Manual only — report to user | |
|
|
@@ -681,10 +743,8 @@ for FILE in $CONFLICTED; do
|
|
|
681
743
|
case "$FILE" in
|
|
682
744
|
src/*.ts|src/*.tsx|src/*.js|src/*.jsx|*.test.*|*.spec.*)
|
|
683
745
|
git rebase --abort
|
|
684
|
-
#
|
|
685
|
-
#
|
|
686
|
-
# (safety commit step 1 leaves the tree clean) so the pop is a no-op.
|
|
687
|
-
git stash pop 2>/dev/null || true
|
|
746
|
+
# No stash to restore: step 3's safety commit leaves the tree clean, and
|
|
747
|
+
# step 4b NEVER stashes (refs/stash is shared across worktrees).
|
|
688
748
|
echo "CONFLICT in $FILE — cannot auto-resolve" >&2
|
|
689
749
|
exit 1 ;;
|
|
690
750
|
esac
|
|
@@ -696,7 +756,6 @@ for FILE in $CONFLICTED; do
|
|
|
696
756
|
if ! validate_structured_md "$FILE"; then
|
|
697
757
|
git checkout --conflict=merge "$FILE" 2>/dev/null || true
|
|
698
758
|
git rebase --abort
|
|
699
|
-
git stash pop 2>/dev/null || true
|
|
700
759
|
echo "CONFLICT in structured registry $FILE produced invalid structure after auto-strip — manual resolution required." >&2
|
|
701
760
|
exit 1
|
|
702
761
|
fi
|
|
@@ -707,21 +766,19 @@ for FILE in $CONFLICTED; do
|
|
|
707
766
|
field-registry.*)
|
|
708
767
|
# field-registry.json typically — strict JSON, can't auto-strip safely
|
|
709
768
|
git rebase --abort
|
|
710
|
-
git stash pop 2>/dev/null || true
|
|
711
769
|
echo "CONFLICT in structured registry $FILE — JSON cannot be auto-stripped." >&2
|
|
712
770
|
exit 1 ;;
|
|
713
771
|
esac
|
|
714
772
|
|
|
715
|
-
# 3. JSONL — only auto-resolve under
|
|
773
|
+
# 3. JSONL — only auto-resolve under ${paths.metrics}/
|
|
716
774
|
case "$FILE" in
|
|
717
|
-
|
|
775
|
+
${paths.metrics}/*.jsonl|${paths.metrics}/**/*.jsonl)
|
|
718
776
|
sed -i '' '/^<<<<<<< /d; /^=======/d; /^>>>>>>> /d' "$FILE"
|
|
719
777
|
git add "$FILE"
|
|
720
778
|
continue ;;
|
|
721
779
|
*.jsonl)
|
|
722
780
|
git rebase --abort
|
|
723
|
-
|
|
724
|
-
echo "CONFLICT in $FILE (.jsonl outside docs/metrics/ — may be fixture/seed) — manual resolution required." >&2
|
|
781
|
+
echo "CONFLICT in $FILE (.jsonl outside ${paths.metrics}/ — may be fixture/seed) — manual resolution required." >&2
|
|
725
782
|
exit 1 ;;
|
|
726
783
|
esac
|
|
727
784
|
|
|
@@ -733,26 +790,30 @@ for FILE in $CONFLICTED; do
|
|
|
733
790
|
esac
|
|
734
791
|
done
|
|
735
792
|
|
|
736
|
-
# Continue rebase +
|
|
793
|
+
# Continue rebase + verify build (no stash to restore — step 4b never stashes)
|
|
737
794
|
git rebase --continue
|
|
738
|
-
git stash pop 2>/dev/null || true
|
|
739
795
|
npm run build
|
|
740
796
|
```
|
|
741
797
|
|
|
742
798
|
If build fails after rebase → STOP and report. The rebase introduced incompatibilities.
|
|
743
799
|
|
|
744
|
-
### 4c. Land the feature branch onto
|
|
800
|
+
### 4c. Land the feature branch onto the trunk branch (strategy-branched)
|
|
745
801
|
|
|
746
|
-
**IMPORTANT — NO local checkout/switch/branch on the main repo, regardless of strategy.** Consumer projects share the main repo as a parallel resource across multiple terminals; running `git checkout
|
|
802
|
+
**IMPORTANT — NO local checkout/switch/branch on the main repo, regardless of strategy.** Consumer projects share the main repo as a parallel resource across multiple terminals; running `git checkout <trunk>` on it preempts whatever the other terminals are doing and breaks worktree-driven workflows. Both strategies below operate from the worktree and the main repo's `HEAD` is never touched.
|
|
747
803
|
|
|
748
804
|
Read `git.merge_strategy` from `baldart.config.yml` (default: `pr`):
|
|
749
805
|
|
|
750
806
|
```bash
|
|
751
|
-
MAIN
|
|
752
|
-
|
|
807
|
+
# $MAIN and $TRUNK were resolved in step 1 (read from the registry entry, R6) and
|
|
808
|
+
# presence-guarded. Fall back to deriving $MAIN from git-common-dir ONLY if it is
|
|
809
|
+
# still unset — never silently re-derive over a value the registry already supplied.
|
|
810
|
+
if [ -z "$MAIN" ]; then
|
|
811
|
+
MAIN=$(git -C "$WORKTREE_PATH" rev-parse --git-common-dir)/..
|
|
812
|
+
MAIN=$(cd "$MAIN" && pwd)
|
|
813
|
+
fi
|
|
753
814
|
|
|
754
815
|
# Resolve strategy from baldart.config.yml (default: pr)
|
|
755
|
-
STRATEGY=$(grep -
|
|
816
|
+
STRATEGY=$(grep -A40 '^git:' "$MAIN/baldart.config.yml" 2>/dev/null \
|
|
756
817
|
| grep 'merge_strategy:' | head -1 \
|
|
757
818
|
| sed -E 's/.*merge_strategy:[[:space:]]*([a-z-]+).*/\1/' || echo "pr")
|
|
758
819
|
STRATEGY=${STRATEGY:-pr}
|
|
@@ -764,7 +825,7 @@ If neither `pr` nor `local-push` resolves, ASK the user once which to use and su
|
|
|
764
825
|
|
|
765
826
|
#### Strategy A — `pr` (default, GitHub PR via `gh`)
|
|
766
827
|
|
|
767
|
-
Use this when
|
|
828
|
+
Use this when the trunk branch is protected on origin (required status checks, reviews, merge queue) — a direct push would be rejected.
|
|
768
829
|
|
|
769
830
|
```bash
|
|
770
831
|
# 1. Push the feature branch to origin (force-with-lease in case of rebase in 4b).
|
|
@@ -776,7 +837,7 @@ PR_NUMBER=$(gh -R "$(gh repo view --json nameWithOwner -q .nameWithOwner)" pr li
|
|
|
776
837
|
|
|
777
838
|
if [ -z "$PR_NUMBER" ]; then
|
|
778
839
|
PR_URL=$(gh pr create \
|
|
779
|
-
--base
|
|
840
|
+
--base "$TRUNK" \
|
|
780
841
|
--head "$BRANCH" \
|
|
781
842
|
--title "[<CARD-ID>] $(echo "$BRANCH" | sed 's|.*/||')" \
|
|
782
843
|
--body "Auto-merged by /mw after final review + QA passed." \
|
|
@@ -797,7 +858,7 @@ if [ "$PR_STATE" != "MERGED" ]; then
|
|
|
797
858
|
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
|
798
859
|
gh api -X PUT "repos/$REPO/pulls/$PR_NUMBER/merge" \
|
|
799
860
|
-f merge_method=merge \
|
|
800
|
-
-f commit_title="[<CARD-ID>] Merge $BRANCH into
|
|
861
|
+
-f commit_title="[<CARD-ID>] Merge $BRANCH into $TRUNK"
|
|
801
862
|
PR_STATE=$(gh pr view "$PR_NUMBER" --json state -q .state)
|
|
802
863
|
fi
|
|
803
864
|
|
|
@@ -805,47 +866,47 @@ if [ "$PR_STATE" != "MERGED" ]; then
|
|
|
805
866
|
echo "❌ PR #$PR_NUMBER did not merge. Inspect manually: $PR_URL"
|
|
806
867
|
exit 1
|
|
807
868
|
fi
|
|
808
|
-
echo "✅ PR #$PR_NUMBER merged to
|
|
869
|
+
echo "✅ PR #$PR_NUMBER merged to $TRUNK."
|
|
809
870
|
```
|
|
810
871
|
|
|
811
872
|
If `gh` is not installed in the project, STOP and ask the user to install GitHub CLI (`brew install gh` or equivalent) and run `gh auth login`. Do NOT fall back to a local checkout — it violates terminal isolation.
|
|
812
873
|
|
|
813
|
-
If both gh CLI and REST API merge fail → STOP and report. Suggest switching to `merge_strategy: local-push` only if
|
|
874
|
+
If both gh CLI and REST API merge fail → STOP and report. Suggest switching to `merge_strategy: local-push` only if the trunk branch is NOT protected on origin.
|
|
814
875
|
|
|
815
876
|
---
|
|
816
877
|
|
|
817
878
|
#### Strategy B — `local-push` (direct FF push, no PR)
|
|
818
879
|
|
|
819
|
-
Use this when
|
|
880
|
+
Use this when the trunk branch is NOT protected on origin and the user wants to skip the GitHub PR roundtrip. After step 4b the feature branch is rebased onto `origin/$TRUNK`, so it can fast-forward the trunk on origin in a single push.
|
|
820
881
|
|
|
821
882
|
```bash
|
|
822
|
-
# 1. Sanity: confirm the feature branch is strictly ahead of origin
|
|
823
|
-
# (i.e. the rebase in step 4b succeeded and we can FF
|
|
824
|
-
git -C "$WORKTREE_PATH" fetch origin
|
|
825
|
-
AHEAD=$(git -C "$WORKTREE_PATH" rev-list --count "origin
|
|
826
|
-
BEHIND=$(git -C "$WORKTREE_PATH" rev-list --count "$BRANCH..origin
|
|
883
|
+
# 1. Sanity: confirm the feature branch is strictly ahead of origin/$TRUNK
|
|
884
|
+
# (i.e. the rebase in step 4b succeeded and we can FF the trunk on origin).
|
|
885
|
+
git -C "$WORKTREE_PATH" fetch origin "$TRUNK" --quiet
|
|
886
|
+
AHEAD=$(git -C "$WORKTREE_PATH" rev-list --count "origin/$TRUNK..$BRANCH")
|
|
887
|
+
BEHIND=$(git -C "$WORKTREE_PATH" rev-list --count "$BRANCH..origin/$TRUNK")
|
|
827
888
|
if [ "$BEHIND" -ne 0 ]; then
|
|
828
|
-
echo "❌ local-push aborted: $BRANCH is $BEHIND commit(s) behind origin
|
|
829
|
-
echo " Re-run step 4b (rebase onto origin
|
|
889
|
+
echo "❌ local-push aborted: $BRANCH is $BEHIND commit(s) behind origin/$TRUNK." >&2
|
|
890
|
+
echo " Re-run step 4b (rebase onto origin/$TRUNK), then retry." >&2
|
|
830
891
|
exit 1
|
|
831
892
|
fi
|
|
832
893
|
if [ "$AHEAD" -eq 0 ]; then
|
|
833
|
-
echo "ℹ️ Nothing to push — $BRANCH is identical to origin
|
|
894
|
+
echo "ℹ️ Nothing to push — $BRANCH is identical to origin/$TRUNK. Skipping."
|
|
834
895
|
else
|
|
835
|
-
echo "Fast-forwarding origin
|
|
896
|
+
echo "Fast-forwarding origin/$TRUNK by $AHEAD commit(s) via local-push…"
|
|
836
897
|
fi
|
|
837
898
|
|
|
838
|
-
# 2. Direct fast-forward push to
|
|
839
|
-
# If
|
|
840
|
-
if ! PUSH_OUTPUT=$(git -C "$WORKTREE_PATH" push origin "$BRANCH
|
|
899
|
+
# 2. Direct fast-forward push to the trunk on origin. No PR.
|
|
900
|
+
# If the trunk is protected on origin, this fails — surface the hint and stop.
|
|
901
|
+
if ! PUSH_OUTPUT=$(git -C "$WORKTREE_PATH" push origin "$BRANCH:$TRUNK" 2>&1); then
|
|
841
902
|
echo "$PUSH_OUTPUT"
|
|
842
903
|
if echo "$PUSH_OUTPUT" | grep -qiE "protected branch|required status|review required"; then
|
|
843
|
-
echo "❌ local-push rejected: '
|
|
904
|
+
echo "❌ local-push rejected: '$TRUNK' is protected on origin." >&2
|
|
844
905
|
echo " Switch baldart.config.yml → git.merge_strategy: pr, or remove the protection." >&2
|
|
845
906
|
fi
|
|
846
907
|
exit 1
|
|
847
908
|
fi
|
|
848
|
-
echo "✅ $BRANCH merged into
|
|
909
|
+
echo "✅ $BRANCH merged into $TRUNK on origin (fast-forward, no PR)."
|
|
849
910
|
|
|
850
911
|
# 3. Delete the remote feature branch (best-effort — never fatal).
|
|
851
912
|
git -C "$WORKTREE_PATH" push origin --delete "$BRANCH" 2>/dev/null || true
|
|
@@ -856,60 +917,62 @@ PR_NUMBER=""
|
|
|
856
917
|
|
|
857
918
|
---
|
|
858
919
|
|
|
859
|
-
#### Common — sync local
|
|
920
|
+
#### Common — sync local trunk ref (both strategies)
|
|
860
921
|
|
|
861
922
|
```bash
|
|
862
|
-
# Sync local
|
|
863
|
-
# NEVER `git checkout
|
|
864
|
-
#
|
|
865
|
-
#
|
|
923
|
+
# Sync the local trunk ($TRUNK) ONLY if the main repo's HEAD is already on it.
|
|
924
|
+
# NEVER `git checkout "$TRUNK"` here. If the user (or another terminal) had the
|
|
925
|
+
# trunk checked out, ff-only updates it cleanly. Otherwise we leave their HEAD
|
|
926
|
+
# alone and they'll pull next time. $TRUNK and $MAIN were resolved + presence-
|
|
927
|
+
# guarded in step 1 (read from the registry, R6).
|
|
866
928
|
CURRENT_BRANCH=$(git -C "$MAIN" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
867
|
-
if [ "$CURRENT_BRANCH" = "
|
|
868
|
-
if git -C "$MAIN" pull --ff-only origin
|
|
869
|
-
echo "✅ Main repo
|
|
929
|
+
if [ "$CURRENT_BRANCH" = "$TRUNK" ]; then
|
|
930
|
+
if git -C "$MAIN" pull --ff-only origin "$TRUNK" 2>/dev/null; then
|
|
931
|
+
echo "✅ Main repo $TRUNK fast-forwarded to origin/$TRUNK."
|
|
870
932
|
else
|
|
871
933
|
# FF blocked. NEVER leave this as a passive "Leaving as-is" TODO for the user
|
|
872
934
|
# (v3.39.0 — the finalizer MUST conclude). The cause is almost always an
|
|
873
935
|
# uncommitted append to the framework-owned telemetry log left by a prior run.
|
|
874
936
|
# Partition the dirty tree:
|
|
875
|
-
# - framework-owned artifact (
|
|
937
|
+
# - framework-owned artifact (${paths.metrics}/skill-runs.jsonl) → auto-reconcile
|
|
876
938
|
# (commit + rebase) so the ff completes with zero data loss;
|
|
877
939
|
# - ANY foreign file → emit a decision marker the orchestrator MUST convert
|
|
878
940
|
# into ONE explicit user gate. Never auto-commit work this run did not own.
|
|
941
|
+
METRICS_LOG="${paths.metrics}/skill-runs.jsonl" # paths.metrics default: docs/metrics
|
|
879
942
|
DIRTY=$(git -C "$MAIN" status --porcelain --untracked-files=no | sed 's/^...//')
|
|
880
943
|
FOREIGN=""
|
|
881
944
|
OWNED=""
|
|
882
945
|
for f in $DIRTY; do
|
|
883
946
|
case "$f" in
|
|
884
|
-
|
|
947
|
+
"$METRICS_LOG") OWNED="$OWNED $f" ;;
|
|
885
948
|
*) FOREIGN="$FOREIGN $f" ;;
|
|
886
949
|
esac
|
|
887
950
|
done
|
|
888
951
|
if [ -n "$FOREIGN" ]; then
|
|
889
|
-
echo "[SYNC-NEEDS-DECISION] main repo
|
|
952
|
+
echo "[SYNC-NEEDS-DECISION] main repo $TRUNK cannot fast-forward; foreign uncommitted files:${FOREIGN} — orchestrator MUST raise ONE explicit user gate (commit / user-handles), never a passive note."
|
|
890
953
|
elif [ -n "$OWNED" ]; then
|
|
891
954
|
# Only framework-owned telemetry is dirty → reconcile autonomously, lossless.
|
|
892
|
-
git -C "$MAIN" add
|
|
893
|
-
git -C "$MAIN" commit -m "chore(metrics): reconcile skill-runs telemetry before
|
|
894
|
-
if git -C "$MAIN" pull --rebase origin
|
|
895
|
-
git -C "$MAIN" push origin
|
|
896
|
-
&& echo "✅ Reconciled stale metrics log + synced
|
|
897
|
-
|| echo "✅ Reconciled stale metrics log (local
|
|
955
|
+
git -C "$MAIN" add "$METRICS_LOG"
|
|
956
|
+
git -C "$MAIN" commit -m "chore(metrics): reconcile skill-runs telemetry before $TRUNK sync" --quiet
|
|
957
|
+
if git -C "$MAIN" pull --rebase origin "$TRUNK" 2>/dev/null; then
|
|
958
|
+
git -C "$MAIN" push origin "$TRUNK" 2>/dev/null \
|
|
959
|
+
&& echo "✅ Reconciled stale metrics log + synced $TRUNK to origin." \
|
|
960
|
+
|| echo "✅ Reconciled stale metrics log (local $TRUNK 1 commit ahead; syncs on next push). Tree clean."
|
|
898
961
|
else
|
|
899
962
|
git -C "$MAIN" rebase --abort 2>/dev/null || true
|
|
900
|
-
echo "[SYNC-NEEDS-DECISION] metrics rebase conflict on
|
|
963
|
+
echo "[SYNC-NEEDS-DECISION] metrics rebase conflict on $TRUNK — orchestrator MUST raise ONE explicit user gate."
|
|
901
964
|
fi
|
|
902
965
|
else
|
|
903
|
-
echo "[SYNC-NEEDS-DECISION] main repo
|
|
966
|
+
echo "[SYNC-NEEDS-DECISION] main repo $TRUNK cannot fast-forward and the working tree is clean (diverged?) — orchestrator MUST raise ONE explicit user gate."
|
|
904
967
|
fi
|
|
905
968
|
fi
|
|
906
969
|
else
|
|
907
|
-
echo "ℹ️ Main repo HEAD is '$CURRENT_BRANCH' — not switching. Next manual
|
|
908
|
-
git -C "$MAIN" fetch origin
|
|
970
|
+
echo "ℹ️ Main repo HEAD is '$CURRENT_BRANCH' — not switching. Next manual \`git pull\` on $TRUNK will sync."
|
|
971
|
+
git -C "$MAIN" fetch origin "$TRUNK" --quiet
|
|
909
972
|
# Structured marker for upstream orchestrators (e.g. /new Phase 6c).
|
|
910
973
|
# `/mw` standalone keeps fetch-only behavior (terminal isolation); orchestrators
|
|
911
974
|
# parse this marker and route reconciliation through an explicit user gate.
|
|
912
|
-
echo "[SYNC-DEFERRED] main repo HEAD=${CURRENT_BRANCH}, local
|
|
975
|
+
echo "[SYNC-DEFERRED] main repo HEAD=${CURRENT_BRANCH}, local $TRUNK not fast-forwarded — orchestrator workspace-hygiene phase must reconcile."
|
|
913
976
|
fi
|
|
914
977
|
```
|
|
915
978
|
|
|
@@ -941,10 +1004,10 @@ git branch -d "$BRANCH"
|
|
|
941
1004
|
```
|
|
942
1005
|
|
|
943
1006
|
```bash
|
|
944
|
-
# 3. Delete the remote branch. The branch is ALREADY MERGED into
|
|
1007
|
+
# 3. Delete the remote branch. The branch is ALREADY MERGED into the trunk at this
|
|
945
1008
|
# point, so this is routine cleanup — NOT the gated "branch deletion" that needs
|
|
946
1009
|
# owner approval (see AGENTS.md § branch deletion). Report the outcome; never
|
|
947
|
-
# degrade a failure into a passive "
|
|
1010
|
+
# degrade a failure into a passive "you can delete it by hand" note handed to the user.
|
|
948
1011
|
if git push origin --delete "$BRANCH" 2>/dev/null; then
|
|
949
1012
|
echo "✅ Remote branch '$BRANCH' deleted (merged → routine cleanup)."
|
|
950
1013
|
else
|
|
@@ -971,9 +1034,9 @@ Merge complete:
|
|
|
971
1034
|
Branch: <BRANCH> (deleted on remote — merged branch deletion is routine cleanup, never gated)
|
|
972
1035
|
Strategy: <pr | local-push>
|
|
973
1036
|
PR: #<PR_NUMBER> merged via `gh pr merge` ← only in `pr` strategy
|
|
974
|
-
Push: fast-forward to origin
|
|
1037
|
+
Push: fast-forward to origin/<trunk> ← only in `local-push` strategy
|
|
975
1038
|
Worktree: <WORKTREE_PATH> (removed)
|
|
976
|
-
|
|
1039
|
+
<trunk>: remote up to date; local main-repo synced (auto-reconciled framework
|
|
977
1040
|
telemetry if it blocked the ff). Any [SYNC-NEEDS-DECISION] /
|
|
978
1041
|
[SYNC-DEFERRED] marker above is for the orchestrator to gate on —
|
|
979
1042
|
NOT a residue to hand the user as a manual TODO.
|
|
@@ -1010,7 +1073,7 @@ Active worktrees:
|
|
|
1010
1073
|
```
|
|
1011
1074
|
|
|
1012
1075
|
**Stale detection**: Worktrees older than 7 days get a warning.
|
|
1013
|
-
**Merged detection**: If the branch has been merged into
|
|
1076
|
+
**Merged detection**: If the branch has been merged into the trunk branch — read each entry's `trunkBranch`, e.g. `git branch -a --merged "$TRUNK"` — flag as "already merged — safe to /cw".
|
|
1014
1077
|
**Orphan detection**: Worktrees in `git worktree list` but NOT in registry get flagged as "unregistered".
|
|
1015
1078
|
|
|
1016
1079
|
---
|
|
@@ -1028,8 +1091,8 @@ Remove worktrees without the full PR/merge flow. Use for:
|
|
|
1028
1091
|
git worktree list
|
|
1029
1092
|
```
|
|
1030
1093
|
|
|
1031
|
-
Cross-reference with registry. For each worktree, check:
|
|
1032
|
-
- Is the branch merged into
|
|
1094
|
+
Cross-reference with registry. For each worktree, check (using its `trunkBranch`):
|
|
1095
|
+
- Is the branch merged into the trunk branch? → "safe to remove"
|
|
1033
1096
|
- Is the branch pushed to remote? → "has remote backup"
|
|
1034
1097
|
- Are there uncommitted changes? → "has uncommitted work — DANGEROUS"
|
|
1035
1098
|
|
|
@@ -1038,7 +1101,7 @@ Cross-reference with registry. For each worktree, check:
|
|
|
1038
1101
|
```
|
|
1039
1102
|
Worktrees available for cleanup:
|
|
1040
1103
|
|
|
1041
|
-
1. feat/FEAT-0200-menu-ranking — merged into
|
|
1104
|
+
1. feat/FEAT-0200-menu-ranking — merged into trunk ✓ safe
|
|
1042
1105
|
2. feat/BUG-0300-old-fix — not merged, pushed to remote
|
|
1043
1106
|
3. feat/FEAT-0100-abandoned — not merged, uncommitted changes ✗ DANGEROUS
|
|
1044
1107
|
|
|
@@ -1085,16 +1148,16 @@ Cleanup complete:
|
|
|
1085
1148
|
|
|
1086
1149
|
## Safety Rules
|
|
1087
1150
|
|
|
1088
|
-
- **NEVER `git checkout`, `git switch`, `git checkout -b`, or `git branch` on the main repo from inside `/mw` / `/nw` / `/cw`.** The main repo is a shared resource across parallel terminals; touching its `HEAD` preempts other agents and breaks worktree-driven workflows. Many consumer projects declare this as an absolute rule in CLAUDE.md and Claude Code's classifier will pattern-match and block these commands preemptively. Use the configured `git.merge_strategy` for
|
|
1089
|
-
- NEVER merge to
|
|
1151
|
+
- **NEVER `git checkout`, `git switch`, `git checkout -b`, or `git branch` on the main repo from inside `/mw` / `/nw` / `/cw`.** The main repo is a shared resource across parallel terminals; touching its `HEAD` preempts other agents and breaks worktree-driven workflows. Many consumer projects declare this as an absolute rule in CLAUDE.md and Claude Code's classifier will pattern-match and block these commands preemptively. Use the configured `git.merge_strategy` for trunk-branch merges and `git -C <main> pull --ff-only` (only when `HEAD = git.trunk_branch` already) for sync. See `/mw` step 4c.
|
|
1152
|
+
- NEVER merge to a production/release branch — only to the configured trunk branch (`git.trunk_branch`), via the configured `git.merge_strategy` (`pr` → `gh pr merge`; `local-push` → `git push origin <feat>:<git.trunk_branch>`). NEVER via local checkout.
|
|
1090
1153
|
- NEVER force push from a worktree (`--force-with-lease` on the feature branch after rebase is the only allowed force variant).
|
|
1091
1154
|
- NEVER `git add .` or `git add -A` — always explicit file names.
|
|
1092
1155
|
- NEVER remove a worktree with uncommitted changes without explicit user confirmation.
|
|
1093
1156
|
- NEVER delete a branch before successful merge verification.
|
|
1094
|
-
- NEVER cleanup a worktree before confirming
|
|
1157
|
+
- NEVER cleanup a worktree before confirming the trunk branch is stable post-merge.
|
|
1095
1158
|
- If build/lint/tsc fails during /mw, STOP and report. Do not skip checks.
|
|
1096
1159
|
- If merge conflicts during /mw, STOP and ask the user. Do not auto-resolve.
|
|
1097
|
-
- Always verify `.worktrees/` is in `.gitignore` before creating (
|
|
1160
|
+
- Always verify `.worktrees/` is in `.gitignore` before creating it (the nw-docs pre-flight in step 0 enforces this; add it before running if absent).
|
|
1098
1161
|
- Commit lock protocol: Each worktree has its own `COMMIT_LOCK` in its git dir — no cross-worktree interference.
|
|
1099
1162
|
- Port persistence: On dev server restart, reuse the port from registry (grep .env.local for PORT=) instead of picking a new one.
|
|
1100
1163
|
- **NEVER use `git stash` in worktrees.** Stashes are globally shared across all worktrees (`refs/stash` via `git.commondir`). A stash created in one worktree or in the main repo can be popped in another, causing conflicts, data loss, and cascading merge failures (see FEAT-0522 incident). Use explicit file staging only — the file ownership map ensures no overlap between cards. The stash pattern in AGENTS.md/CLAUDE.md applies ONLY to the main repo working tree.
|