@slamb2k/mad-skills 2.0.39 → 2.0.41

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mad-skills",
3
3
  "description": "AI-assisted planning, development and governance tools",
4
- "version": "2.0.39",
4
+ "version": "2.0.41",
5
5
  "author": {
6
6
  "name": "slamb2k",
7
7
  "url": "https://github.com/slamb2k"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slamb2k/mad-skills",
3
- "version": "2.0.39",
3
+ "version": "2.0.41",
4
4
  "description": "Claude Code skills collection — full lifecycle development tools",
5
5
  "type": "module",
6
6
  "repository": {
@@ -249,6 +249,21 @@ Before sending the prompt, substitute these variables:
249
249
 
250
250
  Parse SCAFFOLD_REPORT. If status is "failed", report to user and stop.
251
251
 
252
+ ### Branch Discipline Injection
253
+
254
+ When updating an existing project CLAUDE.md (not creating from template):
255
+
256
+ 1. Check if `## Branch Discipline` already exists:
257
+ ```bash
258
+ grep -q "## Branch Discipline" CLAUDE.md
259
+ ```
260
+ 2. If NOT found, inject the Branch Discipline section before `## Guardrails`:
261
+ - Read the file content
262
+ - Find the line containing `## Guardrails`
263
+ - Insert the Branch Discipline section (from the template) immediately before it
264
+ - If no `## Guardrails` section exists, append the section at the end of the file
265
+ 3. If already present, skip (idempotent)
266
+
252
267
  ---
253
268
 
254
269
  ## Phase 5: Verification & Report
@@ -51,6 +51,20 @@ handles curated facts.
51
51
 
52
52
  {UNIVERSAL_PRINCIPLES}
53
53
 
54
+ ## Branch Discipline
55
+
56
+ - **Always sync to main before starting new work** — run `/sync` or
57
+ `git checkout main && git pull` before creating a feature branch
58
+ - **Never branch from a feature branch** — always branch from an up-to-date `main`
59
+ - **One feature per branch** — don't stack unrelated changes on the same branch
60
+ - **After shipping a PR, sync immediately** — checkout main and pull before
61
+ starting the next task
62
+ - **If a PR is pending review**, switch to main before starting unrelated work —
63
+ don't build on top of an unmerged branch
64
+
65
+ These rules prevent divergent branches that require complex rebases with risk
66
+ of silent conflict resolution.
67
+
54
68
  ## Guardrails
55
69
 
56
70
  - Verify tool output format before chaining into another tool
@@ -141,6 +141,39 @@ Before Stage 1, resolve the PLAN argument into content:
141
141
  - File: `Plan: {file path} ({line count} lines)`
142
142
  - Text: `Plan: inline ({word count} words)`
143
143
 
144
+ ## Pre-Build Branch Check
145
+
146
+ Before starting Stage 1, verify the working tree is suitable for building:
147
+
148
+ 1. **Detect current branch and default branch:**
149
+ ```bash
150
+ CURRENT=$(git branch --show-current)
151
+ DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')
152
+ DEFAULT_BRANCH="${DEFAULT_BRANCH:-main}"
153
+ git fetch origin "$DEFAULT_BRANCH" --quiet 2>/dev/null
154
+ ```
155
+
156
+ 2. **If on a feature branch** (not `main`/`master`/default):
157
+ ```bash
158
+ BEHIND=$(git rev-list --count HEAD..origin/"$DEFAULT_BRANCH" 2>/dev/null || echo 0)
159
+ ```
160
+ If `BEHIND > 0`, warn the user via `AskUserQuestion`:
161
+ ```
162
+ "You're on branch '{CURRENT}' which is {BEHIND} commits behind {DEFAULT_BRANCH}.
163
+ Starting a new feature here risks divergent branches and complex rebases."
164
+ ```
165
+ Options:
166
+ - "Switch to main first (Recommended)" — run `/sync`, then create a new branch
167
+ - "Continue on this branch" — proceed (user accepts the risk)
168
+ - "Cancel" — stop
169
+
170
+ 3. **If on the default branch** and not up to date:
171
+ ```bash
172
+ LOCAL=$(git rev-parse "$DEFAULT_BRANCH")
173
+ REMOTE=$(git rev-parse "origin/$DEFAULT_BRANCH")
174
+ ```
175
+ If `LOCAL != REMOTE`, run `/sync` automatically before proceeding.
176
+
144
177
  ---
145
178
 
146
179
  ## Stage 1: Explore
@@ -1,12 +1,12 @@
1
1
  {
2
- "generated": "2026-03-17T23:37:17.477Z",
2
+ "generated": "2026-03-22T07:57:16.501Z",
3
3
  "count": 10,
4
4
  "skills": [
5
5
  {
6
6
  "name": "brace",
7
7
  "directory": "brace",
8
8
  "description": "'Initialize any project directory with a standard scaffold for AI-assisted development. Creates specs/ and context/ directories, a project CLAUDE.md with development workflow and guardrails, .gitignore, and branch protection. Recommends claude-mem for persistent memory. Idempotent — safe to run on existing projects. Triggers: \"init project\", \"setup brace\", \"brace\", \"initialize\", \"bootstrap\", \"scaffold\".'",
9
- "lines": 445,
9
+ "lines": 460,
10
10
  "hasScripts": false,
11
11
  "hasReferences": true,
12
12
  "hasAssets": true,
@@ -16,7 +16,7 @@
16
16
  "name": "build",
17
17
  "directory": "build",
18
18
  "description": "Context-isolated feature development pipeline. Takes a detailed design/plan as argument and executes the full feature-dev lifecycle (explore, question, architect, implement, review, ship) inside subagents so the primary conversation stays compact. Use when you have a well-defined plan and want autonomous execution with minimal context window consumption.",
19
- "lines": 378,
19
+ "lines": 411,
20
20
  "hasScripts": false,
21
21
  "hasReferences": true,
22
22
  "hasAssets": false,
@@ -66,7 +66,7 @@
66
66
  "name": "rig",
67
67
  "directory": "rig",
68
68
  "description": "'Idempotently bootstrap any repository with standard development tools, hooks, and workflows. Use when starting work on a new repo, onboarding to an existing project, or ensuring a repo has proper CI/CD setup. Configures: git hooks (lefthook), commit message templates, PR templates, and GitHub Actions for lint/format/type-check/build. Prompts for user confirmation before changes. Triggers: \"bootstrap repo\", \"setup hooks\", \"configure CI\", \"rig\", \"standardize repo\".'",
69
- "lines": 343,
69
+ "lines": 363,
70
70
  "hasScripts": false,
71
71
  "hasReferences": true,
72
72
  "hasAssets": true,
@@ -76,7 +76,7 @@
76
76
  "name": "ship",
77
77
  "directory": "ship",
78
78
  "description": "\"Ship changes through the full PR lifecycle. Use after completing feature work to commit, push, create PR, wait for checks, and merge. Handles the entire workflow: syncs with main, creates feature branch if needed, groups commits logically with semantic messages, creates detailed PR, monitors CI, fixes issues, squash merges, and cleans up. Invoke when work is ready to ship.\"",
79
- "lines": 418,
79
+ "lines": 495,
80
80
  "hasScripts": true,
81
81
  "hasReferences": true,
82
82
  "hasAssets": false,
@@ -86,7 +86,7 @@
86
86
  "name": "speccy",
87
87
  "directory": "speccy",
88
88
  "description": "Deep-dive interview skill for creating comprehensive specifications. Reviews existing code and docs, then interviews the user through multiple rounds of targeted questions covering technical implementation, UI/UX, concerns, and tradeoffs. Produces a structured spec in specs/. Use when starting a new feature, system, or major change that needs a spec.",
89
- "lines": 253,
89
+ "lines": 272,
90
90
  "hasScripts": false,
91
91
  "hasReferences": true,
92
92
  "hasAssets": false,
@@ -293,6 +293,26 @@ If "Let me choose", present individual options as multi-select.
293
293
  For each approved item, follow the procedures in
294
294
  `references/configuration-steps.md`.
295
295
 
296
+ ### Branch Discipline in CLAUDE.md
297
+
298
+ If the project has an existing `CLAUDE.md`:
299
+
300
+ 1. Check if `## Branch Discipline` already exists:
301
+ ```bash
302
+ grep -q "## Branch Discipline" CLAUDE.md
303
+ ```
304
+ 2. If NOT found, inject the Branch Discipline section before `## Guardrails`:
305
+ - Read the file content
306
+ - Find the line containing `## Guardrails`
307
+ - Insert the Branch Discipline section immediately before it
308
+ - If no `## Guardrails` section exists, append at the end of the file
309
+ 3. If already present, skip (idempotent)
310
+
311
+ The Branch Discipline content to inject is the `## Branch Discipline` section
312
+ from `skills/brace/references/claude-md-template.md`. Read that file to get
313
+ the exact content — this avoids duplication and ensures both /brace and /rig
314
+ inject identical text.
315
+
296
316
  ---
297
317
 
298
318
  ## Phase 5: Verification
@@ -318,16 +318,51 @@ The fix subagent MUST commit and push before returning. Once it returns,
318
318
  attempt = 0
319
319
  while attempt < 2:
320
320
  CHECKS = run_watch()
321
- if CHECKS.status == "all_passed" or CHECKS.status == "no_checks":
321
+ if CHECKS.status == "all_passed":
322
322
  break → proceed immediately to Stage 5 (do NOT ask user to confirm merge)
323
+ if CHECKS.status == "no_checks":
324
+ → prompt user via AskUserQuestion:
325
+ "No CI checks found for PR #{PR_NUMBER} after waiting 30 seconds.
326
+ The repository may not have CI configured, or checks may still be registering."
327
+ Options:
328
+ - "Merge without checks (Recommended)" → break to Stage 5
329
+ - "Wait another 30 seconds" → re-run the watch (do not increment attempt)
330
+ - "Cancel" → stop /ship and display failure banner
331
+ break (if user chose merge or after re-wait resolves)
323
332
  attempt += 1
324
333
  run_fix(CHECKS.failing_checks)
325
334
  → loop back to watch
326
335
 
327
336
  if attempt == 2 and still failing:
328
- report failures to user, stop
337
+ display failure banner and stop (see Failure Handling below)
329
338
  ```
330
339
 
340
+ ### Failure Handling
341
+
342
+ When /ship fails at ANY point (CI exhausts fix attempts, merge fails, post-merge
343
+ verification fails), display the failure banner and STOP:
344
+
345
+ ```
346
+ ┌─ Ship · FAILED ──────────────────────────────────
347
+
348
+ │ ❌ PR #{PR_NUMBER} was NOT merged
349
+
350
+ │ Reason: {specific failure reason}
351
+ │ Branch: {BRANCH} (still active)
352
+
353
+ │ ⚠️ You are still on branch '{BRANCH}'.
354
+ │ Run /sync to return to main before starting new work.
355
+
356
+ └───────────────────────────────────────────────────
357
+ ```
358
+
359
+ **Critical rules on failure:**
360
+ - Do NOT proceed to "What's Next?"
361
+ - Do NOT suggest next tasks or follow-up work
362
+ - Do NOT invoke `/sync` or any other skill
363
+ - Do NOT use language like "will be auto-merged" or "PR is pending"
364
+ - The failure banner is the LAST output — nothing follows it
365
+
331
366
  ---
332
367
 
333
368
  ## Stage 5: Merge & Final Sync
@@ -352,6 +387,34 @@ bash "$SKILL_ROOT/skills/ship/scripts/merge.sh" \
352
387
 
353
388
  Parse LAND_REPORT from output markers. Exit code 0=merged, 1=failed.
354
389
 
390
+ ### 5a-verify. Verify merge completed
391
+
392
+ After merge.sh reports success, verify the PR actually merged:
393
+
394
+ **GitHub:**
395
+ ```bash
396
+ PR_STATE=$(gh pr view "$PR_NUMBER" --json state --jq '.state')
397
+ ```
398
+ Expected: `"MERGED"`
399
+
400
+ **AzDO CLI:**
401
+ ```bash
402
+ PR_STATE=$(az repos pr show --id "$PR_NUMBER" --query status -o tsv)
403
+ ```
404
+ Expected: `"completed"`
405
+
406
+ **AzDO REST:**
407
+ ```bash
408
+ PR_RESP=$(curl -s -H "$AUTH" "${AZDO_ORG_URL}/${AZDO_PROJECT_URL_SAFE}/_apis/git/repositories/${REPO_ID}/pullRequests/${PR_NUMBER}?api-version=7.1")
409
+ PR_STATE=$(echo "$PR_RESP" | jq -r '.status')
410
+ ```
411
+ Expected: `"completed"`
412
+
413
+ If the PR is NOT in the expected merged/completed state, treat as a failure —
414
+ display the failure banner (see Failure Handling) with reason "PR merge was
415
+ accepted but PR is still in '{PR_STATE}' state. The merge may be queued or
416
+ deferred." and stop.
417
+
355
418
  ### 5b. Sync local repo
356
419
 
357
420
  After the merge script succeeds, run the sync script to checkout the default
@@ -361,10 +424,23 @@ branch, pull the merge commit, and **clean up stale branches**:
361
424
  bash "$SKILL_ROOT/skills/sync/scripts/sync.sh" "{REMOTE}" "{DEFAULT_BRANCH}"
362
425
  ```
363
426
 
427
+ After sync completes, verify the working tree is on the default branch:
428
+
429
+ ```bash
430
+ CURRENT=$(git branch --show-current)
431
+ if [ "$CURRENT" != "{DEFAULT_BRANCH}" ]; then
432
+ git checkout {DEFAULT_BRANCH}
433
+ git pull {REMOTE} {DEFAULT_BRANCH}
434
+ fi
435
+ ```
436
+
364
437
  ---
365
438
 
366
439
  ## What's Next?
367
440
 
441
+ **Only run this section if /ship succeeded (PR is merged).** If any failure
442
+ occurred, the failure banner was already displayed and nothing should follow it.
443
+
368
444
  After a successful merge, determine what work comes next by checking these
369
445
  sources (in priority order):
370
446
 
@@ -393,6 +469,7 @@ Compile all stage reports into a summary:
393
469
  │ 🌿 Branch: {branch}
394
470
  │ 🔗 PR: {pr_url}
395
471
  │ 🔀 Merged: {merge_commit} ({merge_type})
472
+ │ 🌿 Now on: {DEFAULT_BRANCH} (up to date)
396
473
 
397
474
  │ 📝 Commits
398
475
  │ • {commit message 1}
@@ -258,6 +258,12 @@ FAILING CHECKS: {FAILING_CHECKS}
258
258
  )"
259
259
  git push
260
260
 
261
+ CRITICAL — after pushing, your job is DONE. Return immediately.
262
+ - Do NOT manually trigger, queue, or run any CI builds or pipelines
263
+ - Do NOT use `az pipelines run`, `gh workflow run`, or any build-triggering command
264
+ - The PR build policy will trigger automatically on push
265
+ - The orchestrator will poll for the new build via ci-watch.sh
266
+
261
267
  ## Output Format
262
268
 
263
269
  FIX_REPORT:
@@ -22,12 +22,14 @@ for arg in "$@"; do
22
22
  done
23
23
 
24
24
  STATUS="" CHECKS="" FAILING=""
25
+ GRACE_POLLS=0
25
26
 
26
27
  emit_report() {
27
28
  echo "CHECKS_REPORT_BEGIN"
28
29
  echo "status=$STATUS"
29
30
  echo "checks=$CHECKS"
30
31
  echo "failing_checks=${FAILING:-none}"
32
+ echo "grace_period_polls=$GRACE_POLLS"
31
33
  echo "CHECKS_REPORT_END"
32
34
  }
33
35
 
@@ -38,15 +40,29 @@ if [ "$PLATFORM" = "github" ]; then
38
40
  emit_report; exit 3
39
41
  fi
40
42
 
43
+ # Grace period: wait for checks to register (CI may not trigger immediately)
44
+ GRACE_POLLS=0
45
+ GRACE_JSON="[]"
46
+ for _ in $(seq 1 3); do
47
+ GRACE_POLLS=$((GRACE_POLLS + 1))
48
+ GRACE_JSON=$(gh pr checks "$PR_NUMBER" --json name,state 2>/dev/null || echo "[]")
49
+ if [ "$GRACE_JSON" != "[]" ]; then
50
+ break
51
+ fi
52
+ sleep 10
53
+ done
54
+
55
+ # If no checks found after grace period, report and exit
56
+ if [ "$GRACE_JSON" = "[]" ]; then
57
+ STATUS="no_checks"; CHECKS=""; FAILING="none"
58
+ emit_report; exit 0
59
+ fi
60
+
41
61
  # gh pr checks --watch blocks until done; --fail-fast stops on first failure
42
62
  gh pr checks "$PR_NUMBER" --watch --fail-fast 2>/dev/null || true
43
63
 
44
64
  # Parse final status
45
65
  CHECKS_JSON=$(gh pr checks "$PR_NUMBER" --json name,state 2>/dev/null || echo "[]")
46
- if [ "$CHECKS_JSON" = "[]" ]; then
47
- STATUS="no_checks"; CHECKS=""; FAILING="none"
48
- emit_report; exit 0
49
- fi
50
66
 
51
67
  FAIL_COUNT=$(echo "$CHECKS_JSON" | jq '[.[] | select(.state=="FAILURE")] | length')
52
68
  CHECKS=$(echo "$CHECKS_JSON" | jq -r '.[] | "\(.name):\(.state | ascii_downcase)"' | paste -sd, -)
@@ -77,9 +93,11 @@ fi
77
93
 
78
94
  # ── AzDO CLI mode ──────────────────────────────────────
79
95
  if [ "$AZDO_MODE" = "cli" ]; then
80
- # Wait for CI to start (max 2 min)
96
+ # Grace period: wait for CI to start (max 2 min)
81
97
  RUNS_FOUND=false
98
+ GRACE_POLLS=0
82
99
  for _ in $(seq 1 8); do
100
+ GRACE_POLLS=$((GRACE_POLLS + 1))
83
101
  RUN_COUNT=$(az pipelines runs list --branch "$BRANCH" --top 5 \
84
102
  --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
85
103
  --query "length(@)" -o tsv 2>/dev/null)
@@ -147,9 +165,11 @@ fi
147
165
  if [ "$AZDO_MODE" = "rest" ]; then
148
166
  BUILDS_URL="$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/build/builds?branchName=refs/heads/$BRANCH&\$top=5&api-version=7.0"
149
167
 
150
- # Wait for CI to start (max 2 min)
168
+ # Grace period: wait for CI to start (max 2 min)
151
169
  RUNS_FOUND=false
170
+ GRACE_POLLS=0
152
171
  for _ in $(seq 1 8); do
172
+ GRACE_POLLS=$((GRACE_POLLS + 1))
153
173
  RUN_COUNT=$(curl -s -H "$AUTH" "$BUILDS_URL" | jq '.value | length')
154
174
  if [ -n "$RUN_COUNT" ] && [ "$RUN_COUNT" != "0" ]; then
155
175
  RUNS_FOUND=true; break
@@ -65,16 +65,34 @@ DELETE_FLAG=$( [ "$DELETE_BRANCH" = true ] && echo "true" || echo "false" )
65
65
 
66
66
  # ── AzDO CLI mode ──────────────────────────────────────
67
67
  if [ "$AZDO_MODE" = "cli" ]; then
68
- # Check for rejected policies
69
- REJECTED=$(az repos pr policy list --id "$PR_NUMBER" \
70
- --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
71
- --query "[?status=='rejected'] | length(@)" -o tsv 2>/dev/null)
72
- if [ -n "$REJECTED" ] && [ "$REJECTED" != "0" ]; then
73
- STATUS="failed"
74
- ERRORS="$REJECTED PR policies are rejected"
75
- MERGE_COMMIT=""; BRANCH_DELETED=false
76
- emit_report; exit 1
77
- fi
68
+ # Wait for all policies to reach terminal state (approved/rejected/notApplicable)
69
+ POLICY_TIMEOUT=20 # 20 iterations × 15 seconds = 5 minutes
70
+ for POLICY_ITER in $(seq 1 $POLICY_TIMEOUT); do
71
+ POLICY_JSON=$(az repos pr policy list --id "$PR_NUMBER" --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" -o json 2>/dev/null || echo "[]")
72
+
73
+ REJECTED=$(echo "$POLICY_JSON" | jq '[.[] | select(.status=="rejected")] | length')
74
+ PENDING=$(echo "$POLICY_JSON" | jq '[.[] | select(.status=="running" or .status=="queued" or .status=="pending")] | length')
75
+
76
+ if [ "${REJECTED:-0}" -gt 0 ]; then
77
+ STATUS="failed"
78
+ ERRORS="policies rejected"
79
+ MERGE_COMMIT=""; BRANCH_DELETED=false
80
+ emit_report; exit 1
81
+ fi
82
+
83
+ if [ "${PENDING:-0}" -eq 0 ]; then
84
+ break # All policies terminal
85
+ fi
86
+
87
+ if [ "$POLICY_ITER" -eq "$POLICY_TIMEOUT" ]; then
88
+ STATUS="failed"
89
+ ERRORS="policies not evaluated after 5 minutes"
90
+ MERGE_COMMIT=""; BRANCH_DELETED=false
91
+ emit_report; exit 1
92
+ fi
93
+
94
+ sleep 15
95
+ done
78
96
 
79
97
  # Complete the PR
80
98
  if az repos pr update --id "$PR_NUMBER" --status completed \
@@ -111,16 +129,35 @@ if [ "$AZDO_MODE" = "rest" ]; then
111
129
  REPO_NAME=$(basename -s .git "$(git remote get-url origin)")
112
130
  PR_API="$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/git/repositories/$REPO_NAME/pullrequests/$PR_NUMBER"
113
131
 
114
- # Check for rejected policies
115
- EVALS=$(curl -s -H "$AUTH" \
116
- "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/policy/evaluations?artifactId=vstfs:///CodeReview/CodeReviewId/$AZDO_PROJECT_URL_SAFE/$PR_NUMBER&api-version=7.0")
117
- REJECTED=$(echo "$EVALS" | jq '[.value[] | select(.status=="rejected")] | length')
118
- if [ "$REJECTED" != "0" ]; then
119
- STATUS="failed"
120
- ERRORS="PR has rejected policy evaluations"
121
- MERGE_COMMIT=""; BRANCH_DELETED=false
122
- emit_report; exit 1
123
- fi
132
+ # Wait for all policy evaluations to reach terminal state
133
+ POLICY_TIMEOUT=20
134
+ for POLICY_ITER in $(seq 1 $POLICY_TIMEOUT); do
135
+ EVALS=$(curl -s -H "$AUTH" \
136
+ "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/policy/evaluations?artifactId=vstfs:///CodeReview/CodeReviewId/$AZDO_PROJECT_URL_SAFE/$PR_NUMBER&api-version=7.0" 2>/dev/null || echo '{"value":[]}')
137
+
138
+ REJECTED=$(echo "$EVALS" | jq '[.value[] | select(.status=="rejected")] | length')
139
+ PENDING=$(echo "$EVALS" | jq '[.value[] | select(.status=="running" or .status=="queued" or .status=="pending")] | length')
140
+
141
+ if [ "${REJECTED:-0}" -gt 0 ]; then
142
+ STATUS="failed"
143
+ ERRORS="PR has rejected policy evaluations"
144
+ MERGE_COMMIT=""; BRANCH_DELETED=false
145
+ emit_report; exit 1
146
+ fi
147
+
148
+ if [ "${PENDING:-0}" -eq 0 ]; then
149
+ break
150
+ fi
151
+
152
+ if [ "$POLICY_ITER" -eq "$POLICY_TIMEOUT" ]; then
153
+ STATUS="failed"
154
+ ERRORS="policies not evaluated after 5 minutes"
155
+ MERGE_COMMIT=""; BRANCH_DELETED=false
156
+ emit_report; exit 1
157
+ fi
158
+
159
+ sleep 15
160
+ done
124
161
 
125
162
  # Resolve merge strategy
126
163
  MERGE_STRATEGY=$( [ "$SQUASH" = true ] && echo "squash" || echo "noFastForward" )
@@ -84,6 +84,25 @@ For each row, in order:
84
84
 
85
85
  ## Stage 1: Context Gathering
86
86
 
87
+ ### Pre-Spec Branch Check
88
+
89
+ Before gathering context, check if the user is on a stale branch:
90
+
91
+ ```bash
92
+ CURRENT=$(git branch --show-current)
93
+ if [ "$CURRENT" != "main" ] && [ "$CURRENT" != "master" ]; then
94
+ git fetch origin main --quiet 2>/dev/null
95
+ BEHIND=$(git rev-list --count HEAD..origin/main 2>/dev/null || echo 0)
96
+ if [ "$BEHIND" -gt 5 ]; then
97
+ echo "⚠️ Branch '$CURRENT' is $BEHIND commits behind main."
98
+ echo " Consider running /sync before building from this spec."
99
+ fi
100
+ fi
101
+ ```
102
+
103
+ This is advisory only (specs don't modify code) — do not block, continue
104
+ regardless of the result.
105
+
87
106
  Before asking any questions, build a thorough understanding of the project:
88
107
 
89
108
  1. **Capture GOAL** — the user's argument describing what needs to be specified