forgedock 1.0.21 → 1.1.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.
Files changed (76) hide show
  1. package/README.md +227 -174
  2. package/bin/cinema.mjs +320 -0
  3. package/bin/demo.mjs +386 -0
  4. package/bin/engine/phases.mjs +148 -0
  5. package/bin/engine/projector.mjs +32 -0
  6. package/bin/engine/reconcile.mjs +11 -0
  7. package/bin/engine/runlog.mjs +79 -0
  8. package/bin/engine/state.mjs +27 -0
  9. package/bin/engine-cli.mjs +50 -0
  10. package/bin/engine.mjs +138 -0
  11. package/bin/forgedock.mjs +693 -1393
  12. package/bin/init-detect.mjs +95 -0
  13. package/bin/journey.mjs +783 -0
  14. package/bin/runner.mjs +847 -0
  15. package/bin/settings-hook.mjs +112 -0
  16. package/bin/tui.mjs +6 -3
  17. package/commands/autopilot.md +4 -4
  18. package/commands/cleanup.md +85 -39
  19. package/commands/deploy-info.md +11 -5
  20. package/commands/design-bench.md +1 -0
  21. package/commands/design-render-critique-loop.md +1 -0
  22. package/commands/design.md +1 -0
  23. package/commands/forge-stats.md +2 -1
  24. package/commands/issue.md +1 -1
  25. package/commands/optimize.md +1 -1
  26. package/commands/orchestrate.md +346 -34
  27. package/commands/quality-gate.md +168 -80
  28. package/commands/review-pr-agents.md +69 -45
  29. package/commands/review-pr.md +54 -6
  30. package/commands/scope.md +11 -3
  31. package/commands/security-audit.md +24 -16
  32. package/commands/sync-ecosystem.md +2 -2
  33. package/commands/work-on/build/architect.md +47 -3
  34. package/commands/work-on/build/context.md +36 -5
  35. package/commands/work-on/build/implement.md +16 -4
  36. package/commands/work-on/build/validate.md +54 -8
  37. package/commands/work-on/build.md +27 -9
  38. package/commands/work-on/close.md +131 -3
  39. package/commands/work-on/investigate.md +24 -1
  40. package/commands/work-on/review.md +24 -0
  41. package/commands/work-on-monolithic.md +1 -0
  42. package/commands/work-on.md +271 -11
  43. package/docs/BRAND.md +6 -2
  44. package/docs/CONFIG.md +39 -34
  45. package/docs/site/.vitepress/config.mjs +85 -0
  46. package/docs/site/annotations-explained.md +55 -0
  47. package/docs/site/command-learning-path.md +104 -0
  48. package/docs/site/command-reference.md +3 -0
  49. package/docs/site/getting-started.md +42 -13
  50. package/docs/site/index.md +1 -1
  51. package/docs/site/troubleshooting.md +208 -0
  52. package/docs/spec/forge-protocol-v1.md +768 -0
  53. package/docs/superpowers/plans/2026-07-03-cinematic-onboarding-tui.md +2339 -0
  54. package/docs/superpowers/plans/2026-07-03-durable-execution-engine.md +1223 -0
  55. package/docs/superpowers/specs/2026-07-03-cinematic-onboarding-tui-design.md +157 -0
  56. package/docs/superpowers/specs/2026-07-03-durable-execution-engine-design.md +194 -0
  57. package/examples/forgedock-demo/README.md +113 -0
  58. package/examples/forgedock-demo/bootstrap.sh +125 -0
  59. package/examples/forgedock-demo/issues/01-bug-delete-missing-auth.md +37 -0
  60. package/examples/forgedock-demo/issues/02-feature-filter-notes.md +37 -0
  61. package/examples/forgedock-demo/issues/03-refactor-router-table.md +33 -0
  62. package/examples/forgedock-demo/issues/04-perf-index-lookup.md +35 -0
  63. package/examples/forgedock-demo/issues/05-docs-api-reference.md +39 -0
  64. package/examples/forgedock-demo/labels.json +21 -0
  65. package/examples/forgedock-demo/package.json +16 -0
  66. package/examples/forgedock-demo/scripts/smoke.js +61 -0
  67. package/examples/forgedock-demo/src/auth.js +28 -0
  68. package/examples/forgedock-demo/src/db.js +66 -0
  69. package/examples/forgedock-demo/src/routes/notes.js +64 -0
  70. package/examples/forgedock-demo/src/server.js +79 -0
  71. package/package.json +13 -3
  72. package/scripts/verify-env-vars.sh +18 -2
  73. package/scripts/verify-host-headers.sh +4 -4
  74. package/scripts/verify-route-registration.sh +2 -2
  75. package/scripts/worktree-lifecycle.sh +202 -0
  76. package/templates/forge.yaml.minimal +44 -0
@@ -176,6 +176,11 @@ if [ -n "$DEVDOCS_PATH" ] && [ -f "$INDEX_PATH" ]; then
176
176
  ISSUE_LABELS=$(gh issue view {NUMBER} -R {GH_REPO} --json labels \
177
177
  --jq '[.labels[].name] | join(" ")' 2>/dev/null || echo "")
178
178
  echo "Issue labels: ${ISSUE_LABELS:-none}"
179
+ # Separate newline-joined list for the loop below — GitHub label names CAN
180
+ # contain spaces (e.g. "good first issue"), so the space-joined $ISSUE_LABELS
181
+ # above is only safe for display, never for iteration.
182
+ ISSUE_LABELS_LIST=$(gh issue view {NUMBER} -R {GH_REPO} --json labels \
183
+ --jq '.labels[].name' 2>/dev/null || echo "")
179
184
 
180
185
  # Read index content
181
186
  INDEX_CONTENT=$(cat "$INDEX_PATH" 2>/dev/null || echo "")
@@ -190,7 +195,12 @@ if [ -n "$DEVDOCS_PATH" ] && [ -f "$INDEX_PATH" ]; then
190
195
 
191
196
  # Extract domain blocks matching any issue label keyword
192
197
  DOMAIN_PATHS=""
193
- for label in $ISSUE_LABELS; do
198
+ # $ISSUE_LABELS_LIST is one label per line — herestring (not a piped
199
+ # `| while read`, which would run the loop body in a subshell and discard
200
+ # DOMAIN_PATHS once the loop exits) preserves newline-safety for labels
201
+ # containing spaces.
202
+ while IFS= read -r label; do
203
+ [ -z "$label" ] && continue
194
204
  KEYWORD=$(echo "$label" | sed 's/^workflow://; s/^priority://; s/^review-finding$//')
195
205
  [ -z "$KEYWORD" ] && continue
196
206
  # Sanitize KEYWORD before AWK injection — strip chars that would break awk regex delimiters
@@ -207,7 +217,7 @@ if [ -n "$DEVDOCS_PATH" ] && [ -f "$INDEX_PATH" ]; then
207
217
  echo "Domain '${SAFE_KEYWORD}' matched — adding docs: $(echo "$BLOCK_PATHS" | tr '\n' ' ')"
208
218
  DOMAIN_PATHS="${DOMAIN_PATHS}${BLOCK_PATHS}"$'\n'
209
219
  fi
210
- done
220
+ done <<< "$ISSUE_LABELS_LIST"
211
221
 
212
222
  # Combine always_load + domain paths (deduplicate)
213
223
  ALL_PATHS=$(printf "%s\n%s" "$ALWAYS_LOAD_PATHS" "$DOMAIN_PATHS" \
@@ -448,11 +458,45 @@ For each non-obvious interaction or potential failure mode:
448
458
  - Suggest a mitigation or check
449
459
 
450
460
  Focus on:
451
- - Files that have changed recently (check `git log --oneline -10 -- {FILE}`)
461
+ - Churn / hot-spot files (see below) — high historical change frequency correlates with defect density
452
462
  - Paths that are called in both sync and async contexts
453
463
  - Any place where the change touches a serialization boundary (JSON in/out, DB read/write)
454
464
  - Unused or dormant code that the change might activate
455
465
 
466
+ ### Churn (Hot-Spot) Signal
467
+
468
+ Recency alone (did the file appear in the last N commits?) is a weak, binary proxy. Compute a bounded per-file **churn tier** instead — how often each affected file has actually changed over a fixed window — and fold it into the risk table below.
469
+
470
+ For each file in `{AFFECTED_FILES}` (never repo-wide — this must stay O(affected files) to fit the Phase A0–A5 time budget):
471
+
472
+ ```bash
473
+ CHURN_WINDOW="90 days ago" # named constant — must match the same window used in review-pr.md Phase 3A
474
+
475
+ # {AFFECTED_FILES} is a space-separated argument (see --files contract at the top
476
+ # of this file), not newline-separated like review-pr.md's $FILES — so a herestring
477
+ # `while read` line-loop would misparse it as a single line. Split explicitly on
478
+ # IFS=' ' into an array instead of relying on a bare `for FILE in {AFFECTED_FILES}`
479
+ # (which word-splits on the shell's default IFS — space, tab, AND newline). This
480
+ # keeps the existing space-separated contract intact while narrowing the splitting
481
+ # surface to spaces only.
482
+ IFS=' ' read -ra AFFECTED_FILES_ARR <<< "{AFFECTED_FILES}"
483
+ for FILE in "${AFFECTED_FILES_ARR[@]}"; do
484
+ COMMITS=$(git log --oneline --since="$CHURN_WINDOW" -- "$FILE" | wc -l)
485
+ if [ "$COMMITS" -ge 15 ]; then TIER="HOT"
486
+ elif [ "$COMMITS" -ge 5 ]; then TIER="MEDIUM"
487
+ else TIER="LOW"
488
+ fi
489
+ echo "$FILE: $TIER ($COMMITS commits in last 90 days)"
490
+ done
491
+ ```
492
+
493
+ **Tiers** (fixed thresholds — keep identical to `review-pr.md` Phase 3A so both phases agree on what "hot" means):
494
+ - **HOT**: 15+ commits in the window
495
+ - **MEDIUM**: 5–14 commits in the window
496
+ - **LOW**: 0–4 commits in the window
497
+
498
+ Any file classified **HOT** MUST get an explicit row in the Risk Assessment table below (e.g. "High historical churn — {N} commits in last 90 days — changes here have a higher defect-density prior"), rated at least MEDIUM severity, with a mitigation such as "extra reviewer scrutiny" or "prefer smaller, isolated diffs to this file." MEDIUM-tier files may be noted inline without a dedicated row. LOW-tier files need no mention.
499
+
456
500
  ---
457
501
 
458
502
  ## Output Format
@@ -125,6 +125,11 @@ if [ -n "$DEVDOCS_PATH" ] && [ -f "$INDEX_PATH" ]; then
125
125
  ISSUE_LABELS=$(gh issue view {NUMBER} -R {GH_REPO} --json labels \
126
126
  --jq '[.labels[].name] | join(" ")' 2>/dev/null || echo "")
127
127
  echo "Issue labels: ${ISSUE_LABELS:-none}"
128
+ # Separate newline-joined list for the loop below — GitHub label names CAN
129
+ # contain spaces (e.g. "good first issue"), so the space-joined $ISSUE_LABELS
130
+ # above is only safe for display, never for iteration.
131
+ ISSUE_LABELS_LIST=$(gh issue view {NUMBER} -R {GH_REPO} --json labels \
132
+ --jq '.labels[].name' 2>/dev/null || echo "")
128
133
 
129
134
  # Read index content
130
135
  INDEX_CONTENT=$(cat "$INDEX_PATH" 2>/dev/null || echo "")
@@ -139,7 +144,12 @@ if [ -n "$DEVDOCS_PATH" ] && [ -f "$INDEX_PATH" ]; then
139
144
 
140
145
  # Extract domain blocks matching any issue label keyword
141
146
  DOMAIN_PATHS=""
142
- for label in $ISSUE_LABELS; do
147
+ # $ISSUE_LABELS_LIST is one label per line — herestring (not a piped
148
+ # `| while read`, which would run the loop body in a subshell and discard
149
+ # DOMAIN_PATHS once the loop exits) preserves newline-safety for labels
150
+ # containing spaces.
151
+ while IFS= read -r label; do
152
+ [ -z "$label" ] && continue
143
153
  # Strip workflow:, priority:, review-finding prefixes — use bare keyword
144
154
  KEYWORD=$(echo "$label" | sed 's/^workflow://; s/^priority://; s/^review-finding$//')
145
155
  [ -z "$KEYWORD" ] && continue
@@ -158,7 +168,7 @@ if [ -n "$DEVDOCS_PATH" ] && [ -f "$INDEX_PATH" ]; then
158
168
  echo "Domain '${SAFE_KEYWORD}' matched — adding docs: $(echo "$BLOCK_PATHS" | tr '\n' ' ')"
159
169
  DOMAIN_PATHS="${DOMAIN_PATHS}${BLOCK_PATHS}"$'\n'
160
170
  fi
161
- done
171
+ done <<< "$ISSUE_LABELS_LIST"
162
172
 
163
173
  # Combine always_load + domain paths (deduplicate)
164
174
  ALL_PATHS=$(printf "%s\n%s" "$ALWAYS_LOAD_PATHS" "$DOMAIN_PATHS" \
@@ -397,7 +407,12 @@ If `GIST_SUMMARIES` is non-empty, it will be included in the `### Prior Investig
397
407
  Query closed issues with `review-finding` label, searching by filename:
398
408
 
399
409
  ```bash
400
- for file in {AFFECTED_FILES}; do
410
+ # {AFFECTED_FILES} is a space-separated file path list (see contract note above)
411
+ # — split explicitly on IFS=' ' into an array instead of a bare
412
+ # `for file in {AFFECTED_FILES}`, which word-splits on the shell's default IFS
413
+ # (space, tab, AND newline) and would corrupt any path containing a space.
414
+ IFS=' ' read -ra AFFECTED_FILES_ARR <<< "{AFFECTED_FILES}"
415
+ for file in "${AFFECTED_FILES_ARR[@]}"; do
401
416
  basename=$(basename "$file" .py)
402
417
  gh issue list -R {GH_REPO} \
403
418
  --state closed \
@@ -425,7 +440,7 @@ Keep findings where the filename or function name appears in the title or body.
425
440
 
426
441
  ## Phase C2: Past Bugs in the Same Module
427
442
 
428
- Mine git log for commit messages referencing issues, then fetch those issues:
443
+ Mine git log for commit messages referencing issues, then fetch those issues. Also read commit bodies/diffs directly — local git history is near-free relative to `gh api` round-trips, and commit bodies often explain the "why" without needing to fetch the linked issue at all.
429
444
 
430
445
  ```bash
431
446
  # Step 1: find issue numbers from git history on affected files
@@ -444,6 +459,20 @@ Filter: keep only `bug`, `fix`, or `review-finding` labeled issues. Skip feature
444
459
 
445
460
  **Max results**: 5 issues.
446
461
 
462
+ **Direct commit-body read (bounded, prefer over `gh api` when it already answers "why")**: For the top 5 commits touching `{AFFECTED_FILES}`, read the commit subject + body directly instead of round-tripping to `gh issue view` when the body already explains the change:
463
+ ```bash
464
+ git log -5 --format='%h %ad %s%n%b' --date=short -- {AFFECTED_FILES}
465
+ ```
466
+ If a commit body fully explains the prior bug/fix (common for squashed or fix-up commits with no `#NNN` reference), use it directly as a "Past Bug in This Module" entry — do not require a linked GitHub issue to exist.
467
+
468
+ **Pickaxe pass (has this exact area been fixed before?)** — one bounded pass, capped at 5 hits, keyed on the suspected symbol/string from the Builder Contract or investigation report:
469
+ ```bash
470
+ git log -S"{suspected_symbol_or_string}" --oneline -- {AFFECTED_FILES} | head -5
471
+ # Use -G instead of -S for regex patterns
472
+ git log -G"{pattern}" --oneline -- {AFFECTED_FILES} | head -5
473
+ ```
474
+ Any hit is a candidate prior fix or reintroduced defect for this exact code area — read `git show {hash}` to confirm scope before including it in the output. This catches regressions the issue-number harvest above misses (e.g. a defect fixed via a squashed commit with no `#NNN` reference).
475
+
447
476
  ---
448
477
 
449
478
  ## Phase C3: Related Code Paths
@@ -526,8 +555,10 @@ gh issue comment {NUMBER} -R {GH_REPO} --body "<!-- FORGE:CONTEXT -->
526
555
  - #{NUM}: \"{TITLE}\" — root cause: {ROOT_CAUSE}
527
556
 
528
557
  ### Past Bugs in This Module
529
- <!-- List of closed bug issues from git log mining. If none: 'No prior bugs found in git history.' -->
558
+ <!-- List of closed bug issues from git log mining, PLUS pickaxe-derived findings (commits with no linked issue, or
559
+ commit bodies read directly per the C2 direct-commit-body step). If none: 'No prior bugs found in git history.' -->
530
560
  - #{NUM}: \"{TITLE}\" — root cause: {SNIPPET}
561
+ - {COMMIT_HASH} (no linked issue): {COMMIT_SUBJECT} — {WHY_RELEVANT, from commit body or pickaxe hit}
531
562
 
532
563
  ### Related Code Paths (must stay consistent)
533
564
  <!-- Files that import or call the changed functions. Builder must read and validate these. -->
@@ -53,7 +53,17 @@ gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
53
53
  --jq '.[] | select(.body | contains("FORGE:CONTEXT")) | .body'
54
54
  ```
55
55
 
56
- **Resume check**: If `<!-- FORGE:BUILDER -->` comment already exists, the implementation is done — EXIT and return `IMPLEMENT_RESULT: status: ALREADY_DONE` to caller.
56
+ **Resume check**:
57
+ - If `<!-- FORGE:BUILDER:COMPLETE -->` is present in a BUILDER comment → implementation is done — EXIT and return `IMPLEMENT_RESULT: status: ALREADY_DONE` to caller.
58
+ - If `<!-- FORGE:BUILDER -->` exists BUT `<!-- FORGE:BUILDER:COMPLETE -->` is ABSENT → implementation was interrupted after the comment was posted but before the commit (validate.md V5). Delete the partial comment and restart from Phase I2:
59
+ ```bash
60
+ PARTIAL_ID=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
61
+ --jq '[.[] | select(.body | contains("FORGE:BUILDER") and (contains("FORGE:BUILDER:COMPLETE") | not))] | last | .id // ""')
62
+ if [ -n "$PARTIAL_ID" ]; then
63
+ gh api repos/{GH_REPO}/issues/comments/$PARTIAL_ID -X DELETE
64
+ echo "Deleted partial FORGE:BUILDER comment (no FORGE:BUILDER:COMPLETE) — restarting implementation"
65
+ fi
66
+ ```
57
67
 
58
68
  **Primary guide**: If `<!-- FORGE:ARCHITECT -->` is present, it is the **primary implementation input**. Follow its ordered implementation list exactly — it defines which files change, in what order, and what consistency checks must pass. The investigation report and contract are secondary context.
59
69
 
@@ -225,6 +235,8 @@ gh issue edit {NUMBER} {GH_FLAG} --body "{UPDATED_BODY}"
225
235
 
226
236
  ## Phase I6: Post FORGE:BUILDER Comment
227
237
 
238
+ Post the implementation summary comment. **Do NOT include `<!-- FORGE:BUILDER:COMPLETE -->` here** — that marker is posted by `validate.md` Phase V5 after the commit succeeds. A crash between I6 and V5 would otherwise leave a completion-marked comment with no commit on the branch.
239
+
228
240
  ```bash
229
241
  gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:BUILDER -->
230
242
  ## Implementation Complete
@@ -247,11 +259,11 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:BUILDER -->
247
259
  - [ ] {Test scenario 2} [type:unit]
248
260
  - [ ] {Test scenario 3} [type:e2e]
249
261
 
250
- > **Test-type annotation** (optional): Append `[type:api]`, `[type:unit]`, `[type:e2e]`, or `[type:manual]` to each checklist item. The test gate reads this annotation directly and skips regex inference. Omit it to rely on regex classification fallback.
251
-
252
- <!-- FORGE:BUILDER:COMPLETE -->"
262
+ > **Test-type annotation** (optional): Append `[type:api]`, `[type:unit]`, `[type:e2e]`, or `[type:manual]` to each checklist item. The test gate reads this annotation directly and skips regex inference. Omit it to rely on regex classification fallback."
253
263
  ```
254
264
 
265
+ **Note**: `<!-- FORGE:BUILDER:COMPLETE -->` is intentionally absent from this comment. It is appended to this comment by `validate.md` Phase V5 after the git commit completes — see `validate.md § Phase V5`. This ordering ensures the completion marker only exists when a real commit is present on the branch.
266
+
255
267
  ---
256
268
 
257
269
  ## Output
@@ -90,7 +90,12 @@ Needs human review before proceeding to commit.
90
90
 
91
91
  ## Phase V2: Format and Verify
92
92
 
93
- Run after quality gate passes. All tool commands are read from `forge.yaml → verification.commands`; each step logs `SKIPPED — not configured` when the corresponding key is absent rather than silently passing:
93
+ Run after quality gate passes. All tool commands are read from `forge.yaml → verification.commands`; each step logs `SKIPPED — not configured` when the corresponding key is absent rather than silently passing.
94
+
95
+ **Track skipped checks** — initialize before any check runs:
96
+ ```bash
97
+ SKIPPED_CHECKS=""
98
+ ```
94
99
 
95
100
  **Python**:
96
101
  ```bash
@@ -101,6 +106,7 @@ if [ -n "$PYTHON_FORMAT" ]; then
101
106
  eval "$PYTHON_FORMAT" 2>&1
102
107
  else
103
108
  echo "SKIPPED — python.format not configured in verification.commands"
109
+ SKIPPED_CHECKS="${SKIPPED_CHECKS:+$SKIPPED_CHECKS, }python.format"
104
110
  fi
105
111
 
106
112
  # Compile check always runs for Python files (no config needed — catches syntax errors)
@@ -120,6 +126,7 @@ if [ -n "$TS_FORMAT" ]; then
120
126
  eval "$TS_FORMAT" 2>&1
121
127
  else
122
128
  echo "SKIPPED — typescript.format not configured in verification.commands"
129
+ SKIPPED_CHECKS="${SKIPPED_CHECKS:+$SKIPPED_CHECKS, }typescript.format"
123
130
  fi
124
131
 
125
132
  if [ -n "$TS_TYPECHECK" ]; then
@@ -130,6 +137,7 @@ elif [ -n "$TS_BUILD" ]; then
130
137
  TS_EXIT=$?
131
138
  else
132
139
  echo "SKIPPED — typescript.typecheck and typescript.build not configured in verification.commands"
140
+ SKIPPED_CHECKS="${SKIPPED_CHECKS:+$SKIPPED_CHECKS, }typescript.typecheck/build"
133
141
  TS_EXIT=0
134
142
  fi
135
143
  ```
@@ -172,21 +180,30 @@ When changed files touch database engine configuration, flag for manual connecti
172
180
  ```bash
173
181
  cd {WORKTREE_PATH}
174
182
  DB_CONFIG_FILES=""
175
- for f in $(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$'); do
183
+ # Process substitution (< <(...)), NOT a piped `| while read`, so DB_CONFIG_FILES
184
+ # set inside the loop body survives past the loop (a piped while-read would run
185
+ # in a subshell and silently discard the accumulator).
186
+ while IFS= read -r f; do
187
+ [ -z "$f" ] && continue
176
188
  grep -qE "create_async_engine|AsyncSession|connect_args|pool_size|prepared_statement|engine_from_config|sessionmaker" "$f" 2>/dev/null && \
177
- DB_CONFIG_FILES="$DB_CONFIG_FILES $f"
178
- done
189
+ DB_CONFIG_FILES="${DB_CONFIG_FILES}${f}"$'\n'
190
+ done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$')
179
191
 
180
192
  if [ -n "$DB_CONFIG_FILES" ]; then
181
- echo "DB CONFIG CHANGE DETECTED in:$DB_CONFIG_FILES"
193
+ echo "DB CONFIG CHANGE DETECTED in:"
194
+ echo "$DB_CONFIG_FILES"
182
195
  echo "ACTION: Verify database connectivity after deploy — changes to engine config, connect_args, or session factories can cause silent runtime failures."
183
196
  echo "RECOMMENDED: Run a minimal connectivity test (e.g., SELECT 1) through the modified session/engine path."
184
197
 
185
198
  # Check for lambda/callable in connect_args — the exact bug class from PR #14391
186
- for f in $DB_CONFIG_FILES; do
199
+ # $DB_CONFIG_FILES is one path per line (built above) — herestring, not a
200
+ # piped `| while read`, so behavior stays consistent with the other fixes
201
+ # in this sweep even though no accumulator is set inside this particular loop.
202
+ while IFS= read -r f; do
203
+ [ -z "$f" ] && continue
187
204
  grep -nE "lambda.*:.*['\"]|=lambda" "$f" 2>/dev/null | grep -iE "connect_args|prepared_statement|pool|engine" && \
188
205
  echo "WARNING: Lambda/callable in database configuration in $f — verify callback signature matches library's expected calling convention"
189
- done
206
+ done <<< "$DB_CONFIG_FILES"
190
207
  fi
191
208
  ```
192
209
 
@@ -234,7 +251,7 @@ After the gate passes, commit all staged changes in a single commit. This includ
234
251
  ```bash
235
252
  cd {WORKTREE_PATH}
236
253
  git add -u
237
- git commit -m "fix({SCOPE}): {description} (#NUMBER)"
254
+ git commit -s -m "fix({SCOPE}): {description} (#NUMBER)"
238
255
  ```
239
256
 
240
257
  Where `{SCOPE}` is the command or module scope from the contract (e.g. `work-on`, `quality-gate`), and `{description}` summarises the implementation. Use the commit convention from the contract:
@@ -278,6 +295,32 @@ If `origin/{PR_BASE}` does not exist yet (new branch), skip this check — no co
278
295
  git ls-remote --exit-code origin {PR_BASE} >/dev/null 2>&1 || echo "PR_BASE not on origin — skipping ancestry audit"
279
296
  ```
280
297
 
298
+ ### V5 Post-Commit: Mark Build Complete (MANDATORY)
299
+
300
+ After the ancestry audit passes (or is skipped), append `<!-- FORGE:BUILDER:COMPLETE -->` to the existing FORGE:BUILDER comment. This is the **only** place this marker is written — it signals that a real commit exists on the branch and the build is safe to resume-skip. <!-- Added: forge#1305 -->
301
+
302
+ ```bash
303
+ # Find the FORGE:BUILDER comment posted by implement.md Phase I6
304
+ BUILDER_COMMENT_ID=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
305
+ --jq '[.[] | select(.body | contains("FORGE:BUILDER") and (contains("FORGE:BUILDER:COMPLETE") | not))] | last | .id // ""')
306
+
307
+ if [ -n "$BUILDER_COMMENT_ID" ]; then
308
+ # Fetch current body and append the completion marker
309
+ CURRENT_BODY=$(gh api repos/{GH_REPO}/issues/comments/$BUILDER_COMMENT_ID --jq '.body')
310
+ UPDATED_BODY="${CURRENT_BODY}
311
+
312
+ <!-- FORGE:BUILDER:COMPLETE -->"
313
+ gh api repos/{GH_REPO}/issues/comments/$BUILDER_COMMENT_ID \
314
+ -X PATCH \
315
+ --field body="$UPDATED_BODY"
316
+ echo "FORGE:BUILDER:COMPLETE appended to comment $BUILDER_COMMENT_ID"
317
+ else
318
+ echo "WARNING: FORGE:BUILDER comment not found or already marked complete — skipping BUILDER:COMPLETE append"
319
+ fi
320
+ ```
321
+
322
+ **Why here and not in implement.md**: The commit (`git commit`) runs in this phase (V5). Appending `:COMPLETE` after the commit ensures that a session crash between implement.md I6 (comment posted) and this step (commit) leaves a partial BUILDER comment without `:COMPLETE`. The next resume will detect the partial comment, delete it, and restart the build. See `implement.md § Phase I1 resume check`.
323
+
281
324
  ---
282
325
 
283
326
  ## Output
@@ -293,6 +336,9 @@ VALIDATE_RESULT:
293
336
  deploy_completeness_fixes: [{VAR_NAME: location_added}, ...]
294
337
  commits_added: [{SHA}, ...] # from V5 if any
295
338
  blocker: {description if gate_passed=false}
339
+ verification_skipped: [] # empty when all configured checks ran; list of skipped check names otherwise
340
+ # e.g. ["python.format", "typescript.typecheck/build"]
341
+ # populated from SKIPPED_CHECKS in Phase V2
296
342
  ```
297
343
 
298
344
  ---
@@ -9,8 +9,8 @@ argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--base PR_BA
9
9
 
10
10
  **Input**: $ARGUMENTS
11
11
 
12
- **Invoked by**: `work-on.md` state 8 (READY_TO_BUILD).
13
- **Output**: Create worktree, post contract, sequence build subcommands, return result to router.
12
+ **Invoked by**: `work-on.md` Phase 3 — entered when the issue carries label `workflow:ready-to-build` or `workflow:building` (see Universal Phase Dispatcher in work-on.md).
13
+ **Output**: Create worktree, post contract, sequence build subcommands, return result to work-on.md.
14
14
 
15
15
  **Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`.
16
16
  **NEVER use plan mode (EnterPlanMode).**
@@ -46,7 +46,16 @@ gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
46
46
  ```
47
47
 
48
48
  **Resume check**:
49
- - If `<!-- FORGE:BUILDER -->` comment exists → build already complete. Return `BUILD_RESULT: status: ALREADY_DONE` to router.
49
+ - If `<!-- FORGE:BUILDER:COMPLETE -->` is present in a BUILDER comment → build already complete. Return `BUILD_RESULT: status: ALREADY_DONE` to router.
50
+ - If `<!-- FORGE:BUILDER -->` exists BUT `<!-- FORGE:BUILDER:COMPLETE -->` is ABSENT → build was interrupted after the comment was posted but before the commit (validate.md V5). Delete the partial comment and restart from Phase B2 (contract): <!-- Added: forge#1305 -->
51
+ ```bash
52
+ PARTIAL_ID=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
53
+ --jq '[.[] | select(.body | contains("FORGE:BUILDER") and (contains("FORGE:BUILDER:COMPLETE") | not))] | last | .id // ""')
54
+ if [ -n "$PARTIAL_ID" ]; then
55
+ gh api repos/{GH_REPO}/issues/comments/$PARTIAL_ID -X DELETE
56
+ echo "Deleted partial FORGE:BUILDER comment (no FORGE:BUILDER:COMPLETE) — restarting build from Phase B2"
57
+ fi
58
+ ```
50
59
  - If no `<!-- FORGE:INVESTIGATOR -->` comment with `<!-- INVESTIGATION:COMPLETE -->` → EXIT with `BUILD_RESULT: status: BLOCKED`, blocker: "Investigation not complete — run investigate first".
51
60
 
52
61
  Extract from investigation report:
@@ -82,6 +91,14 @@ Append `-{NUMBER}` to ensure uniqueness: e.g. `fix/work-on-build-landing-file-85
82
91
 
83
92
  - Review-finding issue → parse `**Code branch**: \`{branch}\`` from issue body; branch from `origin/{branch}`
84
93
  - **Milestone review-finding hybrid lane** (ONLY when Code branch matches `milestone/*`): This is a high-risk lane. The worktree will carry the full milestone history. The PR target is `staging` (or the base specified). **DANGER: Agents MUST NOT use `git merge` to resolve any conflicts in this lane.** Merge-based conflict resolution will pull the entire milestone commit tree onto staging, contaminating it with unapproved code. Use `git rebase` or `git cherry-pick` only. If conflicts cannot be resolved without a merge, post a comment on the issue, add `needs-human`, and STOP.
94
+ - **Missing ref fallback**: After parsing, verify the Code branch still exists on remote. If not, fall back to the lane default (`staging` for fast lane, `milestone/{slug}` for feature lane) and note the fallback:
95
+ ```bash
96
+ SOURCE_BRANCH="{CODE_BRANCH_FROM_ISSUE_BODY}"
97
+ if ! git ls-remote --exit-code origin "$SOURCE_BRANCH" >/dev/null 2>&1; then
98
+ echo "WARNING: Code branch '$SOURCE_BRANCH' not found on remote — falling back to lane default '$PR_BASE'"
99
+ SOURCE_BRANCH="$PR_BASE"
100
+ fi
101
+ ```
85
102
  - Feature lane (has milestone) → branch from `origin/{PR_BASE}`
86
103
  - Fast lane (no milestone) → branch from `origin/staging`
87
104
 
@@ -275,13 +292,14 @@ BUILD_RESULT:
275
292
 
276
293
  ## Integration Point in work-on.md
277
294
 
278
- This module runs at **state 8 (READY_TO_BUILD)** in the routing loop:
295
+ This module runs during **Phase 3** of the work-on.md pipeline (label: `workflow:ready-to-build` or `workflow:building`). The full sequence is defined by the Universal Phase Dispatcher in work-on.md:
279
296
 
280
297
  ```
281
- state 8 → [THIS MODULE] worktree + contract + context + architect + implement + validate
282
- → BUILD_RESULT: COMPLETE
283
- states 7, 6 → work-on:review (push + PR + /review-pr --auto-merge)
284
- states 5, 4 → work-on:close (trajectory + parent tracker + summary)
298
+ Phase 3 (Build) → [THIS MODULE] worktree + contract + context + architect + implement + validate
299
+ → posts FORGE:BUILDER comment, writes FORGE:CHECKPOINT next_phase=REVIEW
300
+ Phase 4 (PR) → work-on:review — push branch, create PR, set workflow:in-review
301
+ Phase 5 (Review) → work-on:review — invoke /review-pr --auto-merge
302
+ Phase 6 (Close) → work-on:close — trajectory + parent tracker + summary + worktree cleanup
285
303
  ```
286
304
 
287
- The router re-reads GitHub state after this module returns COMPLETE — it will then detect state 6 (IN_REVIEW) or 7 (BUILD_NO_PR) and invoke `work-on:review`.
305
+ After this module posts `FORGE:BUILDER` and returns, work-on.md's Universal continuation rule re-reads the issue labels. Since the label is not yet terminal (`workflow:merged` / `workflow:invalid` / `needs-human`), it proceeds immediately to Phase 4 (PR Creation) and then Phase 5 (Auto-Review).
@@ -179,7 +179,7 @@ ISSUE_STATE=$(gh issue view {NUMBER} {GH_FLAG} --json state --jq '.state')
179
179
 
180
180
  **If `REMAINING_AFTER > 0`** (multi-phase: uncompleted phases remain):
181
181
  ```bash
182
- # Post phase-complete marker — router state 10 (MULTI_PHASE) will re-detect this issue
182
+ # Post phase-complete marker — work-on.md's Universal continuation rule will re-read labels and continue to the next phase
183
183
  gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:PHASE:COMPLETE -->
184
184
  Phase complete. PR #{PR_NUMBER} merged to \`{PR_BASE}\`. ${REMAINING_AFTER} phase item(s) remain — leaving issue open for next pipeline iteration."
185
185
 
@@ -279,6 +279,125 @@ Output to stdout (returned to calling agent):
279
279
 
280
280
  ---
281
281
 
282
+ ## Phase C4.5: Pipeline Summary Card (MANDATORY)
283
+
284
+ The shareable artifact. After the close completes, render a box-drawing summary card to
285
+ stdout (terminal screenshot moment) AND compute a machine-readable twin that Phase C5
286
+ embeds in the `FORGE:TRAJECTORY` comment for platform consumption.
287
+
288
+ **All stats are real — pulled from `gh`/`git`. Every lookup degrades gracefully: a missing
289
+ value renders as `—` and NEVER aborts the card. Do NOT fabricate stats.**
290
+
291
+ ### C4.5a: Gather real stats
292
+
293
+ ```bash
294
+ # Commit / diff stats from the merged PR (single API call). Fallbacks to "—" if absent.
295
+ PR_STATS=$(gh pr view {PR_NUMBER} {GH_FLAG} --json commits,additions,deletions,baseRefName,isDraft 2>/dev/null)
296
+ COMMITS=$(echo "$PR_STATS" | jq -r '(.commits | length) // empty' 2>/dev/null); COMMITS=${COMMITS:-—}
297
+ ADDITIONS=$(echo "$PR_STATS" | jq -r '.additions // empty' 2>/dev/null); ADDITIONS=${ADDITIONS:-—}
298
+ DELETIONS=$(echo "$PR_STATS" | jq -r '.deletions // empty' 2>/dev/null); DELETIONS=${DELETIONS:-—}
299
+ PR_TARGET=$(echo "$PR_STATS" | jq -r '.baseRefName // empty' 2>/dev/null); PR_TARGET=${PR_TARGET:-{PR_BASE}}
300
+ IS_DRAFT=$(echo "$PR_STATS" | jq -r '.isDraft // false' 2>/dev/null)
301
+
302
+ # Review summary — count domain-agent verdicts posted by /review-pr on the PR.
303
+ REVIEW_BODIES=$(gh pr view {PR_NUMBER} {GH_FLAG} --json reviews,comments \
304
+ --jq '[.reviews[].body // ""] + [.comments[].body // ""] | .[]' 2>/dev/null)
305
+ # NOTE: `grep -c` already prints `0` on no match (and exits non-zero) — do NOT add
306
+ # `|| echo 0`, which would append a second line ("0\n0") and break the arithmetic
307
+ # and `--argjson` below. Swallow the non-zero exit with `|| true`, then default.
308
+ APPROVED=$(echo "$REVIEW_BODIES" | grep -cE 'APPROVED:' 2>/dev/null || true); APPROVED=${APPROVED:-0}
309
+ CHANGES=$(echo "$REVIEW_BODIES" | grep -cE 'CHANGES REQUESTED:' 2>/dev/null || true); CHANGES=${CHANGES:-0}
310
+ TOTAL_AGENTS=$((APPROVED + CHANGES))
311
+ # Blockers = review-finding issues created by this PR that are still open (best-effort).
312
+ BLOCKERS=$(echo "$REVIEW_BODIES" | grep -ciE 'blocker|merge.?block' 2>/dev/null || true); BLOCKERS=${BLOCKERS:-0}
313
+ if [ "$TOTAL_AGENTS" -gt 0 ]; then
314
+ REVIEW_SUMMARY="${APPROVED}/${TOTAL_AGENTS} agents passed, ${BLOCKERS} blockers"
315
+ else
316
+ REVIEW_SUMMARY="—" # review data unavailable (e.g. review skipped)
317
+ fi
318
+
319
+ # Elapsed wall-clock: first FORGE agent comment → now.
320
+ FIRST_TS=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
321
+ --jq '[.[] | select(.body | contains("FORGE:")) | .created_at] | sort | .[0] // empty' 2>/dev/null)
322
+ if [ -n "$FIRST_TS" ]; then
323
+ START_EPOCH=$(date -u -d "$FIRST_TS" +%s 2>/dev/null \
324
+ || python3 -c "import sys,datetime; ts=sys.argv[1].rstrip('Z'); print(int(datetime.datetime.fromisoformat(ts+'+00:00').timestamp()))" "$FIRST_TS" 2>/dev/null \
325
+ || echo "")
326
+ NOW_EPOCH=$(date -u +%s)
327
+ if [ -n "$START_EPOCH" ]; then
328
+ ELAPSED_SECS=$((NOW_EPOCH - START_EPOCH))
329
+ ELAPSED=$(printf '%dm %02ds' $((ELAPSED_SECS / 60)) $((ELAPSED_SECS % 60)))
330
+ else ELAPSED="—"; ELAPSED_SECS=0; fi
331
+ else ELAPSED="—"; ELAPSED_SECS=0; fi
332
+
333
+ # Pipeline line + status — reflect the ACTUAL terminal state.
334
+ # merged → investigate → architect → build → review → merge ✓
335
+ # decomposed→ investigate → decompose ⏹ (sub-issues spawned)
336
+ # invalid → investigate → invalid ✗
337
+ # blocked → investigate → … → blocked ⚠ (needs-human)
338
+ # draft PR → append "(draft)" to the merge segment
339
+ case "{TERMINAL_STATE}" in
340
+ decomposed) PIPELINE_LINE="investigate → decompose ⏹"; CARD_STATUS="decomposed" ;;
341
+ invalid) PIPELINE_LINE="investigate → invalid ✗"; CARD_STATUS="invalid" ;;
342
+ blocked) PIPELINE_LINE="investigate → build → blocked ⚠"; CARD_STATUS="blocked" ;;
343
+ *) PIPELINE_LINE="investigate → architect → build → review → merge ✓"; CARD_STATUS="merged" ;;
344
+ esac
345
+ [ "$IS_DRAFT" = "true" ] && PIPELINE_LINE="${PIPELINE_LINE} (draft)"
346
+ ```
347
+
348
+ ### C4.5b: Render the card to stdout
349
+
350
+ Print the card to stdout (the calling agent surfaces it in the terminal). Card inner
351
+ width is **51** columns. Truncate the title with an ellipsis (`…`) if `#{NUMBER} — {TITLE}`
352
+ exceeds the field; pad shorter lines with spaces so the right border `║` stays aligned.
353
+
354
+ ```
355
+ ╔═══════════════════════════════════════════════════╗
356
+ ║ ForgeDock Pipeline Complete ║
357
+ ╠═══════════════════════════════════════════════════╣
358
+ ║ ║
359
+ ║ Issue: #{NUMBER} — {TITLE} ║
360
+ ║ Pipeline: {PIPELINE_LINE} ║
361
+ ║ Commits: {COMMITS} ({ADDITIONS} additions, {DELETIONS} deletions) ║
362
+ ║ PR: #{PR_NUMBER} (merged to {PR_TARGET}) ║
363
+ ║ Review: {REVIEW_SUMMARY} ║
364
+ ║ Time: {ELAPSED} ║
365
+ ║ ║
366
+ ╚═══════════════════════════════════════════════════╝
367
+ ```
368
+
369
+ **Edge-case rendering**:
370
+ - Decomposed: title line stays; `Pipeline:` shows `investigate → decompose ⏹`; `PR:`, `Review:`, `Commits:` render `—`; the header reads `ForgeDock Pipeline — Decomposed`.
371
+ - Invalid: header `ForgeDock Pipeline — Closed (invalid)`; `Pipeline:` shows `investigate → invalid ✗`; downstream stats `—`.
372
+ - Blocked / needs-human: header `ForgeDock Pipeline — Blocked`; `Review:`/`PR:` reflect last known state; remaining stats `—`.
373
+ - Draft PR: `PR:` line appends `(draft)` and the merge segment is not marked `✓`.
374
+
375
+ ### C4.5c: Build the machine-readable twin
376
+
377
+ Assemble the JSON object below (used verbatim by Phase C5). Numeric stats that were `—`
378
+ become `null` in JSON; never emit `"—"` as a number.
379
+
380
+ ```bash
381
+ CARD_JSON=$(jq -nc \
382
+ --argjson issue {NUMBER} \
383
+ --arg title "{TITLE}" \
384
+ --arg status "$CARD_STATUS" \
385
+ --arg pipeline "$PIPELINE_LINE" \
386
+ --arg pr "{PR_NUMBER}" \
387
+ --arg target "$PR_TARGET" \
388
+ --arg commits "$COMMITS" --arg adds "$ADDITIONS" --arg dels "$DELETIONS" \
389
+ --arg review "$REVIEW_SUMMARY" --argjson blockers "${BLOCKERS:-0}" \
390
+ --argjson elapsed "${ELAPSED_SECS:-0}" \
391
+ '{issue:$issue, title:$title, status:$status, pipeline:$pipeline,
392
+ pr:($pr|tonumber? // null), pr_target:$target,
393
+ commits:($commits|tonumber? // null),
394
+ additions:($adds|tonumber? // null),
395
+ deletions:($dels|tonumber? // null),
396
+ review:$review, blockers:$blockers, elapsed_seconds:$elapsed}')
397
+ ```
398
+
399
+ ---
400
+
282
401
  ## Phase C5: Trajectory Log (MANDATORY)
283
402
 
284
403
  Post the `<!-- FORGE:TRAJECTORY -->` comment as the final pipeline record:
@@ -305,9 +424,18 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:TRAJECTORY -->
305
424
 
306
425
  **Anomalies**: None
307
426
 
308
- **Pipeline completed**: {TIMESTAMP}"
427
+ **Pipeline completed**: {TIMESTAMP}
428
+
429
+ <!-- FORGE:CARD ${CARD_JSON} -->"
309
430
  ```
310
431
 
432
+ The `<!-- FORGE:CARD {...} -->` block carries the machine-readable summary computed in
433
+ Phase C4.5c. It is wrapped in an HTML comment so it stays hidden in GitHub's rendered view
434
+ (keeping the trajectory comment clean) while remaining greppable in the raw body for platform
435
+ consumption — `/orchestrate` reads it to build per-issue cards. This block is **additive**:
436
+ all existing `FORGE:TRAJECTORY` consumers select via `contains("FORGE:TRAJECTORY")` and parse
437
+ the markdown table, so the embedded JSON does not affect them.
438
+
311
439
  Where:
312
440
  - `{PARENT_STATUS}` = `⏭ Skipped` (if no parent) or `✅ Complete` (if parent updated)
313
441
  - `{PARENT_NOTES}` = `No parent tracker` or `Checked off in #{PARENT_REF}`
@@ -449,7 +577,7 @@ CLOSE_RESULT:
449
577
  parent_closed: {true|false}
450
578
  ```
451
579
 
452
- Where `PHASE_COMPLETE` means the current phase was closed but uncompleted phases remain — the issue is left OPEN for the router to re-evaluate on the next pipeline iteration (state 10, MULTI_PHASE).
580
+ Where `PHASE_COMPLETE` means the current phase was closed but uncompleted phases remain — the issue is left OPEN for work-on.md to re-evaluate on the next invocation (via Universal continuation rule: re-read labels, check terminal state, continue to next phase).
453
581
 
454
582
  ---
455
583
 
@@ -72,13 +72,30 @@ If the issue specifies a **Code branch** (`**Code branch**: \`{branch}\``), chec
72
72
  1. **Check the right branch** — read from the branch specified in the issue body (`**Code branch**: \`{branch}\``) if present
73
73
  2. **Read domain files** — start with the key files for the affected domain
74
74
  3. **Verify claims** — does the code actually have the problem described?
75
- 4. **Git blame** — trace when/why the relevant code was written
75
+ 4. **Git blame** — trace when/why the relevant code was written. Run bounded, local commands (no network round-trip):
76
+ ```bash
77
+ # Introducing commit for each affected file (first commit that added it)
78
+ git log --reverse --format='%h %an %ad %s' --date=short -- {affected_file} | head -1
79
+ # Last-touch commit (most recent change)
80
+ git log -1 --format='%h %an %ad %s' --date=short -- {affected_file}
81
+ # Line-level blame for a specific suspect hunk, if the issue names one
82
+ git blame -L {start},{end} -- {affected_file}
83
+ ```
84
+ Record the introducing commit and last-touch commit for each primary affected file — this feeds the mandatory **History findings** field in Phase 1C.
76
85
  5. **Domain context discovery** (narrow scope only, 1–5 files):
77
86
  ```bash
78
87
  git log --oneline --all -30 -- {affected_files} | grep -oP '#\d+' | sort -u
79
88
  gh issue list -R {GH_REPO} --state closed --limit 8 --search "{function_name}"
80
89
  ```
81
90
  Keep only file/function-level overlap. Max 5 related issues. Everything is a hint to verify, not a fact.
91
+
92
+ **Pickaxe pass (prior fix / regression detection)** — bounded to one pass, capped at 5 hits: search for prior additions/removals of the suspected symbol or literal string named in the issue (a function name, error string, or config key), independent of whether that fix was ever linked to a filed issue:
93
+ ```bash
94
+ git log -S"{suspected_symbol_or_string}" --oneline -- {affected_files} | head -5
95
+ # Use -G instead of -S when the target is a regex pattern rather than a literal string
96
+ git log -G"{pattern}" --oneline -- {affected_files} | head -5
97
+ ```
98
+ Any hit here is a candidate prior fix or reintroduced defect — read the commit body (`git show {hash}`) to confirm before citing it. Feed confirmed hits into the History findings field and let them inform the verdict (e.g. a defect being reintroduced raises severity).
82
99
  6. **Determine root cause** — what's actually broken or missing?
83
100
  7. **Identify affected files** — full list of files that need changes
84
101
  8. **Fix-approach validation** — if the issue proposes a fix, don't adopt it as spec. Trace through the target system's middleware, auth, routing, config. Cross-domain: if fix in domain A interacts with domain B, read domain B's files too.
@@ -113,6 +130,12 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:INVESTIGATOR -->
113
130
  ### Evidence
114
131
  {specific findings — function names, line numbers, behavior observed}
115
132
 
133
+ ### History Findings
134
+ **Introducing commit**: {hash — author — date — subject, per primary affected file}
135
+ **Last touched**: {hash — author — date — subject}
136
+ **Pickaxe hits (prior fixes / regressions)**: {commit(s) found via \`git log -S\`/\`-G\`, or 'None found' — max 5}
137
+ {This field is MANDATORY — populate from the git blame + pickaxe commands in step 4/5. If a file is newly created (no history), write 'New file — no history.'}
138
+
116
139
  ### Recommendation
117
140
  {what to build/fix, concrete and actionable}
118
141