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
@@ -0,0 +1,354 @@
1
+ ---
2
+ description: Auto-generate release notes from merged PRs and FORGE:TRAJECTORY annotations — grouped by conventional commit type
3
+ argument-hint: [v1.0.0..v1.1.0 | since:YYYY-MM-DD | last-N]
4
+ ---
5
+
6
+ # /changelog — Release Notes Generator
7
+
8
+ **Input**: $ARGUMENTS (default: last 30 merged PRs on the staging branch)
9
+
10
+ Generate a structured markdown changelog from merged PRs and their FORGE:TRAJECTORY annotations. Groups entries by conventional commit type (feat, fix, refactor, docs, chore) and enriches each entry with task type and investigation verdict from the pipeline's annotation layer.
11
+
12
+ **Agent model policy**: Default `model: "sonnet"`. Fallback: `model: "opus"` if Sonnet is rate-limited.
13
+
14
+ **NEVER use plan mode (EnterPlanMode)** — it breaks execution context.
15
+
16
+ ---
17
+
18
+ ## Config Resolution
19
+
20
+ Read `forge.yaml` before running any phase. If missing, stop and tell the user to run `npx forgedock init`.
21
+
22
+ ```bash
23
+ CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
24
+
25
+ if [ ! -f "$CONFIG_FILE" ]; then
26
+ echo "ERROR: forge.yaml not found. Run: npx forgedock init"
27
+ exit 1
28
+ fi
29
+
30
+ GH_REPO=$(yq '.project.owner + "/" + .project.repo' "$CONFIG_FILE")
31
+ GH_FLAG="-R $GH_REPO"
32
+ REPO_PATH=$(yq '.paths.root' "$CONFIG_FILE")
33
+ STAGING_BRANCH=$(yq '.branches.staging' "$CONFIG_FILE")
34
+
35
+ echo "Repo: $GH_REPO"
36
+ echo "Branch: $STAGING_BRANCH"
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Phase 1: Parse Arguments
42
+
43
+ Resolve the date/commit range from `$ARGUMENTS`.
44
+
45
+ ```bash
46
+ ARGUMENTS="${ARGUMENTS:-}"
47
+
48
+ # Default: last 30 merged PRs
49
+ MODE="count"
50
+ COUNT=30
51
+ SINCE_DATE=""
52
+ TAG_FROM=""
53
+ TAG_TO=""
54
+
55
+ if [ -z "$ARGUMENTS" ]; then
56
+ MODE="count"
57
+ COUNT=30
58
+ echo "Mode: last $COUNT merged PRs (default)"
59
+
60
+ elif echo "$ARGUMENTS" | grep -qE '^last-[0-9]+$'; then
61
+ # last-N: last N merged PRs
62
+ COUNT=$(echo "$ARGUMENTS" | grep -oE '[0-9]+')
63
+ MODE="count"
64
+ echo "Mode: last $COUNT merged PRs"
65
+
66
+ elif echo "$ARGUMENTS" | grep -qE '^since:[0-9]{4}-[0-9]{2}-[0-9]{2}'; then
67
+ # since:YYYY-MM-DD: all PRs merged after this date
68
+ SINCE_DATE=$(echo "$ARGUMENTS" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}')
69
+ MODE="since"
70
+ echo "Mode: since $SINCE_DATE"
71
+
72
+ elif echo "$ARGUMENTS" | grep -qE '^v?[A-Za-z0-9._-]+ *\.\. *v?[A-Za-z0-9._-]+'; then
73
+ # tag-to-tag: v1.0.0..v1.1.0
74
+ TAG_FROM=$(echo "$ARGUMENTS" | sed 's/ *\.\..*//')
75
+ TAG_TO=$(echo "$ARGUMENTS" | sed 's/.*\.\. *//')
76
+ MODE="tag-range"
77
+ echo "Mode: $TAG_FROM..$TAG_TO"
78
+
79
+ else
80
+ echo "WARNING: Unrecognized argument format: '$ARGUMENTS'"
81
+ echo "Falling back to: last 30 merged PRs"
82
+ MODE="count"
83
+ COUNT=30
84
+ fi
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Phase 2: Collect Merged PRs
90
+
91
+ Collect PRs merged to the staging branch in the resolved range. Use `while read` for all iteration — never `for VAR in $(command)`.
92
+
93
+ ```bash
94
+ echo ""
95
+ echo "=== Collecting merged PRs ==="
96
+
97
+ case "$MODE" in
98
+ count)
99
+ # Last N merged PRs on the staging branch
100
+ PR_JSON=$(gh pr list $GH_FLAG \
101
+ --state merged \
102
+ --base "$STAGING_BRANCH" \
103
+ --limit "$COUNT" \
104
+ --json number,title,mergedAt,body \
105
+ --jq 'sort_by(.mergedAt) | reverse | .[]')
106
+ ;;
107
+
108
+ since)
109
+ # All merged PRs since SINCE_DATE
110
+ PR_JSON=$(gh pr list $GH_FLAG \
111
+ --state merged \
112
+ --base "$STAGING_BRANCH" \
113
+ --limit 200 \
114
+ --json number,title,mergedAt,body \
115
+ --jq "[.[] | select(.mergedAt >= \"${SINCE_DATE}T00:00:00Z\")] | sort_by(.mergedAt) | reverse | .[]")
116
+ ;;
117
+
118
+ tag-range)
119
+ # PRs merged between two git tags — derive date window from tag commit dates
120
+ cd "$REPO_PATH"
121
+
122
+ if ! git rev-parse --verify "$TAG_FROM" >/dev/null 2>&1; then
123
+ echo "ERROR: Tag '$TAG_FROM' not found in git history."
124
+ echo "Available tags: $(git tag --sort=-version:refname | head -10 | tr '\n' ' ')"
125
+ exit 1
126
+ fi
127
+ if ! git rev-parse --verify "$TAG_TO" >/dev/null 2>&1; then
128
+ echo "ERROR: Tag '$TAG_TO' not found in git history."
129
+ echo "Available tags: $(git tag --sort=-version:refname | head -10 | tr '\n' ' ')"
130
+ exit 1
131
+ fi
132
+
133
+ TAG_FROM_DATE=$(git log -1 --format="%aI" "$TAG_FROM")
134
+ TAG_TO_DATE=$(git log -1 --format="%aI" "$TAG_TO")
135
+
136
+ echo "Tag range: $TAG_FROM ($TAG_FROM_DATE) → $TAG_TO ($TAG_TO_DATE)"
137
+
138
+ PR_JSON=$(gh pr list $GH_FLAG \
139
+ --state merged \
140
+ --base "$STAGING_BRANCH" \
141
+ --limit 200 \
142
+ --json number,title,mergedAt,body \
143
+ --jq "[.[] | select(.mergedAt > \"$TAG_FROM_DATE\" and .mergedAt <= \"$TAG_TO_DATE\")] | sort_by(.mergedAt) | reverse | .[]")
144
+ ;;
145
+ esac
146
+
147
+ # Empty-state guard: if no PRs matched the range, emit informative message and exit cleanly
148
+ if [ -z "$PR_JSON" ]; then
149
+ echo "No merged PRs found for this range."
150
+ exit 0
151
+ fi
152
+
153
+ # Write PR list to a temp file so we can iterate without subshell word-splitting
154
+ TMPFILE=$(mktemp)
155
+ echo "$PR_JSON" > "$TMPFILE"
156
+
157
+ PR_COUNT=$(gh pr list $GH_FLAG --state merged --base "$STAGING_BRANCH" --limit "$COUNT" --json number --jq 'length' 2>/dev/null || echo "?")
158
+ echo "Collected PRs for changelog."
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Phase 3: Enrich with FORGE:TRAJECTORY Annotations
164
+
165
+ For each PR, extract the linked issue number and read its FORGE:TRAJECTORY comment.
166
+
167
+ ```bash
168
+ echo ""
169
+ echo "=== Reading FORGE:TRAJECTORY annotations ==="
170
+
171
+ # Accumulators — one line per group, tab-separated: PREFIX|PR_NUM|PR_TITLE|ISSUE_NUM|TASK_TYPE
172
+ declare -a GROUP_FEAT
173
+ declare -a GROUP_FIX
174
+ declare -a GROUP_REFACTOR
175
+ declare -a GROUP_DOCS
176
+ declare -a GROUP_CHORE
177
+ declare -a GROUP_OTHER
178
+
179
+ # Read each PR as a JSON object from the temp file using jq line-by-line
180
+ while IFS= read -r PR_OBJ; do
181
+ [ -z "$PR_OBJ" ] && continue
182
+
183
+ PR_NUM=$(echo "$PR_OBJ" | jq -r '.number')
184
+ PR_TITLE=$(echo "$PR_OBJ" | jq -r '.title')
185
+ PR_BODY=$(echo "$PR_OBJ" | jq -r '.body // ""')
186
+ PR_MERGED_AT=$(echo "$PR_OBJ" | jq -r '.mergedAt')
187
+
188
+ # Extract conventional commit prefix from title
189
+ # Matches: fix(scope): ..., feat: ..., refactor(scope): ..., etc.
190
+ PREFIX=$(echo "$PR_TITLE" | grep -oE '^(feat|fix|refactor|docs|chore|test|ci|build|perf|style)(\([^)]+\))?' | grep -oE '^[a-z]+' | head -1)
191
+ [ -z "$PREFIX" ] && PREFIX="other"
192
+
193
+ # Extract linked issue number from PR body (Closes #N, Fixes #N, Refs #N)
194
+ ISSUE_NUM=$(echo "$PR_BODY" | grep -oiE '(closes?|fixes?|resolves?|refs?)[[:space:]]+#[0-9]+' | grep -oE '[0-9]+' | head -1)
195
+
196
+ # Read FORGE:TRAJECTORY from linked issue (when present)
197
+ TASK_TYPE=""
198
+ VERDICT=""
199
+ if [ -n "$ISSUE_NUM" ]; then
200
+ TRAJ=$(gh api "repos/$GH_REPO/issues/$ISSUE_NUM/comments" \
201
+ --jq '.[] | select(.body | contains("FORGE:TRAJECTORY")) | .body' 2>/dev/null | head -1)
202
+
203
+ if [ -n "$TRAJ" ]; then
204
+ # Extract task type from "Task type: Feature" in the Notes column
205
+ TASK_TYPE=$(echo "$TRAJ" | grep -oE 'Task type: [^|<]+' | sed 's/Task type: //' | tr -d ' ' | head -1)
206
+ # Extract verdict from "✅ CONFIRMED (HIGH)" or "✅ PARTIAL (MEDIUM)"
207
+ VERDICT=$(echo "$TRAJ" | grep -oE 'Investigation \| ✅ (CONFIRMED|PARTIAL|INVALID)' | grep -oE '(CONFIRMED|PARTIAL|INVALID)' | head -1)
208
+ fi
209
+ fi
210
+
211
+ # Build entry line: "- prefix(scope): description (#PR_NUM) — closes #ISSUE_NUM [TASK_TYPE]"
212
+ SCOPE_PART=$(echo "$PR_TITLE" | grep -oE '^[a-zA-Z0-9_]+\([^)]+\)' | head -1)
213
+ if [ -n "$SCOPE_PART" ]; then
214
+ DESCRIPTION=$(echo "$PR_TITLE" | sed "s|^${SCOPE_PART}: *||")
215
+ else
216
+ DESCRIPTION=$(echo "$PR_TITLE" | sed 's/^[a-z]*: *//')
217
+ fi
218
+
219
+ ENTRY="- ${PR_TITLE} ([#${PR_NUM}](https://github.com/${GH_REPO}/pull/${PR_NUM}))"
220
+ if [ -n "$ISSUE_NUM" ]; then
221
+ ENTRY="${ENTRY} — closes [#${ISSUE_NUM}](https://github.com/${GH_REPO}/issues/${ISSUE_NUM})"
222
+ fi
223
+ if [ -n "$TASK_TYPE" ]; then
224
+ ENTRY="${ENTRY} _(${TASK_TYPE})_"
225
+ fi
226
+
227
+ # Add to appropriate group
228
+ case "$PREFIX" in
229
+ feat) GROUP_FEAT+=("$ENTRY") ;;
230
+ fix) GROUP_FIX+=("$ENTRY") ;;
231
+ refactor) GROUP_REFACTOR+=("$ENTRY") ;;
232
+ docs) GROUP_DOCS+=("$ENTRY") ;;
233
+ chore|ci|build|test|perf|style) GROUP_CHORE+=("$ENTRY") ;;
234
+ *) GROUP_OTHER+=("$ENTRY") ;;
235
+ esac
236
+
237
+ done < <(cat "$TMPFILE" | jq -c '.')
238
+
239
+ rm -f "$TMPFILE"
240
+ echo "Annotation pass complete."
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Phase 4: Output Changelog
246
+
247
+ Render grouped markdown. Omit sections with no entries.
248
+
249
+ ```bash
250
+ echo ""
251
+ echo "========================================"
252
+ echo ""
253
+
254
+ # Determine header date range for display
255
+ case "$MODE" in
256
+ count) RANGE_LABEL="last ${COUNT} merged PRs" ;;
257
+ since) RANGE_LABEL="since ${SINCE_DATE}" ;;
258
+ tag-range) RANGE_LABEL="${TAG_FROM} → ${TAG_TO}" ;;
259
+ esac
260
+
261
+ TODAY=$(date +%Y-%m-%d)
262
+ echo "## Changelog — ${RANGE_LABEL} (generated ${TODAY})"
263
+ echo ""
264
+ echo "> Generated by [ForgeDock /changelog](https://github.com/${GH_REPO}) from merged PRs on \`${STAGING_BRANCH}\`"
265
+ echo ""
266
+
267
+ # Features
268
+ if [ ${#GROUP_FEAT[@]} -gt 0 ]; then
269
+ echo "### Features"
270
+ printf '%s\n' "${GROUP_FEAT[@]}"
271
+ echo ""
272
+ fi
273
+
274
+ # Bug Fixes
275
+ if [ ${#GROUP_FIX[@]} -gt 0 ]; then
276
+ echo "### Bug Fixes"
277
+ printf '%s\n' "${GROUP_FIX[@]}"
278
+ echo ""
279
+ fi
280
+
281
+ # Refactors
282
+ if [ ${#GROUP_REFACTOR[@]} -gt 0 ]; then
283
+ echo "### Refactors"
284
+ printf '%s\n' "${GROUP_REFACTOR[@]}"
285
+ echo ""
286
+ fi
287
+
288
+ # Documentation
289
+ if [ ${#GROUP_DOCS[@]} -gt 0 ]; then
290
+ echo "### Documentation"
291
+ printf '%s\n' "${GROUP_DOCS[@]}"
292
+ echo ""
293
+ fi
294
+
295
+ # Maintenance (chore, ci, build, test, perf, style)
296
+ if [ ${#GROUP_CHORE[@]} -gt 0 ]; then
297
+ echo "### Maintenance"
298
+ printf '%s\n' "${GROUP_CHORE[@]}"
299
+ echo ""
300
+ fi
301
+
302
+ # Uncategorized (no conventional commit prefix)
303
+ if [ ${#GROUP_OTHER[@]} -gt 0 ]; then
304
+ echo "### Other"
305
+ printf '%s\n' "${GROUP_OTHER[@]}"
306
+ echo ""
307
+ fi
308
+
309
+ # Summary line
310
+ TOTAL_PRS=$(( ${#GROUP_FEAT[@]} + ${#GROUP_FIX[@]} + ${#GROUP_REFACTOR[@]} + ${#GROUP_DOCS[@]} + ${#GROUP_CHORE[@]} + ${#GROUP_OTHER[@]} ))
311
+ echo "---"
312
+ echo ""
313
+ echo "**${TOTAL_PRS} changes** across: ${#GROUP_FEAT[@]} features, ${#GROUP_FIX[@]} fixes, ${#GROUP_REFACTOR[@]} refactors, ${#GROUP_DOCS[@]} docs, ${#GROUP_CHORE[@]} maintenance, ${#GROUP_OTHER[@]} other."
314
+ echo ""
315
+ echo "========================================"
316
+ ```
317
+
318
+ ---
319
+
320
+ ## Error Handling
321
+
322
+ - **forge.yaml missing**: Stop immediately with message to run `npx forgedock init`.
323
+ - **Tag not found**: List available tags, exit with non-zero status.
324
+ - **No PRs in range**: Emit "No merged PRs found for this range." and exit cleanly.
325
+ - **Issue has no FORGE:TRAJECTORY**: Skip gracefully — use PR title and number only. Do NOT crash.
326
+ - **gh CLI not authenticated**: `gh auth status` and `gh auth login` to fix.
327
+ - **Large date ranges**: Default limit is 200 PRs for `since:` mode — adjust `--limit` if more are expected.
328
+
329
+ ---
330
+
331
+ ## Usage Examples
332
+
333
+ ```bash
334
+ # Default: last 30 merged PRs
335
+ /changelog
336
+
337
+ # Last 50 merged PRs
338
+ /changelog last-50
339
+
340
+ # All PRs merged since a specific date
341
+ /changelog since:2024-06-01
342
+
343
+ # PRs between two release tags
344
+ /changelog v1.0.0..v1.1.0
345
+ ```
346
+
347
+ ## Output Notes
348
+
349
+ The changelog is printed to stdout. Pipe or copy-paste into:
350
+ - A `CHANGELOG.md` file
351
+ - A GitHub release body (`gh release create v1.x.x --notes "..."`)
352
+ - A PR description for a staging → main deploy
353
+
354
+ FORGE:TRAJECTORY annotations are read on a best-effort basis. Issues closed outside the ForgeDock pipeline (no trajectory comment) will appear with PR title and number only, without the task type annotation.
@@ -2,6 +2,8 @@
2
2
  description: Sweep closed issues for stale labels, missing workflow state, and Project board gaps — plus prune worktrees, branches, and milestones
3
3
  argument-hint: [labels | branches | milestones | board | orphans | all]
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # /cleanup — Full Hygiene Sweep
7
9
 
@@ -0,0 +1,330 @@
1
+ ---
2
+ description: On-demand Claude Code compatibility and feature parity report — compares installed version against the breakpoints registry and lists affected ForgeDock features
3
+ argument-hint: [--refresh]
4
+ ---
5
+
6
+ # /compat-audit — Claude Code Compatibility Report
7
+
8
+ **Input**: $ARGUMENTS
9
+
10
+ You are the ForgeDock compatibility auditor. Produce a point-in-time advisory report that shows whether the user's installed Claude Code version is current and which ForgeDock features may behave differently on their runtime. This command is **non-blocking** — it never aborts the session. All output is advisory.
11
+
12
+ **Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`.
13
+ **NEVER use plan mode (EnterPlanMode).**
14
+
15
+ ---
16
+
17
+ ## Config Preamble
18
+
19
+ Read `forge.yaml` to resolve the ForgeDock installation path:
20
+
21
+ ```bash
22
+ CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
23
+ if [ -f "$CONFIG_FILE" ]; then
24
+ FORGE_HOME_PATH=$(yq '.paths.root // ""' "$CONFIG_FILE" 2>/dev/null || echo "")
25
+ fi
26
+
27
+ # Fallback: resolve from this command file's own location if yq is unavailable
28
+ # or forge.yaml is absent. The commands/ directory is always inside FORGE_HOME.
29
+ if [ -z "$FORGE_HOME_PATH" ] || [ ! -d "$FORGE_HOME_PATH" ]; then
30
+ FORGE_HOME_PATH="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/.." 2>/dev/null && pwd)"
31
+ fi
32
+ export FORGE_HOME_PATH
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Phase 1: Parse Arguments
38
+
39
+ Parse `$ARGUMENTS` for the `--refresh` flag:
40
+
41
+ ```bash
42
+ REFRESH=false
43
+ for arg in $ARGUMENTS; do
44
+ [ "$arg" = "--refresh" ] && REFRESH=true
45
+ done
46
+ ```
47
+
48
+ **If `--refresh` is set**: delete the version cache before detection so the npm registry is queried for the latest version regardless of TTL:
49
+
50
+ ```bash
51
+ VERSION_CACHE="$HOME/.claude/forgedock/version-cache.json"
52
+ if [ "$REFRESH" = "true" ] && [ -f "$VERSION_CACHE" ]; then
53
+ rm -f "$VERSION_CACHE"
54
+ echo "Version cache cleared — will query npm registry for fresh data."
55
+ fi
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Phase 2: Detect Installed Claude Code Version
61
+
62
+ Run `detectClaudeVersion()` from `bin/forge-utils.mjs`. This function reads from the cache at `~/.claude/forgedock/version-cache.json` (24h TTL) on a cache hit, or queries `npm info @anthropic-ai/claude-code version` and `claude --version` on a miss. It is fail-open — it never throws.
63
+
64
+ ```bash
65
+ VERSION_CACHE="$HOME/.claude/forgedock/version-cache.json"
66
+ INSTALLED="unknown"
67
+ LATEST="unknown"
68
+ STALE=false
69
+ DELTA=""
70
+ CACHED_AT=""
71
+ DAYS_SINCE_REFRESH="unknown"
72
+
73
+ if [ -f "$VERSION_CACHE" ]; then
74
+ INSTALLED=$(CACHE_PATH="$VERSION_CACHE" node -e "
75
+ try {
76
+ const c = JSON.parse(require('fs').readFileSync(process.env.CACHE_PATH, 'utf8'));
77
+ process.stdout.write(c.installed || 'unknown');
78
+ } catch { process.stdout.write('unknown'); }
79
+ " 2>/dev/null || echo "unknown")
80
+
81
+ LATEST=$(CACHE_PATH="$VERSION_CACHE" node -e "
82
+ try {
83
+ const c = JSON.parse(require('fs').readFileSync(process.env.CACHE_PATH, 'utf8'));
84
+ process.stdout.write(c.latest || 'unknown');
85
+ } catch { process.stdout.write('unknown'); }
86
+ " 2>/dev/null || echo "unknown")
87
+
88
+ STALE=$(CACHE_PATH="$VERSION_CACHE" node -e "
89
+ try {
90
+ const c = JSON.parse(require('fs').readFileSync(process.env.CACHE_PATH, 'utf8'));
91
+ process.stdout.write(c.stale === true ? 'true' : 'false');
92
+ } catch { process.stdout.write('false'); }
93
+ " 2>/dev/null || echo "false")
94
+
95
+ DELTA=$(CACHE_PATH="$VERSION_CACHE" node -e "
96
+ try {
97
+ const c = JSON.parse(require('fs').readFileSync(process.env.CACHE_PATH, 'utf8'));
98
+ process.stdout.write(c.delta || '');
99
+ } catch { process.stdout.write(''); }
100
+ " 2>/dev/null || echo "")
101
+
102
+ DAYS_SINCE_REFRESH=$(CACHE_PATH="$VERSION_CACHE" node -e "
103
+ try {
104
+ const c = JSON.parse(require('fs').readFileSync(process.env.CACHE_PATH, 'utf8'));
105
+ if (typeof c.cachedAt === 'number') {
106
+ const days = Math.floor((Date.now() - c.cachedAt) / 86400000);
107
+ process.stdout.write(days.toString());
108
+ } else {
109
+ process.stdout.write('unknown');
110
+ }
111
+ } catch { process.stdout.write('unknown'); }
112
+ " 2>/dev/null || echo "unknown")
113
+ else
114
+ # Cache absent — attempt a fresh detection by running detectClaudeVersion()
115
+ DETECT_RESULT=$(node --input-type=module <<'NODE_EOF' 2>/dev/null
116
+ import { pathToFileURL } from 'url';
117
+ import { join, resolve, dirname } from 'path';
118
+ import { fileURLToPath } from 'url';
119
+ const forgeHome = process.env.FORGE_HOME_PATH || '.';
120
+ const utilsPath = join(forgeHome, 'bin', 'forge-utils.mjs');
121
+ try {
122
+ const { detectClaudeVersion } = await import(pathToFileURL(utilsPath).href);
123
+ const r = await detectClaudeVersion();
124
+ process.stdout.write(JSON.stringify(r));
125
+ } catch {
126
+ process.stdout.write(JSON.stringify({ version: 'unknown' }));
127
+ }
128
+ NODE_EOF
129
+ )
130
+ INSTALLED=$(echo "$DETECT_RESULT" | node -e "
131
+ try {
132
+ const r = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
133
+ process.stdout.write(r.installed || r.version || 'unknown');
134
+ } catch { process.stdout.write('unknown'); }
135
+ " 2>/dev/null || echo "unknown")
136
+ LATEST=$(echo "$DETECT_RESULT" | node -e "
137
+ try {
138
+ const r = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
139
+ process.stdout.write(r.latest || 'unknown');
140
+ } catch { process.stdout.write('unknown'); }
141
+ " 2>/dev/null || echo "unknown")
142
+ fi
143
+ ```
144
+
145
+ **Fail-open**: if all detection attempts return `unknown`, proceed to Phase 3 and report degraded output.
146
+
147
+ ---
148
+
149
+ ## Phase 3: Load Breakpoints Registry
150
+
151
+ Read `docs/claude-breakpoints.json` from the ForgeDock installation root:
152
+
153
+ ```bash
154
+ BREAKPOINTS_FILE="${FORGE_HOME_PATH}/docs/claude-breakpoints.json"
155
+ BREAKPOINTS_JSON=""
156
+
157
+ if [ -f "$BREAKPOINTS_FILE" ]; then
158
+ BREAKPOINTS_JSON=$(cat "$BREAKPOINTS_FILE" 2>/dev/null || echo "")
159
+ else
160
+ echo "NOTE: docs/claude-breakpoints.json not found at ${FORGE_HOME_PATH}/docs/."
161
+ echo "This file is included in ForgeDock v1.x+. Run: npx forgedock update"
162
+ fi
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Phase 4: Diff — Identify Active Breakpoints
168
+
169
+ Compare each breakpoint's `version` against `INSTALLED` using **numeric** semver comparison (split on `.`, compare each segment as an integer — NOT lexicographic):
170
+
171
+ ```bash
172
+ if [ -n "$BREAKPOINTS_JSON" ] && [ "$INSTALLED" != "unknown" ]; then
173
+ ACTIVE_BREAKPOINTS=$(node -e "
174
+ try {
175
+ const data = JSON.parse(process.argv[1]);
176
+ const installed = process.argv[2];
177
+
178
+ function parseVer(v) {
179
+ return v.split('.').map(s => parseInt(s, 10));
180
+ }
181
+ function isLessThan(a, b) {
182
+ const pa = parseVer(a), pb = parseVer(b);
183
+ const len = Math.max(pa.length, pb.length);
184
+ for (let i = 0; i < len; i++) {
185
+ const ai = pa[i] || 0, bi = pb[i] || 0;
186
+ if (ai < bi) return true;
187
+ if (ai > bi) return false;
188
+ }
189
+ return false;
190
+ }
191
+
192
+ const breakpoints = data.breakpoints || [];
193
+ const active = breakpoints.filter(bp => isLessThan(installed, bp.version));
194
+ process.stdout.write(JSON.stringify(active));
195
+ } catch (e) {
196
+ process.stdout.write('[]');
197
+ }
198
+ " -- "$BREAKPOINTS_JSON" "$INSTALLED" 2>/dev/null || echo "[]")
199
+ else
200
+ # Version unknown — cannot determine active breakpoints; list all as unknown status
201
+ ACTIVE_BREAKPOINTS="UNKNOWN"
202
+ fi
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Phase 5: Produce Report
208
+
209
+ Print the compat-audit report to stdout. This output is **advisory only** — it does not block the session or exit non-zero.
210
+
211
+ ---
212
+
213
+ ### Compat-Audit Report Format
214
+
215
+ Compose and print the following report, substituting resolved values:
216
+
217
+ ```
218
+ ╔══════════════════════════════════════════════════════╗
219
+ ║ ForgeDock / Claude Code Compat Audit ║
220
+ ╚══════════════════════════════════════════════════════╝
221
+
222
+ Version Status
223
+ ──────────────
224
+ Installed : {INSTALLED} (or "unknown — run /compat-audit --refresh")
225
+ Latest : {LATEST} (or "unknown — npm registry unavailable")
226
+ Status : {UP TO DATE | STALE — {DELTA}}
227
+ Cache age : {DAYS_SINCE_REFRESH} day(s) (refresh with: /compat-audit --refresh)
228
+ ```
229
+
230
+ **If STALE is true** (installed differs from latest):
231
+
232
+ ```
233
+ ⚠ Your Claude Code is outdated.
234
+ Upgrade: npm update -g @anthropic-ai/claude-code
235
+ ```
236
+
237
+ **If STALE is false and both versions are known**:
238
+
239
+ ```
240
+ ✓ Claude Code is up to date.
241
+ ```
242
+
243
+ **If versions are unknown**:
244
+
245
+ ```
246
+ ? Version unknown. Is Claude Code installed? Try: claude --version
247
+ For a fresh check: /compat-audit --refresh
248
+ ```
249
+
250
+ ---
251
+
252
+ ### Active Breakpoints Section
253
+
254
+ **Case A — installed version is known and breakpoints loaded**:
255
+
256
+ If `ACTIVE_BREAKPOINTS` is an empty array (`[]`):
257
+
258
+ ```
259
+ Breakpoints
260
+ ───────────
261
+ ✓ No active breakpoints — your installed version meets all ForgeDock requirements.
262
+ ```
263
+
264
+ If `ACTIVE_BREAKPOINTS` contains entries, print a table:
265
+
266
+ ```
267
+ Breakpoints (active — features introduced AFTER your installed version)
268
+ ───────────────────────────────────────────────────────────────────────
269
+ Version │ Type │ Severity │ ForgeDock Impact
270
+ ─────────┼──────────────────┼──────────┼──────────────────────────────
271
+ {version}│ {type} │ {severity}│ {forgedock_impact}
272
+ ...
273
+
274
+ Affected ForgeDock features:
275
+ • {forgedock_impact for each active breakpoint}
276
+
277
+ Recommendation: upgrade to Claude Code {LATEST} to restore full ForgeDock functionality.
278
+ Command: npm update -g @anthropic-ai/claude-code
279
+ ```
280
+
281
+ **Case B — version unknown but breakpoints loaded**:
282
+
283
+ ```
284
+ Breakpoints
285
+ ───────────
286
+ ? Version unknown — cannot determine which breakpoints are active.
287
+ All known breakpoints listed below for reference:
288
+
289
+ Version │ Type │ Severity │ Description
290
+ ─────────┼──────────────────┼──────────┼──────────────────────────────
291
+ {version}│ {type} │ {severity}│ {description}
292
+ ...
293
+
294
+ To get version-specific breakpoint status: /compat-audit --refresh
295
+ ```
296
+
297
+ **Case C — breakpoints file not found**:
298
+
299
+ ```
300
+ Breakpoints
301
+ ───────────
302
+ ? Breakpoints registry not found (docs/claude-breakpoints.json).
303
+ Run: npx forgedock update # to get latest ForgeDock commands (includes breakpoints)
304
+ ```
305
+
306
+ ---
307
+
308
+ ### Footer
309
+
310
+ Always end with:
311
+
312
+ ```
313
+ ──────────────────────────────────────────────────────
314
+ This report is advisory. ForgeDock continues to work regardless of version gaps.
315
+ For the full ForgeDock version intelligence picture, also run: forgedock doctor
316
+ ──────────────────────────────────────────────────────
317
+ ```
318
+
319
+ ---
320
+
321
+ ## Error Handling
322
+
323
+ - **Cache unreadable** → treat as cache absent, attempt fresh detection
324
+ - **`claude --version` unavailable** → `INSTALLED = "unknown"`, continue
325
+ - **npm registry unavailable** → `LATEST = "unknown"`, continue
326
+ - **Breakpoints file missing** → print Case C above, continue
327
+ - **node unavailable** → print raw cache content if possible, otherwise print "version detection requires Node.js ≥ 18"
328
+ - **Any uncaught exception** → print "compat-audit encountered an error: {message}" and exit 0
329
+
330
+ The command MUST exit 0 in all cases. It NEVER blocks the Claude Code session.