forgedock 1.0.21 → 1.0.22
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.
- package/README.md +134 -80
- package/bin/demo.mjs +386 -0
- package/bin/forgedock.mjs +357 -30
- package/bin/init-detect.mjs +95 -0
- package/bin/runner.mjs +780 -0
- package/commands/cleanup.md +85 -39
- package/commands/deploy-info.md +6 -2
- package/commands/forge-stats.md +1 -1
- package/commands/optimize.md +1 -1
- package/commands/orchestrate.md +320 -26
- package/commands/quality-gate.md +91 -49
- package/commands/review-pr-agents.md +62 -38
- package/commands/review-pr.md +33 -2
- package/commands/scope.md +11 -3
- package/commands/security-audit.md +24 -16
- package/commands/sync-ecosystem.md +2 -2
- package/commands/work-on/build/architect.md +47 -3
- package/commands/work-on/build/context.md +36 -5
- package/commands/work-on/build/validate.md +16 -7
- package/commands/work-on/close.md +129 -1
- package/commands/work-on/investigate.md +24 -1
- package/commands/work-on.md +226 -8
- package/docs/site/annotations-explained.md +55 -0
- package/docs/site/command-learning-path.md +104 -0
- package/docs/site/command-reference.md +3 -0
- package/docs/site/getting-started.md +36 -14
- package/docs/site/troubleshooting.md +208 -0
- package/docs/spec/forge-protocol-v1.md +768 -0
- package/examples/forgedock-demo/README.md +113 -0
- package/examples/forgedock-demo/bootstrap.sh +125 -0
- package/examples/forgedock-demo/issues/01-bug-delete-missing-auth.md +37 -0
- package/examples/forgedock-demo/issues/02-feature-filter-notes.md +37 -0
- package/examples/forgedock-demo/issues/03-refactor-router-table.md +33 -0
- package/examples/forgedock-demo/issues/04-perf-index-lookup.md +35 -0
- package/examples/forgedock-demo/issues/05-docs-api-reference.md +39 -0
- package/examples/forgedock-demo/labels.json +21 -0
- package/examples/forgedock-demo/package.json +16 -0
- package/examples/forgedock-demo/scripts/smoke.js +61 -0
- package/examples/forgedock-demo/src/auth.js +28 -0
- package/examples/forgedock-demo/src/db.js +66 -0
- package/examples/forgedock-demo/src/routes/notes.js +64 -0
- package/examples/forgedock-demo/src/server.js +79 -0
- package/package.json +5 -1
- package/templates/forge.yaml.minimal +44 -0
package/commands/cleanup.md
CHANGED
|
@@ -47,7 +47,7 @@ All `{GH_REPO}`, `{GH_FLAG}`, `{REPO_PATH}`, `{STAGING_BRANCH}`, `{PROJECT_BOARD
|
|
|
47
47
|
| `labels` or empty | Fix stale/missing workflow labels on closed issues |
|
|
48
48
|
| `orphans` | Close open issues whose PRs are already merged |
|
|
49
49
|
| `branches` | Prune worktrees and remote branches for merged PRs |
|
|
50
|
-
| `milestones` |
|
|
50
|
+
| `milestones` | Report milestones with 0 open issues (advisory — never closes) |
|
|
51
51
|
| `board` | Sync closed issues to Project board with correct terminal state |
|
|
52
52
|
| `all` | All of the above, in order |
|
|
53
53
|
|
|
@@ -172,58 +172,109 @@ echo "Removed: $WORKTREE_PATH ($BRANCH_NAME)"
|
|
|
172
172
|
|
|
173
173
|
### 3C: Prune merged remote branches
|
|
174
174
|
|
|
175
|
-
Delete remote `fix/` and `feat/` branches
|
|
175
|
+
Delete remote `fix/` and `feat/` branches whose PR has merged — using **GitHub PR state as the source of truth**, not local git ancestry.
|
|
176
|
+
|
|
177
|
+
**Why not `git branch -r --merged {STAGING_BRANCH}`**: an ancestry check only catches branches whose tip commit is reachable from `{STAGING_BRANCH}`. This misses two common cases:
|
|
178
|
+
- **Feature-lane branches merged into a milestone branch.** Per `work-on.md` Phase 3E, milestone issues branch from and PR into `origin/milestone/{slug}`, not `{STAGING_BRANCH}`. Once such a branch's own PR merges into the milestone branch, it's fully absorbed and safe to delete — but it won't show up as "merged into staging" until the milestone itself ships (which may be days later, or never, if abandoned). This is the dominant cause of `feat/*` branches accumulating for a milestone/feature cluster.
|
|
179
|
+
- **Squash merges.** If a branch was merged via squash (manual UI merge, or org/branch-protection settings that force squash-only), the resulting commit is a brand-new SHA that is never an ancestor of the original branch — so ancestry-based detection never matches it, even though the PR is clearly `MERGED` on GitHub.
|
|
180
|
+
|
|
181
|
+
Both gaps disappear when merged-PR head-refs (regardless of base branch or merge strategy) are used directly as the deletion set:
|
|
182
|
+
|
|
183
|
+
**Why also check `headRefOid`**: branch names are freely reusable in git. A name-only match cannot distinguish "this branch's current tip is the commit that merged" from "a branch with this name merged at some point in the past and has since been reused for new, unmerged work" (e.g. a second issue happens to slugify to the same `fix/*`/`feat/*` name). Comparing the branch's live remote tip SHA against the merged PR's `headRefOid` closes that gap — deletion only proceeds when the (name, SHA) pair matches a merged PR exactly, regardless of base branch or merge strategy.
|
|
184
|
+
|
|
185
|
+
**Why `--force-with-lease` on the delete**: the `CURRENT_SHA` snapshot above is read from the local `origin/$branch` ref as of the `git fetch --prune` at the top of this phase. For large batches this loop can run for a while (one network call per branch), so a new commit can land on the remote branch between the fetch and that branch's turn in the loop — a TOCTOU window. A plain `git push origin --delete "$branch"` is unconditional: it deletes whatever the remote ref currently points to, even if that's no longer `$CURRENT_SHA`. Using `--force-with-lease=refs/heads/$branch:$CURRENT_SHA` makes the deletion a server-verified compare-and-swap — the remote rejects the update if `refs/heads/$branch` has moved since the snapshot, instead of silently deleting new work.
|
|
176
186
|
|
|
177
187
|
```bash
|
|
178
188
|
cd {REPO_PATH}
|
|
179
189
|
git fetch --prune origin
|
|
180
190
|
|
|
181
|
-
#
|
|
182
|
-
|
|
183
|
-
|
|
191
|
+
# All remote fix/* and feat/* branches currently on origin
|
|
192
|
+
REMOTE_BRANCHES=$(git branch -r | grep -E "origin/(fix|feat)/" | sed 's|origin/||' | tr -d ' ')
|
|
193
|
+
|
|
194
|
+
# Head-ref name + head-ref SHA of every merged PR, regardless of base branch (staging,
|
|
195
|
+
# milestone/*, main) or merge strategy (merge-commit vs. squash) — this is GitHub's own
|
|
196
|
+
# merge bookkeeping, not local ref topology, so it's immune to both gaps above.
|
|
197
|
+
# headRefOid is captured alongside headRefName so deletion can be gated on the branch's
|
|
198
|
+
# CURRENT tip matching the commit that actually merged, not just a name match.
|
|
199
|
+
# --limit is set high to avoid silent truncation; repos with more historical merged PRs
|
|
200
|
+
# than this should re-run `/cleanup branches` incrementally to catch the remainder.
|
|
201
|
+
MERGED_HEADS=$(gh pr list {GH_FLAG} --state merged --limit 1000 --json headRefName,headRefOid --jq '.[] | "\(.headRefName)\t\(.headRefOid)"')
|
|
202
|
+
|
|
203
|
+
DELETE_COUNT=0
|
|
204
|
+
SKIP_COUNT=0
|
|
205
|
+
RACE_SKIP_COUNT=0
|
|
206
|
+
for branch in $REMOTE_BRANCHES; do
|
|
207
|
+
CURRENT_SHA=$(git rev-parse "origin/$branch" 2>/dev/null)
|
|
208
|
+
if printf '%s\n' "$MERGED_HEADS" | grep -qxF "$(printf '%s\t%s' "$branch" "$CURRENT_SHA")"; then
|
|
209
|
+
# Name AND tip SHA match a merged PR's head-ref — safe to delete.
|
|
210
|
+
# Use --force-with-lease as a server-side compare-and-swap: the remote re-verifies
|
|
211
|
+
# refs/heads/$branch is still at $CURRENT_SHA at push time, closing the TOCTOU gap
|
|
212
|
+
# between this snapshot and the actual delete (see note above).
|
|
213
|
+
if git push origin --force-with-lease="refs/heads/$branch:$CURRENT_SHA" ":refs/heads/$branch" 2>&1; then
|
|
214
|
+
DELETE_COUNT=$((DELETE_COUNT + 1))
|
|
215
|
+
# Log the pre-deletion SHA so it's visible in run output/logs for recovery —
|
|
216
|
+
# see "Recovery: restoring an accidentally pruned branch" below.
|
|
217
|
+
echo "Deleted origin/$branch (was $CURRENT_SHA) — restorable via: git push origin $CURRENT_SHA:refs/heads/$branch"
|
|
218
|
+
else
|
|
219
|
+
# Lease rejected — the branch's remote tip moved since the snapshot (a new push
|
|
220
|
+
# landed mid-loop). Do NOT retry/force past this: skip and let the next cleanup
|
|
221
|
+
# run re-evaluate it against fresh state.
|
|
222
|
+
RACE_SKIP_COUNT=$((RACE_SKIP_COUNT + 1))
|
|
223
|
+
echo "RACE: origin/$branch tip changed since snapshot ($CURRENT_SHA) — lease rejected, not deleting. Will re-evaluate on next /cleanup run."
|
|
224
|
+
fi
|
|
225
|
+
elif printf '%s\n' "$MERGED_HEADS" | cut -f1 | grep -qxF "$branch"; then
|
|
226
|
+
# Name matches a merged PR's head-ref, but the branch's current tip does not match
|
|
227
|
+
# any merged commit recorded under that name — likely a reused branch name holding
|
|
228
|
+
# new, unmerged work. Skip deletion rather than risk destroying live commits.
|
|
229
|
+
SKIP_COUNT=$((SKIP_COUNT + 1))
|
|
230
|
+
echo "SKIP: origin/$branch name matches a merged PR head-ref but current tip ($CURRENT_SHA) does not match the merged commit — branch name likely reused for new work. Not deleting."
|
|
231
|
+
fi
|
|
232
|
+
done
|
|
233
|
+
echo "Deleted $DELETE_COUNT merged remote branches, skipped $SKIP_COUNT name-matched/SHA-mismatched branches, skipped $RACE_SKIP_COUNT raced branches (tip changed between snapshot and delete) (source of truth: gh pr list --state merged, verified against headRefOid, deletion gated by --force-with-lease)"
|
|
234
|
+
```
|
|
184
235
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
236
|
+
**Note**: This can take a while for large batches (1 network call per deleted branch, plus one batched `gh pr list` call). Run in background if > 20 branches.
|
|
237
|
+
|
|
238
|
+
**One-time backfill for existing stale branches**: repos that adopted this fix after already accumulating stale `feat/*`/`fix/*` branches (e.g. from milestones that shipped long ago) should run `/cleanup branches` once manually — the PR-state query above will catch the full backlog in a single pass since it isn't scoped to "this batch" or "this session," it queries all merged PRs on the repo.
|
|
239
|
+
|
|
240
|
+
**Recovery: restoring an accidentally pruned branch**
|
|
241
|
+
|
|
242
|
+
Phase 3C only ever deletes a branch whose (name, SHA) pair matched a merged PR's `headRefOid`, so the exact deleted commit is always recoverable: its SHA is echoed in the run log above (`Deleted origin/$branch (was $CURRENT_SHA) ...`), and the commit object survives in the remote's object store until garbage collection (for merge-commit merges it also remains reachable from the PR's merge commit; for squash merges the original tip becomes a dangling commit — not reachable by ancestry, but still restorable by SHA for as long as it hasn't been GC'd). If a branch is pruned in error (or needs to be recreated for any reason), restore it with:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Using the SHA logged at deletion time (or from `gh pr view {PR_NUMBER} --json headRefOid`
|
|
246
|
+
# if the run log is no longer available):
|
|
247
|
+
git push origin <sha>:refs/heads/<branch>
|
|
189
248
|
```
|
|
190
249
|
|
|
191
|
-
**
|
|
250
|
+
Alternatively, GitHub itself offers a one-click **"Restore branch"** button on the merged PR's page (shown on the "<branch> was deleted" banner) for a limited window after deletion — typically available for as long as the underlying ref data hasn't been garbage-collected, often a few weeks. This is the fastest option when working from the GitHub UI rather than a local clone.
|
|
251
|
+
|
|
252
|
+
Both options only apply to branches deleted by this phase (or by GitHub's own merge/delete UI) — a SHA is not captured for branches removed by other means (e.g. manual `git push origin --delete` run outside of `/cleanup`), so recovery there depends on `git reflog` on a clone that still has the ref, or GitHub's audit log.
|
|
192
253
|
|
|
193
254
|
---
|
|
194
255
|
|
|
195
|
-
## Phase 4: Milestone Hygiene
|
|
256
|
+
## Phase 4: Milestone Hygiene (Advisory Only)
|
|
257
|
+
|
|
258
|
+
**NEVER close milestones in this phase.** Milestones are only closed by `/milestone ship` (human-gated) or `/milestone close` (explicit abandonment). Incorrect closure destroys milestone state that cannot be trivially recovered. <!-- fix: forge#1160 -->
|
|
196
259
|
|
|
197
|
-
### 4A: Find
|
|
260
|
+
### 4A: Find milestones with 0 open issues
|
|
198
261
|
|
|
199
262
|
```bash
|
|
200
263
|
# List open milestones with 0 remaining issues
|
|
201
264
|
gh api repos/:owner/:repo/milestones --jq '.[] | select(.state == "open" and .open_issues == 0) | "\(.number) | \(.title) | open:\(.open_issues) closed:\(.closed_issues)"'
|
|
202
265
|
```
|
|
203
266
|
|
|
204
|
-
### 4B:
|
|
267
|
+
### 4B: Report findings (DO NOT close)
|
|
205
268
|
|
|
206
|
-
For each milestone with 0 open issues,
|
|
269
|
+
For each milestone with 0 open issues, report it as a candidate for shipping or closing — but take no action:
|
|
207
270
|
|
|
208
271
|
```bash
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
# Check if milestone branch was merged to staging or main
|
|
213
|
-
SHIPPED_STAGING=$(gh pr list --base staging --head "milestone/$SLUG" --state merged --json number --jq '.[0].number' 2>/dev/null)
|
|
214
|
-
SHIPPED_MAIN=$(gh pr list --base main --head "milestone/$SLUG" --state merged --json number --jq '.[0].number' 2>/dev/null)
|
|
215
|
-
|
|
216
|
-
if [ -n "$SHIPPED_STAGING" ] || [ -n "$SHIPPED_MAIN" ]; then
|
|
217
|
-
# Safe to close — code has been shipped to staging or main
|
|
218
|
-
gh api repos/:owner/:repo/milestones/$MILESTONE_NUMBER -X PATCH -f state=closed
|
|
219
|
-
echo "CLOSED milestone: $MILESTONE_TITLE (shipped via PR #${SHIPPED_STAGING:-$SHIPPED_MAIN})"
|
|
220
|
-
else
|
|
221
|
-
# DO NOT close — code is only on the milestone branch, not yet shipped
|
|
222
|
-
echo "SKIPPED: $MILESTONE_TITLE — issues done but milestone branch not merged to staging or main"
|
|
223
|
-
fi
|
|
272
|
+
echo "ADVISORY: $MILESTONE_TITLE — 0 open issues, $CLOSED_ISSUES closed"
|
|
273
|
+
echo " → To ship: /milestone ship $SLUG"
|
|
274
|
+
echo " → To abandon: /milestone close $SLUG"
|
|
224
275
|
```
|
|
225
276
|
|
|
226
|
-
|
|
277
|
+
Do NOT call `gh api ... -X PATCH -f state=closed` on any milestone. This phase is informational only.
|
|
227
278
|
|
|
228
279
|
---
|
|
229
280
|
|
|
@@ -281,15 +332,10 @@ fi
|
|
|
281
332
|
| Local branches deleted | {N} |
|
|
282
333
|
| Remote branches deleted | {N} |
|
|
283
334
|
|
|
284
|
-
### Milestones
|
|
285
|
-
| Milestone | Issues (closed) |
|
|
286
|
-
|
|
287
|
-
| {title} | {N} |
|
|
288
|
-
|
|
289
|
-
### Milestones Skipped (unshipped)
|
|
290
|
-
| Milestone | Issues (closed) | Reason |
|
|
291
|
-
|-----------|-----------------|--------|
|
|
292
|
-
| {title} | {N} | No merged PR from milestone/{slug} → staging or main |
|
|
335
|
+
### Milestones Ready to Ship (advisory — no action taken)
|
|
336
|
+
| Milestone | Issues (closed) | Recommended Action |
|
|
337
|
+
|-----------|-----------------|-------------------|
|
|
338
|
+
| {title} | {N} | `/milestone ship {slug}` or `/milestone close {slug}` |
|
|
293
339
|
|
|
294
340
|
### Board Synced
|
|
295
341
|
| Action | Count |
|
package/commands/deploy-info.md
CHANGED
|
@@ -189,10 +189,14 @@ if [ -n "$MIGRATIONS" ]; then
|
|
|
189
189
|
echo "⚠️ DATABASE MIGRATIONS INCLUDED:"
|
|
190
190
|
echo "$MIGRATIONS"
|
|
191
191
|
# Show migration content for review
|
|
192
|
-
|
|
192
|
+
# $MIGRATIONS is one path per line (`git diff --name-only`) — herestring,
|
|
193
|
+
# not a piped `| while read`, for consistency with the newline-safe pattern
|
|
194
|
+
# used elsewhere in this sweep.
|
|
195
|
+
while IFS= read -r m; do
|
|
196
|
+
[ -z "$m" ] && continue
|
|
193
197
|
echo "--- $m ---"
|
|
194
198
|
git show origin/$SOURCE:$m 2>/dev/null | head -30
|
|
195
|
-
done
|
|
199
|
+
done <<< "$MIGRATIONS"
|
|
196
200
|
fi
|
|
197
201
|
```
|
|
198
202
|
|
package/commands/forge-stats.md
CHANGED
package/commands/optimize.md
CHANGED
|
@@ -382,7 +382,7 @@ Then:
|
|
|
382
382
|
--- Next steps ---
|
|
383
383
|
1. Review generated scripts in .forgedock/scripts/
|
|
384
384
|
2. Run each script manually to verify output
|
|
385
|
-
3. If correct: git add .forgedock/scripts/ && git commit -m "feat(scripts): adaptive scripts from /optimize"
|
|
385
|
+
3. If correct: git add .forgedock/scripts/ && git commit -s -m "feat(scripts): adaptive scripts from /optimize"
|
|
386
386
|
4. If not: edit scripts directly or update forge.yaml → learned: and re-run /optimize
|
|
387
387
|
```
|
|
388
388
|
|