forgedock 1.0.16 → 1.0.18

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 (190) hide show
  1. package/README.md +54 -3
  2. package/bin/cross-publish.mjs +344 -0
  3. package/bin/forgedock.mjs +2205 -541
  4. package/bin/github-app-manifest.json.license +2 -0
  5. package/bin/hooks/session-start.mjs +4 -5
  6. package/bin/registry.mjs +20 -0
  7. package/bin/tui.mjs +172 -96
  8. package/commands/adopt.md +433 -0
  9. package/commands/analytics.md +2 -0
  10. package/commands/audit-agents.md +2 -0
  11. package/commands/audit.md +2 -0
  12. package/commands/autopilot.md +77 -0
  13. package/commands/changelog.md +354 -0
  14. package/commands/cleanup.md +2 -0
  15. package/commands/compat-audit.md +330 -0
  16. package/commands/deploy-info.md +6 -5
  17. package/commands/design-bench.md +176 -0
  18. package/commands/design-render-critique-loop.md +103 -0
  19. package/commands/design.md +259 -0
  20. package/commands/diagnose.md +483 -0
  21. package/commands/explain.md +349 -0
  22. package/commands/forge-stats.md +2 -0
  23. package/commands/forgedock-init.md +2 -0
  24. package/commands/geo-audit.md +2 -0
  25. package/commands/incident-response.md +2 -0
  26. package/commands/issue.md +9 -3
  27. package/commands/milestone.md +2 -0
  28. package/commands/optimize.md +411 -0
  29. package/commands/orchestrate.md +239 -0
  30. package/commands/pipeline-health.md +60 -1
  31. package/commands/qa-sweep.md +39 -2
  32. package/commands/quality-gate.md +67 -0
  33. package/commands/replay.md +263 -0
  34. package/commands/resume.md +264 -0
  35. package/commands/review-pr-agents.md +5 -1
  36. package/commands/review-pr-staging.md +160 -2
  37. package/commands/review-pr.md +85 -3
  38. package/commands/rollback.md +2 -0
  39. package/commands/scope.md +280 -0
  40. package/commands/security-audit.md +2 -0
  41. package/commands/status.md +262 -0
  42. package/commands/sync-ecosystem.md +2 -0
  43. package/commands/test-gate.md +929 -0
  44. package/commands/validate.md +2 -0
  45. package/commands/work-on/build/architect.md +128 -18
  46. package/commands/work-on/build/context.md +130 -17
  47. package/commands/work-on/build/implement.md +7 -3
  48. package/commands/work-on/build/validate.md +2 -0
  49. package/commands/work-on/build.md +32 -7
  50. package/commands/work-on/close.md +85 -0
  51. package/commands/work-on/decompose.md +2 -0
  52. package/commands/work-on/investigate.md +30 -3
  53. package/commands/work-on/review.md +13 -1
  54. package/commands/work-on-monolithic.md +2 -0
  55. package/commands/work-on.md +676 -38
  56. package/docs/BRAND.md +51 -0
  57. package/docs/CI.md +177 -0
  58. package/docs/CONFIG.md +94 -0
  59. package/docs/FORGE-PROTOCOL.md +324 -0
  60. package/docs/articles/b-vs-c-baseline.md +210 -0
  61. package/docs/articles/full-abc-benchmark.md +205 -0
  62. package/docs/articles/per-repo-adaptive-scripts-token-savings.md +174 -0
  63. package/docs/articles/selective-spec-loading-token-savings.md +140 -0
  64. package/docs/claude-breakpoints.json +47 -0
  65. package/docs/design/abc-benchmark.md +195 -0
  66. package/docs/design/design-architect-rationale.md +123 -0
  67. package/docs/design/design-memory.md +61 -0
  68. package/docs/design/design-spec-schema.md +248 -0
  69. package/docs/design/design-system-lint.md +150 -0
  70. package/docs/design/divergent-generation.md +106 -0
  71. package/docs/design/effects-appropriateness.md +133 -0
  72. package/docs/design/fixtures/briefs/README.md +47 -0
  73. package/docs/design/fixtures/briefs/cadence.md +39 -0
  74. package/docs/design/fixtures/briefs/plume.md +39 -0
  75. package/docs/design/fixtures/briefs/slipstream.md +39 -0
  76. package/docs/design/fixtures/briefs/tender.md +39 -0
  77. package/docs/design/fixtures/briefs/voltage.md +39 -0
  78. package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-1.html +364 -0
  79. package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-2.html +420 -0
  80. package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-3.html +506 -0
  81. package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-1.html +611 -0
  82. package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-2.html +594 -0
  83. package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-3.html +817 -0
  84. package/docs/design/fixtures/runs/b-vs-c-baseline/runs.json +175 -0
  85. package/docs/design/fixtures/runs/b-vs-c-baseline/scorecard.json +138 -0
  86. package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-1.html +373 -0
  87. package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-2.html +398 -0
  88. package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-3.html +595 -0
  89. package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-1.html +531 -0
  90. package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-2.html +565 -0
  91. package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-3.html +674 -0
  92. package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-1.html +388 -0
  93. package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-2.html +463 -0
  94. package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-3.html +568 -0
  95. package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-1.html +562 -0
  96. package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-2.html +618 -0
  97. package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-3.html +618 -0
  98. package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-1.html +364 -0
  99. package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-2.html +420 -0
  100. package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-3.html +506 -0
  101. package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-1.html +527 -0
  102. package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-2.html +445 -0
  103. package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-3.html +483 -0
  104. package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-1.html +611 -0
  105. package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-2.html +594 -0
  106. package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-3.html +817 -0
  107. package/docs/design/fixtures/runs/full-abc/runs.json +190 -0
  108. package/docs/design/fixtures/runs/full-abc/scorecard.json +144 -0
  109. package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-1.html +496 -0
  110. package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-2.html +447 -0
  111. package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-3.html +564 -0
  112. package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-1.html +373 -0
  113. package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-2.html +398 -0
  114. package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-3.html +595 -0
  115. package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-1.html +622 -0
  116. package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-2.html +475 -0
  117. package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-3.html +660 -0
  118. package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-1.html +531 -0
  119. package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-2.html +565 -0
  120. package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-3.html +674 -0
  121. package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-1.html +525 -0
  122. package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-2.html +446 -0
  123. package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-3.html +463 -0
  124. package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-1.html +388 -0
  125. package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-2.html +463 -0
  126. package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-3.html +568 -0
  127. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-1.html +1068 -0
  128. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-2.html +1423 -0
  129. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-3.html +1245 -0
  130. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-1.html +305 -0
  131. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-2.html +327 -0
  132. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-3.html +428 -0
  133. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-1.html +1270 -0
  134. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-2.html +1172 -0
  135. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-3.html +1532 -0
  136. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-1.html +315 -0
  137. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-2.html +342 -0
  138. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-3.html +353 -0
  139. package/docs/design/fixtures/runs/full-abc-opus/runs.json +190 -0
  140. package/docs/design/fixtures/runs/full-abc-opus/scorecard.json +144 -0
  141. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-1.html +345 -0
  142. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-2.html +396 -0
  143. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-3.html +502 -0
  144. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-1.html +408 -0
  145. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-2.html +446 -0
  146. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-3.html +496 -0
  147. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-1.html +813 -0
  148. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-2.html +711 -0
  149. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-3.html +721 -0
  150. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-1.html +280 -0
  151. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-2.html +291 -0
  152. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-3.html +366 -0
  153. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-1.html +1010 -0
  154. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-2.html +836 -0
  155. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-3.html +930 -0
  156. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-1.html +407 -0
  157. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-2.html +396 -0
  158. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-3.html +529 -0
  159. package/docs/design/foundations/brutalist.css +294 -0
  160. package/docs/design/foundations/editorial.css +300 -0
  161. package/docs/design/foundations/minimal-luxury.css +295 -0
  162. package/docs/design/foundations/technical.css +293 -0
  163. package/docs/design/foundations/warm-photo.css +299 -0
  164. package/docs/design/reference-corpus.md +1726 -0
  165. package/docs/design/render-critique-loop.md +57 -0
  166. package/docs/site/getting-started.md +21 -0
  167. package/docs/spec-graph-schema.md +397 -0
  168. package/package.json +3 -3
  169. package/scripts/bench-scorecard.mjs +239 -0
  170. package/scripts/build-spec-graph.mjs +509 -0
  171. package/scripts/classify-lane.sh +138 -0
  172. package/scripts/design-system-lint.mjs +993 -0
  173. package/scripts/graph-query.sh +486 -0
  174. package/scripts/transition-label.sh +169 -0
  175. package/scripts/validate-pr-target.sh +30 -0
  176. package/scripts/validate-spec-graph.sh +423 -0
  177. package/templates/devdocs/README.md +72 -0
  178. package/templates/devdocs/agent/using-forgedock.md +3 -0
  179. package/templates/devdocs/agent/using-github.md +3 -0
  180. package/templates/devdocs/index.yaml +162 -0
  181. package/templates/devdocs/project/architecture.md +3 -0
  182. package/templates/devdocs/project/conventions.md +3 -0
  183. package/templates/devdocs/project/custom-instructions.md +3 -0
  184. package/templates/devdocs/project/glossary.md +3 -0
  185. package/templates/devdocs/project/stack.md +3 -0
  186. package/templates/devdocs/templates/commit-convention.md +3 -0
  187. package/templates/devdocs/templates/issue.md +3 -0
  188. package/templates/devdocs/templates/pull-request.md +3 -0
  189. package/templates/devdocs/templates/review-finding.md +3 -0
  190. package/templates/workflows/forgedock-review.yml +96 -0
@@ -2,6 +2,8 @@
2
2
  description: Independently verify if a reported issue is actually a problem before making code changes.
3
3
  argument-hint: [issue description or #number]
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # /validate — Independent Issue Verification
7
9
 
@@ -2,6 +2,8 @@
2
2
  description: Pre-implementation architecture planning — traces ALL affected code paths and produces an ordered implementation plan before any code is written
3
3
  argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--files AFFECTED_FILES]
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # work-on/build/architect — Multi-Path Implementation Planning
7
9
 
@@ -11,6 +13,23 @@ argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--files AFFE
11
13
 
12
14
  ---
13
15
 
16
+ ## COMPLEXITY_BAND Guard (check BEFORE all phases)
17
+
18
+ Read COMPLEXITY_BAND from the `<!-- FORGE:FAST_PATH -->` comment on the issue:
19
+
20
+ ```bash
21
+ COMPLEXITY_BAND=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
22
+ --jq '.[] | select(.body | contains("FORGE:FAST_PATH")) | .body' 2>/dev/null \
23
+ | sed -n 's/.*\*\*COMPLEXITY_BAND\*\*: \([A-Z_]*\).*/\1/p' | head -1)
24
+ COMPLEXITY_BAND="${COMPLEXITY_BAND:-STANDARD}"
25
+ ```
26
+
27
+ **If COMPLEXITY_BAND: TRIVIAL** → skip all phases (A0 through A5), post NO comment, return empty plan to caller immediately. Do not read any files. This is not an error — trivial single-file changes have no cross-path consistency risk. <!-- Added: forge#679 -->
28
+
29
+ **If COMPLEXITY_BAND: STANDARD or COMPLEX** → proceed to Phase A0 below.
30
+
31
+ ---
32
+
14
33
  ## Mission
15
34
 
16
35
  Eliminate cross-path inconsistency bugs before any code is written. The single biggest class of production bugs is a change applied to one code path but not all sibling paths that share the same logic. This agent traces the FULL request/data flow from every entry point, identifies every file that must change, and produces an ordered implementation plan the builder follows exactly.
@@ -127,12 +146,103 @@ fi
127
146
 
128
147
  ### Step 2: Secondary reads — other applicable project files
129
148
 
130
- Read all other `*.md` files under `DEVDOCS_PATH` where `applies_to` contains `work-on`. Skip `project/custom-instructions.md` (already read). Sort by authority (`required` first).
149
+ **Index-first path** (preferred when `index.yaml` exists): Use domain-filtered selective loading instead of O(N) enumerate. Falls back to full enumerate when index is absent. Skip `project/custom-instructions.md` (already read in Step 1). Sort by authority (`required` first).
131
150
 
132
151
  ```bash
133
152
  PROJECT_CONVENTIONS=""
153
+ INDEX_PATH="${DEVDOCS_PATH}/index.yaml"
154
+
155
+ # --- Read devdocs.index_path override from forge.yaml (optional) ---
156
+ if [ -f "$FORGE_YAML_PATH" ]; then
157
+ INDEX_PATH_OVERRIDE=$(grep -A5 '^devdocs:' "$FORGE_YAML_PATH" \
158
+ | grep '^\s*index_path:' \
159
+ | head -1 \
160
+ | sed 's/.*index_path:\s*//' \
161
+ | tr -d '"'"'"' \
162
+ | tr -d '[:space:]')
163
+ if [ -n "$INDEX_PATH_OVERRIDE" ]; then
164
+ case "$INDEX_PATH_OVERRIDE" in
165
+ /*) INDEX_PATH="$INDEX_PATH_OVERRIDE" ;;
166
+ *) INDEX_PATH="{REPO_PATH}/${INDEX_PATH_OVERRIDE}" ;;
167
+ esac
168
+ fi
169
+ fi
170
+
171
+ if [ -n "$DEVDOCS_PATH" ] && [ -f "$INDEX_PATH" ]; then
172
+ # --- Index-first loading path ---
173
+ echo "Devdocs index found at ${INDEX_PATH} — using selective domain loading"
174
+
175
+ # Read issue labels to determine domain(s)
176
+ ISSUE_LABELS=$(gh issue view {NUMBER} -R {GH_REPO} --json labels \
177
+ --jq '[.labels[].name] | join(" ")' 2>/dev/null || echo "")
178
+ echo "Issue labels: ${ISSUE_LABELS:-none}"
179
+
180
+ # Read index content
181
+ INDEX_CONTENT=$(cat "$INDEX_PATH" 2>/dev/null || echo "")
182
+
183
+ # Extract always_load paths (lines with "path:" under "always_load:" block)
184
+ ALWAYS_LOAD_PATHS=$(echo "$INDEX_CONTENT" \
185
+ | awk '/^always_load:/,/^[a-z]/' \
186
+ | grep '^\s*-\s*path:' \
187
+ | sed 's/.*path:\s*//' \
188
+ | tr -d '"'"'"' \
189
+ | tr -d '[:space:]')
190
+
191
+ # Extract domain blocks matching any issue label keyword
192
+ DOMAIN_PATHS=""
193
+ for label in $ISSUE_LABELS; do
194
+ KEYWORD=$(echo "$label" | sed 's/^workflow://; s/^priority://; s/^review-finding$//')
195
+ [ -z "$KEYWORD" ] && continue
196
+ # Sanitize KEYWORD before AWK injection — strip chars that would break awk regex delimiters
197
+ SAFE_KEYWORD=$(printf '%s' "$KEYWORD" | tr -cd 'a-zA-Z0-9_-')
198
+ [ -z "$SAFE_KEYWORD" ] && continue
199
+
200
+ BLOCK_PATHS=$(echo "$INDEX_CONTENT" \
201
+ | awk "/^ ${SAFE_KEYWORD}:/{found=1; next} found && /^ [a-z]/{found=0} found && /^\s*-\s*path:/{print}" \
202
+ | sed 's/.*path:\s*//' \
203
+ | tr -d '"'"'"' \
204
+ | tr -d '[:space:]')
205
+
206
+ if [ -n "$BLOCK_PATHS" ]; then
207
+ echo "Domain '${SAFE_KEYWORD}' matched — adding docs: $(echo "$BLOCK_PATHS" | tr '\n' ' ')"
208
+ DOMAIN_PATHS="${DOMAIN_PATHS}${BLOCK_PATHS}"$'\n'
209
+ fi
210
+ done
211
+
212
+ # Combine always_load + domain paths (deduplicate)
213
+ ALL_PATHS=$(printf "%s\n%s" "$ALWAYS_LOAD_PATHS" "$DOMAIN_PATHS" \
214
+ | grep -v '^$' | sort -u)
215
+
216
+ # When no domain labels matched, load only always_load entries
217
+ if [ -z "$DOMAIN_PATHS" ]; then
218
+ echo "No domain labels matched index — loading always_load entries only"
219
+ ALL_PATHS="$ALWAYS_LOAD_PATHS"
220
+ fi
221
+
222
+ APPLICABLE_FILES=""
223
+ while IFS= read -r rel_path; do
224
+ [ -z "$rel_path" ] && continue
225
+ # Skip custom-instructions — already read in Step 1
226
+ [ "$rel_path" = "project/custom-instructions.md" ] && continue
227
+ ABS_PATH="${DEVDOCS_PATH}/${rel_path}"
228
+ [ -f "$ABS_PATH" ] || { echo "WARN: index references missing file: ${rel_path} — skipping"; continue; }
229
+
230
+ FRONTMATTER=$(awk '/^---/{c++; if(c==1){next} if(c==2){exit}} c==1{print}' "$ABS_PATH" 2>/dev/null)
231
+ AUTHORITY=$(echo "$FRONTMATTER" | grep 'authority:' | head -1 | sed 's/.*authority:\s*//' | tr -d ' ')
232
+ case "$AUTHORITY" in
233
+ required) SORT_KEY="1" ;;
234
+ recommended) SORT_KEY="2" ;;
235
+ *) SORT_KEY="3" ;;
236
+ esac
237
+ APPLICABLE_FILES="${APPLICABLE_FILES}${SORT_KEY}|${ABS_PATH}"$'\n'
238
+ done <<< "$ALL_PATHS"
239
+
240
+ APPLICABLE_FILES=$(printf "%s" "$APPLICABLE_FILES" | sort | cut -d'|' -f2-)
241
+
242
+ elif [ -n "$DEVDOCS_PATH" ]; then
243
+ # --- Fallback: O(N) enumerate (backward compatible — no index.yaml present) ---
244
+ echo "No index.yaml found at ${INDEX_PATH} — falling back to full enumerate (backward compatible)"
134
245
 
135
- if [ -n "$DEVDOCS_PATH" ]; then
136
246
  APPLICABLE_FILES=""
137
247
 
138
248
  while IFS= read -r -d '' mdfile; do
@@ -148,32 +258,31 @@ if [ -n "$DEVDOCS_PATH" ]; then
148
258
  recommended) SORT_KEY="2" ;;
149
259
  *) SORT_KEY="3" ;;
150
260
  esac
151
- APPLICABLE_FILES="${APPLICABLE_FILES}${SORT_KEY}|${mdfile}\n"
261
+ APPLICABLE_FILES="${APPLICABLE_FILES}${SORT_KEY}|${mdfile}"$'\n'
152
262
  fi
153
263
  done < <(find "$DEVDOCS_PATH" -name "*.md" -print0 2>/dev/null)
154
264
 
155
265
  APPLICABLE_FILES=$(printf "$APPLICABLE_FILES" | sort | cut -d'|' -f2-)
266
+ fi
156
267
 
157
- while IFS= read -r mdfile; do
158
- [ -z "$mdfile" ] && continue
159
- TOTAL_LINES=$(wc -l < "$mdfile" 2>/dev/null || echo 0)
160
- if [ "$TOTAL_LINES" -gt 200 ]; then
161
- FILE_CONTENT=$(head -200 "$mdfile")
162
- TRUNCATION_NOTE="_[Truncated at 200 lines — ${TOTAL_LINES} total.]_"
163
- else
164
- FILE_CONTENT=$(cat "$mdfile")
165
- TRUNCATION_NOTE=""
166
- fi
268
+ while IFS= read -r mdfile; do
269
+ [ -z "$mdfile" ] && continue
270
+ TOTAL_LINES=$(wc -l < "$mdfile" 2>/dev/null || echo 0)
271
+ if [ "$TOTAL_LINES" -gt 200 ]; then
272
+ FILE_CONTENT=$(head -200 "$mdfile")
273
+ TRUNCATION_NOTE="_[Truncated at 200 lines — ${TOTAL_LINES} total.]_"
274
+ else
275
+ FILE_CONTENT=$(cat "$mdfile")
276
+ TRUNCATION_NOTE=""
277
+ fi
167
278
 
168
- REL_PATH="${mdfile#${DEVDOCS_PATH}/}"
169
- PROJECT_CONVENTIONS="${PROJECT_CONVENTIONS}
279
+ REL_PATH="${mdfile#${DEVDOCS_PATH}/}"
280
+ PROJECT_CONVENTIONS="${PROJECT_CONVENTIONS}
170
281
 
171
282
  #### \`${REL_PATH}\`
172
283
  ${FILE_CONTENT}
173
284
  ${TRUNCATION_NOTE}"
174
- done <<< "$APPLICABLE_FILES"
175
- fi
176
- ```
285
+ done <<< "$APPLICABLE_FILES"
177
286
 
178
287
  ### Step 3: Precedence rules to enforce
179
288
 
@@ -411,6 +520,7 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:ARCHITECT -->
411
520
  ## Skip Conditions
412
521
 
413
522
  Skip this entire step (post nothing, return empty plan to caller) if:
523
+ - **COMPLEXITY_BAND: TRIVIAL** — checked via FORGE:FAST_PATH comment at entry (see guard above) <!-- Primary skip path: forge#679 -->
414
524
  - Issue creates only **new files** with no callers to find (e.g. a new command file with no existing integration point yet)
415
525
  - Issue is a 1-file config or docs edit with no code logic
416
526
  - Issue title starts with "docs:" or "chore:"
@@ -2,6 +2,8 @@
2
2
  description: Pre-implementation context gathering — surfaces historical findings, bug patterns, and related code paths before the builder writes any code
3
3
  argument-hint: [issue number] [affected_files...] [--functions function_names...]
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # work-on/build/context — Pre-Implementation Context Gathering
7
9
 
@@ -11,6 +13,23 @@ argument-hint: [issue number] [affected_files...] [--functions function_names...
11
13
 
12
14
  ---
13
15
 
16
+ ## COMPLEXITY_BAND Guard (check BEFORE all phases)
17
+
18
+ Read COMPLEXITY_BAND from the `<!-- FORGE:FAST_PATH -->` comment on the issue:
19
+
20
+ ```bash
21
+ COMPLEXITY_BAND=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
22
+ --jq '.[] | select(.body | contains("FORGE:FAST_PATH")) | .body' 2>/dev/null \
23
+ | sed -n 's/.*\*\*COMPLEXITY_BAND\*\*: \([A-Z_]*\).*/\1/p' | head -1)
24
+ COMPLEXITY_BAND="${COMPLEXITY_BAND:-STANDARD}"
25
+ ```
26
+
27
+ **If COMPLEXITY_BAND: TRIVIAL** → skip all phases (C-1 through C4), post NO comment, return empty briefing to caller immediately. Do not query GitHub, do not read files. This is not an error — trivial single-file changes have no institutional memory to surface. <!-- Added: forge#679 -->
28
+
29
+ **If COMPLEXITY_BAND: STANDARD or COMPLEX** → proceed to Phase C-1 below.
30
+
31
+ ---
32
+
14
33
  ## Mission
15
34
 
16
35
  Surface what went wrong in this area before the builder writes a single line of code. The builder starts with the investigator report and contract — this step adds institutional memory: what did review agents catch last time someone touched these files, what bugs recurred, what other paths must stay consistent. When prior investigation Gists are linked in the issue body, fetch and summarize them so the builder has cross-issue context without manual lookups. When a milestone-level index Gist exists, use it to discover all investigation Gists for the milestone — providing full cross-issue context from a single URL. <!-- Updated: forge#341 -->
@@ -75,13 +94,106 @@ fi
75
94
 
76
95
  ### Step 1: Enumerate and filter applicable files
77
96
 
78
- Find all `.md` files under `DEVDOCS_PATH`, parse YAML frontmatter, keep those with `work-on` in `applies_to`. Sort by authority (`required` first, then `recommended`, then `reference`).
97
+ **Index-first path** (preferred when `index.yaml` exists): Read the lightweight index, extract issue labels to filter by domain, load only matching docs. Falls back to O(N) enumerate when index is absent.
79
98
 
80
99
  ```bash
81
100
  DEVDOCS_APPLICABLE="" # list of paths that apply
101
+ INDEX_PATH="${DEVDOCS_PATH}/index.yaml"
102
+
103
+ # --- Read devdocs.index_path override from forge.yaml (optional) ---
104
+ if [ -f "$FORGE_YAML_PATH" ]; then
105
+ INDEX_PATH_OVERRIDE=$(grep -A5 '^devdocs:' "$FORGE_YAML_PATH" \
106
+ | grep '^\s*index_path:' \
107
+ | head -1 \
108
+ | sed 's/.*index_path:\s*//' \
109
+ | tr -d '"'"'"' \
110
+ | tr -d '[:space:]')
111
+ if [ -n "$INDEX_PATH_OVERRIDE" ]; then
112
+ # Resolve relative to REPO_PATH if not absolute
113
+ case "$INDEX_PATH_OVERRIDE" in
114
+ /*) INDEX_PATH="$INDEX_PATH_OVERRIDE" ;;
115
+ *) INDEX_PATH="{REPO_PATH}/${INDEX_PATH_OVERRIDE}" ;;
116
+ esac
117
+ fi
118
+ fi
119
+
120
+ if [ -n "$DEVDOCS_PATH" ] && [ -f "$INDEX_PATH" ]; then
121
+ # --- Index-first loading path ---
122
+ echo "Devdocs index found at ${INDEX_PATH} — using selective domain loading"
123
+
124
+ # Read issue labels to determine domain(s)
125
+ ISSUE_LABELS=$(gh issue view {NUMBER} -R {GH_REPO} --json labels \
126
+ --jq '[.labels[].name] | join(" ")' 2>/dev/null || echo "")
127
+ echo "Issue labels: ${ISSUE_LABELS:-none}"
128
+
129
+ # Read index content
130
+ INDEX_CONTENT=$(cat "$INDEX_PATH" 2>/dev/null || echo "")
131
+
132
+ # Extract always_load paths (lines with "path:" under "always_load:" block)
133
+ ALWAYS_LOAD_PATHS=$(echo "$INDEX_CONTENT" \
134
+ | awk '/^always_load:/,/^[a-z]/' \
135
+ | grep '^\s*-\s*path:' \
136
+ | sed 's/.*path:\s*//' \
137
+ | tr -d '"'"'"' \
138
+ | tr -d '[:space:]')
139
+
140
+ # Extract domain blocks matching any issue label keyword
141
+ DOMAIN_PATHS=""
142
+ for label in $ISSUE_LABELS; do
143
+ # Strip workflow:, priority:, review-finding prefixes — use bare keyword
144
+ KEYWORD=$(echo "$label" | sed 's/^workflow://; s/^priority://; s/^review-finding$//')
145
+ [ -z "$KEYWORD" ] && continue
146
+ # Sanitize KEYWORD before AWK injection — strip chars that would break awk regex delimiters
147
+ SAFE_KEYWORD=$(printf '%s' "$KEYWORD" | tr -cd 'a-zA-Z0-9_-')
148
+ [ -z "$SAFE_KEYWORD" ] && continue
149
+
150
+ # Find the domain block matching this keyword, extract its doc paths
151
+ BLOCK_PATHS=$(echo "$INDEX_CONTENT" \
152
+ | awk "/^ ${SAFE_KEYWORD}:/{found=1; next} found && /^ [a-z]/{found=0} found && /^\s*-\s*path:/{print}" \
153
+ | sed 's/.*path:\s*//' \
154
+ | tr -d '"'"'"' \
155
+ | tr -d '[:space:]')
156
+
157
+ if [ -n "$BLOCK_PATHS" ]; then
158
+ echo "Domain '${SAFE_KEYWORD}' matched — adding docs: $(echo "$BLOCK_PATHS" | tr '\n' ' ')"
159
+ DOMAIN_PATHS="${DOMAIN_PATHS}${BLOCK_PATHS}"$'\n'
160
+ fi
161
+ done
162
+
163
+ # Combine always_load + domain paths (deduplicate)
164
+ ALL_PATHS=$(printf "%s\n%s" "$ALWAYS_LOAD_PATHS" "$DOMAIN_PATHS" \
165
+ | grep -v '^$' | sort -u)
166
+
167
+ # When no domain labels matched, load only authority:required files from always_load
168
+ if [ -z "$DOMAIN_PATHS" ]; then
169
+ echo "No domain labels matched index — loading always_load entries only"
170
+ ALL_PATHS="$ALWAYS_LOAD_PATHS"
171
+ fi
172
+
173
+ # Build DEVDOCS_APPLICABLE list with sort keys
174
+ while IFS= read -r rel_path; do
175
+ [ -z "$rel_path" ] && continue
176
+ ABS_PATH="${DEVDOCS_PATH}/${rel_path}"
177
+ [ -f "$ABS_PATH" ] || { echo "WARN: index references missing file: ${rel_path} — skipping"; continue; }
178
+
179
+ # Extract authority from frontmatter for sort key
180
+ FRONTMATTER=$(awk '/^---/{c++; if(c==1){next} if(c==2){exit}} c==1{print}' "$ABS_PATH" 2>/dev/null)
181
+ AUTHORITY=$(echo "$FRONTMATTER" | grep 'authority:' | head -1 | sed 's/.*authority:\s*//' | tr -d ' ')
182
+ case "$AUTHORITY" in
183
+ required) SORT_KEY="1" ;;
184
+ recommended) SORT_KEY="2" ;;
185
+ reference) SORT_KEY="3" ;;
186
+ *) SORT_KEY="4" ;;
187
+ esac
188
+ DEVDOCS_APPLICABLE="${DEVDOCS_APPLICABLE}${SORT_KEY}|${ABS_PATH}"$'\n'
189
+ done <<< "$ALL_PATHS"
190
+
191
+ DEVDOCS_APPLICABLE=$(printf "%s" "$DEVDOCS_APPLICABLE" | sort | cut -d'|' -f2-)
192
+
193
+ elif [ -n "$DEVDOCS_PATH" ]; then
194
+ # --- Fallback: O(N) enumerate (backward compatible — no index.yaml present) ---
195
+ echo "No index.yaml found at ${INDEX_PATH} — falling back to full enumerate (backward compatible)"
82
196
 
83
- if [ -n "$DEVDOCS_PATH" ]; then
84
- # Find all markdown files recursively
85
197
  while IFS= read -r -d '' mdfile; do
86
198
  # Extract frontmatter block (between first two --- markers)
87
199
  FRONTMATTER=$(awk '/^---/{c++; if(c==1){next} if(c==2){exit}} c==1{print}' "$mdfile" 2>/dev/null)
@@ -96,16 +208,16 @@ if [ -n "$DEVDOCS_PATH" ]; then
96
208
  reference) SORT_KEY="3" ;;
97
209
  *) SORT_KEY="4" ;;
98
210
  esac
99
- DEVDOCS_APPLICABLE="${DEVDOCS_APPLICABLE}${SORT_KEY}|${mdfile}\n"
211
+ DEVDOCS_APPLICABLE="${DEVDOCS_APPLICABLE}${SORT_KEY}|${mdfile}"$'\n'
100
212
  fi
101
213
  done < <(find "$DEVDOCS_PATH" -name "*.md" -print0 2>/dev/null)
102
214
 
103
215
  # Sort by authority key and extract paths
104
- DEVDOCS_APPLICABLE=$(printf "$DEVDOCS_APPLICABLE" | sort | cut -d'|' -f2-)
216
+ DEVDOCS_APPLICABLE=$(printf "%s" "$DEVDOCS_APPLICABLE" | sort | cut -d'|' -f2-)
105
217
  fi
106
218
 
107
219
  if [ -z "$DEVDOCS_APPLICABLE" ]; then
108
- echo "No devdocs files with 'applies_to: work-on' found — skipping Phase C-1 content read"
220
+ echo "No applicable devdocs files found — skipping Phase C-1 content read"
109
221
  fi
110
222
  ```
111
223
 
@@ -165,26 +277,26 @@ INDEX_GIST_URLS=""
165
277
 
166
278
  # Check issue body for milestone index annotation
167
279
  MILESTONE_INDEX_URL=$(echo "$ISSUE_BODY" \
168
- | grep -oP '(?<=<!-- FORGE:MILESTONE_INDEX: )https://[^ ]+(?= -->)' \
280
+ | sed -n 's/.*<!-- FORGE:MILESTONE_INDEX: \(https:\/\/[^ ]*\) -->.*/\1/p' \
169
281
  | head -1)
170
282
 
171
283
  # If not in issue body, check milestone description
172
284
  if [ -z "$MILESTONE_INDEX_URL" ] && [ -n "$MILESTONE_NUM" ]; then
173
285
  MILESTONE_DESC=$(gh api repos/{GH_REPO}/milestones/${MILESTONE_NUM} --jq '.description // ""' 2>/dev/null)
174
286
  MILESTONE_INDEX_URL=$(echo "$MILESTONE_DESC" \
175
- | grep -oP '(?<=<!-- FORGE:MILESTONE_INDEX: )https://[^ ]+(?= -->)' \
287
+ | sed -n 's/.*<!-- FORGE:MILESTONE_INDEX: \(https:\/\/[^ ]*\) -->.*/\1/p' \
176
288
  | head -1)
177
289
  fi
178
290
 
179
291
  # If found, fetch the index and extract individual Gist URLs from table rows
180
292
  if [ -n "$MILESTONE_INDEX_URL" ]; then
181
- INDEX_GIST_ID=$(echo "$MILESTONE_INDEX_URL" | grep -oP '[a-f0-9]{20,}' | tail -1)
293
+ INDEX_GIST_ID=$(echo "$MILESTONE_INDEX_URL" | grep -oE '[a-f0-9]{20,}' | tail -1)
182
294
  if [ -n "$INDEX_GIST_ID" ]; then
183
295
  INDEX_CONTENT=$(timeout 15 gh gist view "$INDEX_GIST_ID" --raw 2>/dev/null)
184
296
  if [ -n "$INDEX_CONTENT" ]; then
185
297
  # Extract Gist URLs from table rows (format: | ... | https://gist.github.com/... | ... |)
186
298
  INDEX_GIST_URLS=$(echo "$INDEX_CONTENT" \
187
- | grep -oP 'https://gist\.github\.com/[a-f0-9/]+' \
299
+ | grep -oE 'https://gist\.github\.com/[a-f0-9/]+' \
188
300
  | head -10)
189
301
  echo "Milestone index fetched: found $(echo "$INDEX_GIST_URLS" | wc -l) investigation Gist(s)"
190
302
  else
@@ -198,7 +310,7 @@ fi
198
310
 
199
311
  ```bash
200
312
  GIST_URLS=$(echo "$ISSUE_BODY" \
201
- | grep -oP '(?<=<!-- FORGE:PRIOR_GIST: )https://[^ ]+(?= -->)' \
313
+ | sed -n 's/.*<!-- FORGE:PRIOR_GIST: \(https:\/\/[^ ]*\) -->.*/\1/p' \
202
314
  | head -5)
203
315
 
204
316
  # Merge with any URLs discovered from milestone index (deduplicate)
@@ -223,7 +335,7 @@ GIST_SUMMARIES=""
223
335
 
224
336
  for url in $GIST_URLS; do
225
337
  # Extract Gist ID from URL (last path segment, strip any trailing slash)
226
- GIST_ID=$(echo "$url" | grep -oP '[a-f0-9]{20,}' | tail -1)
338
+ GIST_ID=$(echo "$url" | grep -oE '[a-f0-9]{20,}' | tail -1)
227
339
 
228
340
  if [ -z "$GIST_ID" ]; then
229
341
  echo "WARNING: Could not extract Gist ID from URL: $url — skipping"
@@ -241,10 +353,10 @@ for url in $GIST_URLS; do
241
353
  fi
242
354
 
243
355
  # Extract key sections for summary (~2K chars target per Gist)
244
- VERDICT=$(echo "$GIST_CONTENT" | grep -oP '(?<=verdict: )\w+' | head -1)
245
- TASK_TYPE=$(echo "$GIST_CONTENT" | grep -oP '(?<=task_type: ).+' | head -1)
246
- SEVERITY=$(echo "$GIST_CONTENT" | grep -oP '(?<=severity: )\w+' | head -1)
247
- SOURCE_ISSUE=$(echo "$GIST_CONTENT" | grep -oP '(?<=issue: )\d+' | head -1)
356
+ VERDICT=$(echo "$GIST_CONTENT" | sed -n 's/.*verdict: \([A-Za-z_]*\).*/\1/p' | head -1)
357
+ TASK_TYPE=$(echo "$GIST_CONTENT" | sed -n 's/.*task_type: \(.*\)/\1/p' | head -1)
358
+ SEVERITY=$(echo "$GIST_CONTENT" | sed -n 's/.*severity: \([A-Za-z_]*\).*/\1/p' | head -1)
359
+ SOURCE_ISSUE=$(echo "$GIST_CONTENT" | sed -n 's/.*issue: \([0-9]*\).*/\1/p' | head -1)
248
360
 
249
361
  # Extract structured sections: Root Cause, Recommendation, Affected Files
250
362
  ROOT_CAUSE=$(echo "$GIST_CONTENT" \
@@ -318,7 +430,7 @@ Mine git log for commit messages referencing issues, then fetch those issues:
318
430
  ```bash
319
431
  # Step 1: find issue numbers from git history on affected files
320
432
  git log --oneline -30 -- {AFFECTED_FILES} \
321
- | grep -oP '#\d+' \
433
+ | grep -oE '#[0-9]+' \
322
434
  | sort -u \
323
435
  | head -8
324
436
 
@@ -448,6 +560,7 @@ gh issue comment {NUMBER} -R {GH_REPO} --body "<!-- FORGE:CONTEXT -->
448
560
  ## Skip Conditions
449
561
 
450
562
  Skip this entire step (post nothing, return empty briefing) if:
563
+ - **COMPLEXITY_BAND: TRIVIAL** — checked via FORGE:FAST_PATH comment at entry (see guard above) <!-- Primary skip path: forge#679 -->
451
564
  - Issue is a 1-file config or docs edit with no code logic
452
565
  - The affected files have zero git history (new files being created)
453
566
  - `{AFFECTED_FILES}` is empty (investigation produced no file list)
@@ -2,6 +2,8 @@
2
2
  description: Implementation agent — writes code, makes commits, posts builder comment
3
3
  argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--worktree PATH] [--branch BRANCH]
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # work-on/build/implement — Implementation Subcommand
7
9
 
@@ -241,9 +243,11 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:BUILDER -->
241
243
  {Checklist of each criterion from the contract, marked ✅ or ❌}
242
244
 
243
245
  ### Testing Checklist
244
- - [ ] {Test scenario 1}
245
- - [ ] {Test scenario 2}
246
- - [ ] {Test scenario 3}
246
+ - [ ] {Test scenario 1} [type:api]
247
+ - [ ] {Test scenario 2} [type:unit]
248
+ - [ ] {Test scenario 3} [type:e2e]
249
+
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.
247
251
 
248
252
  <!-- FORGE:BUILDER:COMPLETE -->"
249
253
  ```
@@ -2,6 +2,8 @@
2
2
  description: Validation agent — quality gate loop, format/verify, proxy check, deploy check
3
3
  argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--worktree PATH] [--files FILE1 FILE2...]
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # work-on/build/validate — Validation Subcommand
7
9
 
@@ -2,6 +2,8 @@
2
2
  description: Build subcommand — create worktree, post contract, sequence context/architect/implement/validate
3
3
  argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--base PR_BASE]
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # work-on/build — Build Phase Orchestrator
7
9
 
@@ -13,7 +15,7 @@ argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--base PR_BA
13
15
  **Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`.
14
16
  **NEVER use plan mode (EnterPlanMode).**
15
17
 
16
- **CRITICAL: You MUST execute ALL phases B0–B6 in order. After each Skill() call returns, you MUST continue to the next phase. Do NOT skip phases B3 (context) or B4 (architect) — they post mandatory `FORGE:CONTEXT` and `FORGE:ARCHITECT` comments that the implement phase reads as its primary input. Skipping them degrades build quality.**
18
+ **CRITICAL: You MUST execute ALL phases B0–B6 in order. After each Skill() call returns, you MUST continue to the next phase. Phases B3 (context) and B4 (architect) are skipped ONLY when COMPLEXITY_BAND: TRIVIAL (read from FORGE:FAST_PATH comment in Phase B0). For STANDARD and COMPLEX tasks they are NOT optional — skipping them degrades build quality.**
17
19
 
18
20
  ---
19
21
 
@@ -53,6 +55,16 @@ Extract from investigation report:
53
55
  - Recommendation
54
56
  - Task type (Bug Fix / Feature / Refactor / Maintenance / UI/UX / Full-Stack)
55
57
 
58
+ **Read COMPLEXITY_BAND** (from FORGE:FAST_PATH comment posted by Phase 3B):
59
+ ```bash
60
+ COMPLEXITY_BAND=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
61
+ --jq '.[] | select(.body | contains("FORGE:FAST_PATH")) | .body' 2>/dev/null \
62
+ | sed -n 's/.*\*\*COMPLEXITY_BAND\*\*: \([A-Z_]*\).*/\1/p' | head -1)
63
+ # Default to STANDARD if not found (conservative — runs full pipeline)
64
+ COMPLEXITY_BAND="${COMPLEXITY_BAND:-STANDARD}"
65
+ echo "COMPLEXITY_BAND: $COMPLEXITY_BAND"
66
+ ```
67
+
56
68
  ---
57
69
 
58
70
  ## Phase B1: Create Worktree & Branch
@@ -146,7 +158,7 @@ After posting the Builder Contract, extract the primary function/class names fro
146
158
  FUNCTION_NAMES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
147
159
  --jq '.[] | select(.body | contains("FORGE:CONTRACT")) | .body' \
148
160
  | awk '/^### Deliverables/{p=1; next} /^### /{p=0} p' \
149
- | grep -oP '`[A-Za-z_][A-Za-z0-9_]*`' \
161
+ | grep -oE '`[A-Za-z_][A-Za-z0-9_]*`' \
150
162
  | tr -d '`' \
151
163
  | sort -u \
152
164
  | tr '\n' ' ' \
@@ -161,9 +173,11 @@ If `FUNCTION_NAMES` is non-empty, it will be passed via `--functions` to the con
161
173
 
162
174
  ---
163
175
 
164
- ## Phase B3: Context Gathering (MANDATORY Subcommand)
176
+ ## Phase B3: Context Gathering (MANDATORY for STANDARD/COMPLEX — skip for TRIVIAL)
177
+
178
+ **Skip if COMPLEXITY_BAND: TRIVIAL** (read from FORGE:FAST_PATH in Phase B0) — do not invoke the context subcommand. Proceed directly to Phase B4.
165
179
 
166
- **This phase is NOT optional.** Always invoke it regardless of issue size or complexity. Do NOT invent skip heuristics (e.g. "FAST_PATH") — the context subcommand handles trivial cases gracefully and returns quickly.
180
+ **For STANDARD and COMPLEX tasks**: Always invoke. Do NOT skip without a TRIVIAL COMPLEXITY_BAND.
167
181
 
168
182
  Invoke the context subcommand to surface historical review findings and bug patterns:
169
183
 
@@ -184,9 +198,11 @@ Skill("work-on:build:context", args="{NUMBER} --repo {GH_REPO} --gh-flag {GH_FLA
184
198
 
185
199
  ---
186
200
 
187
- ## Phase B4: Architecture Planning (MANDATORY Subcommand)
201
+ ## Phase B4: Architecture Planning (MANDATORY for STANDARD/COMPLEX — skip for TRIVIAL)
188
202
 
189
- **This phase is NOT optional.** Always invoke it regardless of issue size or complexity. Do NOT invent skip heuristics — the architect subcommand handles simple changes gracefully and returns quickly. Even a 1-file fix benefits from cross-path consistency checks.
203
+ **Skip if COMPLEXITY_BAND: TRIVIAL** (read from FORGE:FAST_PATH in Phase B0) — do not invoke the architect subcommand. Proceed directly to Phase B5.
204
+
205
+ **For STANDARD and COMPLEX tasks**: Always invoke. Even a 1-file STANDARD fix benefits from cross-path consistency checks. Do NOT skip without a TRIVIAL COMPLEXITY_BAND.
190
206
 
191
207
  Invoke the architect subcommand to trace all affected code paths and produce an ordered implementation plan:
192
208
 
@@ -229,9 +245,18 @@ Skill("work-on:build:validate", args="{NUMBER} --repo {GH_REPO} --gh-flag {GH_FL
229
245
  Where `{CHANGED_FILES}` is the space-separated list of files changed by the implement subcommand (read from `IMPLEMENT_RESULT` or from the `<!-- FORGE:BUILDER -->` comment).
230
246
 
231
247
  **After subcommand returns**:
232
- - `VALIDATE_RESULT: gate_passed: true` → build complete, return `BUILD_RESULT: status: COMPLETE`
248
+ - `VALIDATE_RESULT: gate_passed: true` → write checkpoint, then return `BUILD_RESULT: status: COMPLETE`
233
249
  - `VALIDATE_RESULT: gate_passed: false` → subcommand has already posted comment and added `needs-human` label; return `BUILD_RESULT: status: BLOCKED`
234
250
 
251
+ **When gate_passed is true — write machine-readable phase checkpoint before returning (MANDATORY)**:
252
+ ```bash
253
+ CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
254
+ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
255
+ \`\`\`json
256
+ {\"phase\": \"BUILD\", \"status\": \"COMPLETE\", \"next_phase\": \"REVIEW\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
257
+ \`\`\`"
258
+ ```
259
+
235
260
  ---
236
261
 
237
262
  ## Output