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: Self-analysis — measures pipeline performance, correlates with prompt changes, proposes improvements
3
3
  argument-hint: [project repo slug or "all"]
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # /pipeline-health — Forge Self-Analysis
7
9
 
@@ -541,6 +543,57 @@ fi
541
543
 
542
544
  **Why this matters**: Outcome metrics (findings per PR, fix-up rate) measure what the pipeline produces. Orchestration efficiency measures how much wasted motion occurs getting there. An agent that takes 40 minutes to do 8 minutes of actual work has 80% idle time — all of which is pipeline overhead. Reducing stall time directly improves throughput without changing code quality. This is the only metric that reveals whether the routing loop in `work-on.md` is functioning or causing agents to stop and wait.
543
545
 
546
+ **Step 4 — Batch stall rate (from FORGE:STALL_DETECTED annotations)**:
547
+
548
+ Query issues that received a `<!-- FORGE:STALL_DETECTED -->` comment within the analysis window. This measures how often the orchestrator's time-based stall detector fired — distinct from the resume-cycle count (which comes from completion-event stalls).
549
+
550
+ ```bash
551
+ # Denominator: issues that entered the pipeline in the window (have any workflow:* label)
552
+ # Using pipeline-active issues avoids inflating the denominator with all 500 issues in window
553
+ BATCH_TOTAL=$(gh issue list -R $GH_REPO \
554
+ --state all --limit 500 \
555
+ --json number,createdAt,labels \
556
+ --jq "[.[] | select(.createdAt > \"$SINCE\") | select(.labels | map(.name) | any(startswith(\"workflow:\")))] | length" \
557
+ 2>/dev/null || echo 0)
558
+
559
+ # Enumerate pipeline-active issue numbers for per-issue comment queries
560
+ PIPELINE_ISSUE_NUMS=$(gh issue list -R $GH_REPO \
561
+ --state all --limit 500 \
562
+ --json number,createdAt,labels \
563
+ --jq "[.[] | select(.createdAt > \"$SINCE\") | select(.labels | map(.name) | any(startswith(\"workflow:\"))) | .number]" \
564
+ 2>/dev/null || echo "[]")
565
+
566
+ # Count issues with at least one FORGE:STALL_DETECTED comment
567
+ # Use wc -l pattern (not variable accumulation) to avoid bash subshell scope loss
568
+ STALL_COUNT=$(echo "$PIPELINE_ISSUE_NUMS" | jq -r '.[]' | while read -r NUM; do
569
+ STALL_COMMENTS=$(gh api repos/$GH_REPO/issues/${NUM}/comments \
570
+ --jq '[.[] | select(.body | contains("FORGE:STALL_DETECTED"))] | length' 2>/dev/null || echo 0)
571
+ [ "$STALL_COMMENTS" -gt 0 ] && echo "$NUM"
572
+ done | wc -l)
573
+
574
+ # Count issues that escalated (2+ stall comments = auto-resume exhausted)
575
+ ESCALATED_COUNT=$(echo "$PIPELINE_ISSUE_NUMS" | jq -r '.[]' | while read -r NUM; do
576
+ STALL_COMMENTS=$(gh api repos/$GH_REPO/issues/${NUM}/comments \
577
+ --jq '[.[] | select(.body | contains("FORGE:STALL_DETECTED"))] | length' 2>/dev/null || echo 0)
578
+ [ "$STALL_COMMENTS" -ge 2 ] && echo "$NUM"
579
+ done | wc -l)
580
+
581
+ # Compute batch stall rate
582
+ if [ "$BATCH_TOTAL" -gt 0 ]; then
583
+ BATCH_STALL_RATE=$(echo "scale=1; $STALL_COUNT * 100 / $BATCH_TOTAL" | bc 2>/dev/null || echo "N/A")
584
+ echo "Batch stall rate: ${BATCH_STALL_RATE}% (${STALL_COUNT}/${BATCH_TOTAL} pipeline issues stalled) — target: < 10%"
585
+ echo "Escalated to needs-human: ${ESCALATED_COUNT} issues"
586
+ else
587
+ BATCH_STALL_RATE="N/A"
588
+ echo "Batch stall rate: N/A (no pipeline issues in window or FORGE:STALL_DETECTED not yet deployed)"
589
+ fi
590
+ export BATCH_STALL_RATE STALL_COUNT BATCH_TOTAL ESCALATED_COUNT
591
+ ```
592
+
593
+ **Target**: `BATCH_STALL_RATE` < 10% (stalled issues / total issues in window). A rate above 10% indicates systemic pipeline unreliability — agents stopping without reaching terminal state frequently enough to require orchestrator intervention.
594
+
595
+ **Note**: `BATCH_STALL_RATE` requires `FORGE:STALL_DETECTED` comments, which are written by the stall detector in `/orchestrate` Step 4B.5. If the stall detector is not deployed, this metric returns N/A and falls back to the resume-cycle proxy from Step 3.
596
+
544
597
  ### 2L: Post-deploy failure metrics (pipeline escape signal)
545
598
 
546
599
  `/audit` creates `audit-finding` labeled issues in the Forge repo whenever a defect escapes the pipeline and reaches staging or production. Each finding is tagged with a `**Failure point**:` field indicating which pipeline phase failed (INVESTIGATION, IMPLEMENTATION, REVIEW, REVIEW_FALSE_NEG, DEPLOY_GATE, etc.). Aggregating these reveals the post-deploy defect rate and review escape rate — signals invisible to the pre-merge metrics above.
@@ -2100,6 +2153,8 @@ gh issue create -R $FORGE_REPO \
2100
2153
  | — Avg agent idle% | ?% | < 30% | ✅/⚠️/❌ |
2101
2154
  | — Avg resume cycles per agent | ? | < 1 | ✅/⚠️/❌ |
2102
2155
  | — Clean agent rate | ?% | > 60% | ✅/⚠️/❌ |
2156
+ | — Batch stall rate | ?% (?/? issues) | < 10% | ✅/⚠️/❌ |
2157
+ | — Escalated to needs-human | ? issues | 0 | ✅/⚠️/❌ |
2103
2158
  | Original work ratio | ?% | > 70% | ✅/⚠️/❌ |
2104
2159
  | Milestone merge success | ?% | 80%+ | ✅/⚠️/❌ |
2105
2160
  | Issue close velocity | ? days | < 2 days | ✅/⚠️/❌ |
@@ -2123,13 +2178,15 @@ _If no trajectory data available in window: "No FORGE:TRAJECTORY comments found
2123
2178
 
2124
2179
  ## Orchestration Efficiency
2125
2180
 
2126
- _From Phase 2K — persisted FORGE:AUDIT-AGENTS summaries ([N] sessions, [M] agents total)_
2181
+ _From Phase 2K — persisted FORGE:AUDIT-AGENTS summaries ([N] sessions, [M] agents total) + FORGE:STALL_DETECTED annotations_
2127
2182
 
2128
2183
  | Metric | Value | Target | Status |
2129
2184
  |--------|-------|--------|--------|
2130
2185
  | Avg agent idle% | ?% | < 30% | ✅/⚠️/❌ |
2131
2186
  | Avg resume cycles per agent | ? | < 1 | ✅/⚠️/❌ |
2132
2187
  | Clean agent rate | ?% | > 60% | ✅/⚠️/❌ |
2188
+ | Batch stall rate | ?% (?/? issues) | < 10% | ✅/⚠️/❌ |
2189
+ | Escalated to needs-human | ? issues | 0 | ✅/⚠️/❌ |
2133
2190
 
2134
2191
  **Top stall boundaries** (phase transitions causing the most stalls):
2135
2192
  | Boundary | Occurrences |
@@ -2140,6 +2197,8 @@ _From Phase 2K — persisted FORGE:AUDIT-AGENTS summaries ([N] sessions, [M] age
2140
2197
 
2141
2198
  _If no FORGE:AUDIT-AGENTS data available: "No persisted audit-agents summaries found in window. Run \`audit-agents --persist\` after orchestration runs to enable this metric. Proxy: [N] issues had resume/stall anomalies in trajectory data."_
2142
2199
 
2200
+ _Batch stall rate: sourced from \`FORGE:STALL_DETECTED\` comments (requires \`/orchestrate\` Step 4B.5 to be active). N/A if stall detector is not deployed._
2201
+
2143
2202
  ## Transcript Flow Metrics
2144
2203
 
2145
2204
  _From Phase 2N — JSONL transcript analytics ([N] pipeline sessions, [M] subagent sessions in window)_
@@ -2,6 +2,8 @@
2
2
  description: Full platform QA sweep — auto-discovers every page, tests every UI element via browser automation, creates GitHub issues for all findings
3
3
  argument-hint: [all | dashboard | marketing | page <route> | journey | visual | functional | a11y]
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # /qa-sweep — Platform-Wide QA Testing
7
9
 
@@ -14,9 +16,26 @@ You are the QA orchestrator. Auto-discover every page across the platform (dashb
14
16
 
15
17
  ---
16
18
 
19
+ ## Playwright MCP Prerequisite (MANDATORY — check before proceeding)
20
+
21
+ Playwright MCP is a **guaranteed ForgeDock dependency** for all browser automation commands. Before running any qa-sweep phase, verify it is available:
22
+
23
+ **Check**: Confirm `mcp__playwright__*` tools are available in your tool context (e.g., `mcp__playwright__browser_navigate`, `mcp__playwright__browser_snapshot`).
24
+
25
+ **If Playwright MCP tools are NOT available**: STOP immediately. Do not attempt to run qa-sweep — all browser automation steps will fail silently.
26
+
27
+ **Fix**:
28
+ 1. Register Playwright MCP: `claude mcp add playwright npx @playwright/mcp@latest`
29
+ 2. Restart Claude Code to reload MCP servers
30
+ 3. Verify with: `npx forgedock doctor` (Check 9 confirms registration)
31
+
32
+ See README.md Requirements section for full setup instructions.
33
+
34
+ ---
35
+
17
36
  ## Browser Tool Reference
18
37
 
19
- All browser automation uses Playwright MCP tools (`mcp__playwright__*`).
38
+ All browser automation uses Playwright MCP tools (`mcp__playwright__*`). Playwright MCP is a guaranteed ForgeDock dependency — see the Playwright MCP Prerequisite section above.
20
39
 
21
40
  | Action | Tool | Key param |
22
41
  |--------|------|-----------|
@@ -95,7 +114,25 @@ Depth-first, workflow-driven:
95
114
 
96
115
  ## Phase 0: Setup & Auth
97
116
 
98
- **Load config from forge.yaml** before running any checks:
117
+ **Playwright MCP prerequisite guard** — run BEFORE loading forge.yaml config:
118
+
119
+ Confirm `mcp__playwright__browser_navigate` (or any `mcp__playwright__*` tool) is available in your tool context. If Playwright MCP tools are absent:
120
+
121
+ ```
122
+ STOP. Playwright MCP is not registered in this Claude Code session.
123
+
124
+ Register it with: claude mcp add playwright npx @playwright/mcp@latest
125
+ Then restart Claude Code and run /qa-sweep again.
126
+
127
+ Verify installation with: npx forgedock doctor (Check 9 confirms Playwright MCP status)
128
+ See: README.md Requirements section for full setup instructions.
129
+ ```
130
+
131
+ Do not proceed past this guard if Playwright MCP tools are unavailable — all browser automation steps will fail silently without clear error messages.
132
+
133
+ ---
134
+
135
+ **Load config from forge.yaml** after confirming Playwright MCP is available:
99
136
 
100
137
  ```bash
101
138
  QA_CONFIG=$(python3 - <<'PYEOF'
@@ -2,6 +2,8 @@
2
2
  description: Pre-commit quality check — catches defects the review would flag, so the builder can fix them before committing
3
3
  argument-hint: (invoked by /work-on, not directly)
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # /quality-gate — Pre-Commit Quality Check
7
9
 
@@ -15,6 +17,42 @@ You do NOT post to GitHub. You do NOT create issues. You return findings directl
15
17
 
16
18
  ---
17
19
 
20
+ ## Adaptive Design
21
+
22
+ This quality gate uses **domain detection** to adapt to your project's actual tech stack. Checks are not one-size-fits-all — they are selectively triggered based on the file types and patterns present in each change.
23
+
24
+ **How it works**:
25
+ 1. Changed files are classified into domains (AUTH, DEPLOY, DATABASE, FRONTEND, etc.) by pattern-matching against file paths, extensions, and diff content
26
+ 2. Only checks for detected domains are executed — irrelevant checks are skipped entirely
27
+ 3. A project that only changes markdown files runs zero domain checks; a project that changes only TypeScript components runs only SECURITY, FRONTEND, and PROXY checks
28
+
29
+ **Universal check** (always runs for any code file):
30
+ - **SECURITY** — SQL injection, SSRF, path traversal, hardcoded secrets, XSS, command injection
31
+
32
+ **Conditional checks** (triggered only when relevant patterns are detected):
33
+
34
+ | Domain | Triggered when |
35
+ |--------|----------------|
36
+ | AUTH | Files matching `*router*` or `route.ts` handlers |
37
+ | DEPLOY | Files referencing `os.getenv` or `process.env` |
38
+ | DATABASE | `.sql` files or files under `migrations/` |
39
+ | FRONTEND | `.tsx`/`.ts` files in client component paths |
40
+ | PROXY | Client components using `fetch`/`useSWR`/`apiFetch` |
41
+ | SHELL | `.sh` files or scripts using `curl`/`wget` |
42
+ | STRING_SETS | Python files in anti-detection or browser automation paths |
43
+ | CONCURRENCY | Python files using `asyncio.shield`, `asyncio.wait_for`, or `Task.cancel` |
44
+ | STATE | Python files with new module-level mutable variable assignments |
45
+ | CAPACITY | Worker/infra Python files with new size/limit/threshold constants |
46
+ | WORKFLOW | `.yml` files in `.github/workflows/` |
47
+ | IMPORT_RESOLUTION | Python files with new `from app.*` imports added outside `try:` blocks |
48
+ | INFRA | Dockerfiles or entrypoints with runtime UID changes |
49
+ | ROUTER_BUG | Router Python files where gate conditions are narrowed |
50
+ | FORGE_GRAPH | `commands/*.md` specs or `scripts/*` files change (ForgeDock self-consistency) |
51
+
52
+ **Stack-agnostic coverage**: The example domains above reflect common patterns caught in production; they are not requirements. A Go or Ruby project benefits from SECURITY checks. A Node.js project benefits from FRONTEND, PROXY, and DEPLOY checks. Any project benefits from DATABASE and WORKFLOW checks when those file types are present. The stack-specific examples (Python routers, SOPS secrets chain, FastAPI layouts, appleboy SSH deploys) are illustrative — the domain detection system applies the applicable subset to any codebase.
53
+
54
+ ---
55
+
18
56
  ## Input Format
19
57
 
20
58
  The builder passes:
@@ -61,6 +99,7 @@ Before running checks, classify the changed files into domains. This avoids runn
61
99
  | `*.py` with new `from app.` import statements added outside `try:` blocks | IMPORT_RESOLUTION |
62
100
  | `Dockerfile*` or `entrypoint*.sh` with added/changed `USER`, `su-exec`, `gosu`, or `setuid` | INFRA |
63
101
  | `*.py` in `routers/` where the diff removes a line containing an or-gate condition (lines starting with `-` containing `if.*or`) | ROUTER_BUG |
102
+ | `commands/*.md` or `scripts/*` files (ForgeDock repo only — dogfoods its own spec graph) | FORGE_GRAPH |
64
103
 
65
104
  **Apply the classification:**
66
105
 
@@ -88,6 +127,9 @@ for f in {CHANGED_FILES}; do
88
127
  echo "$f" | grep -qE '\.py$' && DOMAINS="$DOMAINS STRING_SETS"
89
128
  ;;
90
129
  esac
130
+ case "$f" in
131
+ commands/*.md|scripts/*) DOMAINS="$DOMAINS FORGE_GRAPH" ;;
132
+ esac
91
133
  case "$f" in
92
134
  .github/workflows/*.yml) DOMAINS="$DOMAINS WORKFLOW" ;;
93
135
  esac
@@ -375,6 +417,31 @@ done
375
417
 
376
418
  Report any hit as **HIGH** — the DB connection will fail in any environment where PostgreSQL is not on `127.0.0.1:5432`.
377
419
 
420
+ ### 2G.6: Spec graph self-consistency (FORGE_GRAPH domain)
421
+
422
+ **Triggered when**: changed files include `commands/*.md` specs or `scripts/*` files (ForgeDock repo only — it dogfoods its own Spec Knowledge Graph). Skips silently in any project without `scripts/validate-spec-graph.sh`.
423
+
424
+ **Why this matters**: ForgeDock's pipeline behavior is encoded in a ~1.2 MB prose spec corpus where drift is invisible — a `FORGE:*` annotation written but never read, a `Skill(skill="X")` referencing a sub-phase that no longer exists, or a `workflow:*` label set by no command. The Spec Knowledge Graph (`build-spec-graph.mjs`) makes this queryable; `validate-spec-graph.sh` consumes it and partitions findings into HARD (dangling cross-refs, broken label transitions) and SOFT (orphan annotations).
425
+
426
+ ```bash
427
+ # Run only when commands/*.md or scripts/* changed and the validator ships in this repo.
428
+ VALIDATOR="$FORGEDOCK_SCRIPTS/validate-spec-graph.sh"
429
+ [ -f "$VALIDATOR" ] || VALIDATOR="{WORKTREE_PATH}/scripts/validate-spec-graph.sh"
430
+ if [ -f "$VALIDATOR" ]; then
431
+ # The graph is auto-built from the worktree (gitignored JSON not required).
432
+ # --soft keeps the gate non-blocking on the documented baseline orphans;
433
+ # HARD findings are still reported below and surfaced as gate findings.
434
+ GRAPH_REPORT=$(bash "$VALIDATOR" --root "{WORKTREE_PATH}" --soft 2>&1 || true)
435
+ echo "$GRAPH_REPORT"
436
+ # Map any HARD finding to a quality-gate finding (dangling ref / broken transition).
437
+ echo "$GRAPH_REPORT" | grep -E '^\s*\[HARD\]' | while IFS= read -r hit; do
438
+ echo "FORGE_GRAPH | HIGH | spec-graph | ${hit#*] }"
439
+ done
440
+ fi
441
+ ```
442
+
443
+ Report each `[HARD]` line as **HIGH** — a dangling `Skill()` target or a label set by no command is a real pipeline break (an agent will invoke a phase that does not exist, or a state will never be entered/exited). Orphan-annotation `[SOFT]` lines are advisory: surface them in the report for periodic triage but do NOT block the commit. Scaffolding `[INFO]` lines are expected and need no action. To gate on HARD findings (fail the build), drop `--soft`; the default keeps existing baseline orphans from blocking until they are triaged. <!-- Added: forge#869 -->
444
+
378
445
  ### 2H: Asyncio cancellation safety (Python async code)
379
446
 
380
447
  **Triggered when**: changed Python files contain `asyncio.shield`, `asyncio.wait_for`, or `Task.cancel`.
@@ -0,0 +1,263 @@
1
+ ---
2
+ description: Read-only playback of a completed pipeline run — shows FORGE annotations phase by phase with timestamps
3
+ argument-hint: <issue-number>
4
+ ---
5
+
6
+ # /replay — Pipeline Run Playback
7
+
8
+ **Input**: $ARGUMENTS (issue number)
9
+
10
+ Replay a completed pipeline run. Fetches all FORGE annotations posted during the pipeline and displays them chronologically — phase headings, timestamps, and key structured fields. Read-only: nothing is written, no labels changed.
11
+
12
+ **Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`.
13
+
14
+ ---
15
+
16
+ ## Config Preamble
17
+
18
+ ```bash
19
+ CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
20
+ if [ -f "$CONFIG_FILE" ]; then
21
+ GH_OWNER=$(yq '.project.owner' "$CONFIG_FILE")
22
+ GH_REPO_NAME=$(yq '.project.repo' "$CONFIG_FILE")
23
+ GH_REPO="${GH_OWNER}/${GH_REPO_NAME}"
24
+ GH_FLAG="-R $GH_REPO"
25
+ else
26
+ echo "ERROR: forge.yaml not found."
27
+ echo "Run: npx forgedock init"
28
+ exit 1
29
+ fi
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Step 1: Parse Input
35
+
36
+ ```bash
37
+ NUMBER=$(echo "$ARGUMENTS" | grep -oP '^\s*#?\K\d+' | head -1)
38
+ if [ -z "$NUMBER" ]; then
39
+ echo "Usage: /replay <issue-number>"
40
+ echo "Example: /replay 42"
41
+ exit 1
42
+ fi
43
+ echo "Replaying pipeline run for issue #${NUMBER}..."
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Step 2: Fetch Issue Metadata
49
+
50
+ ```bash
51
+ ISSUE_JSON=$(gh issue view "$NUMBER" "$GH_FLAG" \
52
+ --json number,title,state,labels,createdAt,closedAt 2>/dev/null)
53
+
54
+ if [ -z "$ISSUE_JSON" ] || echo "$ISSUE_JSON" | grep -q '"message"'; then
55
+ echo "ERROR: Issue #${NUMBER} not found in ${GH_REPO}."
56
+ exit 1
57
+ fi
58
+
59
+ ISSUE_TITLE=$(echo "$ISSUE_JSON" | jq -r '.title')
60
+ ISSUE_STATE=$(echo "$ISSUE_JSON" | jq -r '.state')
61
+ ISSUE_CREATED=$(echo "$ISSUE_JSON" | jq -r '.createdAt')
62
+ ISSUE_CLOSED=$(echo "$ISSUE_JSON" | jq -r '.closedAt // "open"')
63
+ WORKFLOW_LABELS=$(echo "$ISSUE_JSON" | jq -r '[.labels[].name | select(startswith("workflow:"))] | join(", ")')
64
+
65
+ echo ""
66
+ echo "══════════════════════════════════════════════════════════════"
67
+ echo " Pipeline Replay — #${NUMBER}: ${ISSUE_TITLE}"
68
+ echo "══════════════════════════════════════════════════════════════"
69
+ echo " State : ${ISSUE_STATE}"
70
+ echo " Opened : ${ISSUE_CREATED}"
71
+ [ "$ISSUE_CLOSED" != "open" ] && echo " Closed : ${ISSUE_CLOSED}"
72
+ [ -n "$WORKFLOW_LABELS" ] && echo " Labels : ${WORKFLOW_LABELS}"
73
+ echo "══════════════════════════════════════════════════════════════"
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Step 3: Fetch All Comments
79
+
80
+ ```bash
81
+ # One API call — returns all comments in chronological order
82
+ ALL_COMMENTS=$(gh api "repos/${GH_REPO}/issues/${NUMBER}/comments" 2>/dev/null)
83
+
84
+ if [ -z "$ALL_COMMENTS" ] || [ "$ALL_COMMENTS" = "[]" ]; then
85
+ echo ""
86
+ echo " No comments on this issue. No pipeline run to replay."
87
+ exit 0
88
+ fi
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Step 4: Identify FORGE Annotations
94
+
95
+ ```bash
96
+ # Filter to comments that contain a FORGE pipeline annotation
97
+ FORGE_COMMENTS=$(echo "$ALL_COMMENTS" | jq -c '[.[] | select(.body | test("<!-- FORGE:(INVESTIGATOR|CONTRACT|CONTEXT|ARCHITECT|BUILDER|DECOMPOSED|TRAJECTORY)"))]')
98
+ FORGE_COUNT=$(echo "$FORGE_COMMENTS" | jq 'length')
99
+
100
+ if [ "$FORGE_COUNT" -eq 0 ]; then
101
+ echo ""
102
+ echo " No FORGE pipeline annotations found on issue #${NUMBER}."
103
+ echo " This issue was not processed by the /work-on pipeline,"
104
+ echo " or comments were posted before pipeline annotation was introduced."
105
+ exit 0
106
+ fi
107
+
108
+ echo ""
109
+ echo " Found ${FORGE_COUNT} pipeline annotation(s). Playing back..."
110
+ echo ""
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Step 5: Display Each Annotation
116
+
117
+ Phase mapping (inline): `INVESTIGATOR`→Phase 1, `CONTRACT`→Phase 3C, `CONTEXT`→Phase 3C.5, `ARCHITECT`→Phase 3C.6, `BUILDER`→Phase 3M, `DECOMPOSED`→Phase 2, `TRAJECTORY`→Phase 7.
118
+
119
+ For each FORGE comment in chronological order, print a formatted block. Use Python for robust multiline extraction from the comment body.
120
+
121
+ ```bash
122
+ echo "$FORGE_COMMENTS" | jq -c '.[]' | while IFS= read -r COMMENT; do
123
+ BODY=$(echo "$COMMENT" | jq -r '.body')
124
+ CREATED=$(echo "$COMMENT" | jq -r '.created_at')
125
+ AUTHOR=$(echo "$COMMENT" | jq -r '.user.login')
126
+
127
+ # Identify annotation type
128
+ ANNOTATION=$(echo "$BODY" | grep -oP '(?<=<!-- )FORGE:[A-Z:]+(?= -->| -->)' | head -1)
129
+
130
+ # Map to phase label
131
+ case "$ANNOTATION" in
132
+ "FORGE:INVESTIGATOR") PHASE_LABEL="Phase 1 — Investigation" ;;
133
+ "FORGE:CONTRACT") PHASE_LABEL="Phase 3C — Builder Contract" ;;
134
+ "FORGE:CONTEXT") PHASE_LABEL="Phase 3C.5 — Context Gathering" ;;
135
+ "FORGE:ARCHITECT") PHASE_LABEL="Phase 3C.6 — Architecture Plan" ;;
136
+ "FORGE:BUILDER") PHASE_LABEL="Phase 3M — Implementation" ;;
137
+ "FORGE:DECOMPOSED") PHASE_LABEL="Phase 2 — Decomposition" ;;
138
+ "FORGE:TRAJECTORY") PHASE_LABEL="Phase 7 — Pipeline Trajectory" ;;
139
+ *) PHASE_LABEL="$ANNOTATION" ;;
140
+ esac
141
+
142
+ # Format timestamp: 2025-01-15T14:32:00Z → 2025-01-15 14:32 UTC
143
+ TIMESTAMP=$(echo "$CREATED" | sed 's/T/ /; s/:[0-9][0-9]Z/ UTC/')
144
+
145
+ echo "┌─────────────────────────────────────────────────────────────"
146
+ echo "│ ${PHASE_LABEL}"
147
+ echo "│ ${TIMESTAMP} · @${AUTHOR}"
148
+ echo "├─────────────────────────────────────────────────────────────"
149
+
150
+ # Extract key fields per annotation type
151
+ ANNOTATION="$ANNOTATION" BODY_CONTENT="$BODY" python3 - <<'PYEOF'
152
+ import re, sys, os
153
+
154
+ body = os.environ.get('BODY_CONTENT', '')
155
+
156
+ def extract(pattern, default="—"):
157
+ m = re.search(pattern, body, re.MULTILINE | re.DOTALL)
158
+ return m.group(1).strip() if m else default
159
+
160
+ def count_lines(header):
161
+ m = re.search(r'### ' + header + r'\n(.*?)(?=\n###|\Z)', body, re.DOTALL)
162
+ if not m: return 0
163
+ return len([l for l in m.group(1).strip().splitlines() if l.strip()])
164
+
165
+ annotation = os.environ.get('ANNOTATION', '')
166
+
167
+ if annotation == "FORGE:INVESTIGATOR":
168
+ verdict = extract(r'\*\*Verdict\*\*:\s*(\S+)')
169
+ confidence = extract(r'\*\*Confidence\*\*:\s*(\S+)')
170
+ severity = extract(r'\*\*Severity\*\*:\s*(\S+)')
171
+ task_type = extract(r'\*\*Task Type\*\*:\s*([^\n]+)')
172
+ root_cause = extract(r'### Root Cause\n([^\n]+)')
173
+ decompose = extract(r'\*\*(YES|NO)\*\* —', '—')
174
+ print(f"│ Verdict : {verdict} ({confidence} confidence)")
175
+ print(f"│ Severity : {severity} | Task type: {task_type}")
176
+ print(f"│ Root cause : {root_cause[:80]}")
177
+ print(f"│ Decompose : {decompose}")
178
+
179
+ elif annotation == "FORGE:CONTRACT":
180
+ task_type = extract(r'\*\*Task type\*\*:\s*([^\n]+)')
181
+ approach = extract(r'### Proposed Approach\n([^\n]+)')
182
+ n_files = len(re.findall(r'^\|[^|]+\|[^|]+\|[^|]+\|', body, re.MULTILINE)) - 2 # subtract header+sep
183
+ n_criteria = len(re.findall(r'^- \[', body, re.MULTILINE))
184
+ print(f"│ Task type : {task_type}")
185
+ print(f"│ Approach : {approach[:80]}")
186
+ print(f"│ Deliverables: {max(0,n_files)} file(s) | Criteria: {n_criteria}")
187
+
188
+ elif annotation == "FORGE:CONTEXT":
189
+ pitfalls = extract(r'### Known Pitfalls[^\n]*\n([^\n]+)')
190
+ print(f"│ Pitfalls : {pitfalls[:80]}")
191
+ print(f"│ (Context gathered — see full comment for details)")
192
+
193
+ elif annotation == "FORGE:ARCHITECT":
194
+ n_paths = len(re.findall(r'^\| \d+', body, re.MULTILINE))
195
+ order_m = re.search(r'### Implementation Order\n(.*?)(?=\n###|\Z)', body, re.DOTALL)
196
+ n_steps = len([l for l in (order_m.group(1).strip().splitlines() if order_m else []) if re.match(r'^\d+\.', l.strip())])
197
+ print(f"│ Paths : {n_paths} affected path(s)")
198
+ print(f"│ Order : {n_steps} implementation step(s)")
199
+ print(f"│ (Full plan in comment)")
200
+
201
+ elif annotation == "FORGE:BUILDER":
202
+ branch = extract(r'\*\*Branch\*\*:\s*`([^`]+)`')
203
+ commits = extract(r'\*\*Commits\*\*:\s*([^\n]+)')
204
+ n_files = extract(r'\*\*Files changed\*\*:\s*(\d+)')
205
+ approach = extract(r'### Approach\n([^\n]+)')
206
+ print(f"│ Branch : {branch}")
207
+ print(f"│ Commits : {commits}")
208
+ print(f"│ Files : {n_files}")
209
+ print(f"│ Approach : {approach[:80]}")
210
+
211
+ elif annotation == "FORGE:DECOMPOSED":
212
+ subs_m = re.search(r'### Sub-Issues Created\n(.*?)(?=\n###|\Z)', body, re.DOTALL)
213
+ subs = [l.strip() for l in (subs_m.group(1).strip().splitlines() if subs_m else []) if l.strip().startswith('-')]
214
+ print(f"│ Sub-issues : {len(subs)}")
215
+ for s in subs[:5]:
216
+ print(f"│ {s}")
217
+
218
+ elif annotation == "FORGE:TRAJECTORY":
219
+ # Print the phase table lines verbatim (they're already formatted)
220
+ in_table = False
221
+ count = 0
222
+ for line in body.splitlines():
223
+ if '| Phase' in line or (in_table and line.startswith('|')):
224
+ in_table = True
225
+ print(f"│ {line}")
226
+ count += 1
227
+ elif in_table and not line.startswith('|'):
228
+ break
229
+ if count == 0:
230
+ print("│ (Trajectory table not found in comment)")
231
+ decisions = extract(r'\*\*Decisions\*\*:\s*([^\n]+)')
232
+ anomalies = extract(r'\*\*Anomalies\*\*:\s*([^\n]+)')
233
+ print(f"│ Decisions : {decisions[:80]}")
234
+ print(f"│ Anomalies : {anomalies[:80]}")
235
+
236
+ else:
237
+ # Unknown annotation — print first non-empty, non-marker line
238
+ for line in body.splitlines():
239
+ stripped = line.strip()
240
+ if stripped and not stripped.startswith('<!--') and not stripped.startswith('#'):
241
+ print(f"│ {stripped[:100]}")
242
+ break
243
+ PYEOF
244
+
245
+ echo "└─────────────────────────────────────────────────────────────"
246
+ echo ""
247
+ done
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Step 7: Summary Footer
253
+
254
+ ```bash
255
+ echo "══════════════════════════════════════════════════════════════"
256
+ echo " Replay complete — ${FORGE_COUNT} pipeline phase(s) shown"
257
+ echo " Issue: https://github.com/${GH_REPO}/issues/${NUMBER}"
258
+ echo "══════════════════════════════════════════════════════════════"
259
+ ```
260
+
261
+ ---
262
+
263
+ **Constraints**: Read-only — never writes to GitHub, never posts comments, never edits labels. Partial pipeline runs are shown as-is (missing phases are absent, not errors).