forgedock 1.8.0 → 1.8.2
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/bin/engine/admission.mjs +57 -1
- package/bin/engine/admission.test.mjs +60 -0
- package/commands/analytics.md +3 -1
- package/commands/audit-agents.md +3 -1
- package/commands/audit.md +10 -8
- package/commands/autopilot.md +16 -14
- package/commands/ci-audit.md +4 -2
- package/commands/cleanup.md +1 -1
- package/commands/deploy-pr.md +31 -11
- package/commands/geo-audit.md +3 -1
- package/commands/incident-response.md +3 -1
- package/commands/issue.md +4 -2
- package/commands/milestone.md +8 -6
- package/commands/orchestrate/config.md +2 -2
- package/commands/orchestrate/phase-1-resolve.md +56 -12
- package/commands/orchestrate/phase-3-dependency.md +15 -5
- package/commands/orchestrate/phase-4-execution.md +593 -78
- package/commands/orchestrate/phase-5-cleanup.md +4 -2
- package/commands/orchestrate/phase-6-report.md +7 -3
- package/commands/orchestrate.md +13 -9
- package/commands/pipeline-health/phase-4-proposals.md +6 -4
- package/commands/pipeline-health/phase-5-report.md +4 -2
- package/commands/pipeline-health.md +9 -7
- package/commands/pipeline-resume.md +4 -2
- package/commands/qa-sweep.md +3 -1
- package/commands/quality-gate.md +4 -3
- package/commands/recover-orphans.md +172 -13
- package/commands/review-pr-agents.md +6 -4
- package/commands/review-pr-staging.md +13 -5
- package/commands/review-pr.md +187 -19
- package/commands/rollback.md +3 -1
- package/commands/security-audit.md +3 -1
- package/commands/signal-planner.md +6 -4
- package/commands/sync-ecosystem.md +4 -2
- package/commands/test-gate.md +4 -2
- package/commands/upgrade-deps.md +4 -2
- package/commands/validate.md +3 -1
- package/commands/work-on/build/architect.md +81 -19
- package/commands/work-on/build/context.md +61 -18
- package/commands/work-on/build/implement.md +62 -18
- package/commands/work-on/build/validate.md +240 -60
- package/commands/work-on/build.md +344 -146
- package/commands/work-on/close.md +617 -246
- package/commands/work-on/decompose.md +239 -41
- package/commands/work-on/investigate.md +377 -61
- package/commands/work-on/remediate.md +42 -9
- package/commands/work-on/review.md +240 -29
- package/commands/work-on-monolithic.md +48 -7
- package/commands/work-on.md +180 -2014
- package/docs/CODEX.md +2 -0
- package/docs/CONFIG.md +11 -0
- package/package.json +1 -1
- package/scripts/build-spec-graph.mjs +11 -4
- package/scripts/check-spec-markers.sh +11 -1
- package/scripts/checkbox-sections.test.sh +8 -23
- package/scripts/extract-affected-files.sh +5 -2
- package/scripts/extract-affected-files.test.sh +27 -0
- package/scripts/forge-root.test.sh +176 -0
- package/scripts/is-docs-only.sh +31 -0
- package/scripts/is-docs-only.test.sh +64 -0
- package/scripts/lint-dispatch-prompt.sh +217 -0
- package/scripts/lint-dispatch-prompt.test.sh +307 -0
- package/scripts/validate-spec-graph.sh +3 -2
- package/scripts/verify-phase-trail.sh +282 -0
- package/scripts/verify-phase-trail.test.sh +298 -0
- package/scripts/wait-ci-green.sh +98 -0
- package/scripts/wait-ci-green.test.sh +99 -0
|
@@ -6,6 +6,8 @@ install: core
|
|
|
6
6
|
|
|
7
7
|
# /orchestrate — Phase 5: Post-Batch Cleanup
|
|
8
8
|
|
|
9
|
+
> **Skill names**: `{FORGE_SKILL_PREFIX}` is `forgedock:` (plugin install) or empty (`install.sh`), resolved once per run by `commands/work-on.md` § Skill Name Resolution. If the skill is not found under either name, STOP and report "skill not found" — never run the phase inline.
|
|
10
|
+
|
|
9
11
|
## Phase 5: Post-Batch Cleanup
|
|
10
12
|
|
|
11
13
|
**This phase is MANDATORY after every orchestration batch.** It prevents the rot that accumulates when multiple agents merge PRs in parallel — stale labels, orphaned worktrees, unclosed issues.
|
|
@@ -15,7 +17,7 @@ install: core
|
|
|
15
17
|
Invoke the `/cleanup` skill with `all` to sweep everything:
|
|
16
18
|
|
|
17
19
|
```
|
|
18
|
-
Skill(skill="cleanup", args="all")
|
|
20
|
+
Skill(skill="{FORGE_SKILL_PREFIX}cleanup", args="all")
|
|
19
21
|
```
|
|
20
22
|
|
|
21
23
|
This will:
|
|
@@ -31,7 +33,7 @@ This will:
|
|
|
31
33
|
Invoke `/audit-agents` on this session to measure pipeline efficiency:
|
|
32
34
|
|
|
33
35
|
```
|
|
34
|
-
Skill(skill="audit-agents", args="latest")
|
|
36
|
+
Skill(skill="{FORGE_SKILL_PREFIX}audit-agents", args="latest")
|
|
35
37
|
```
|
|
36
38
|
|
|
37
39
|
Include the audit summary in the final report (Phase 6). Key metrics to surface:
|
|
@@ -39,7 +39,10 @@ for NUM in {all_completed_issue_numbers}; do
|
|
|
39
39
|
if [ -n "$TRAJ_BODY" ]; then
|
|
40
40
|
# Decode via protocol CLI — handles both Base64url form (forge#1727) and gracefully
|
|
41
41
|
# exits 1 (skips) for pre-migration inline-JSON entries.
|
|
42
|
-
|
|
42
|
+
# The codec ships with ForgeDock, not with the consumer repo: resolve it from the install root.
|
|
43
|
+
_l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"; CODEC_CLI=""
|
|
44
|
+
for _c in '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l"; do case "$_c" in /*) [ -z "$CODEC_CLI" ] && [ -f "$_c/packages/protocol/src/cli.js" ] && CODEC_CLI="$_c/packages/protocol/src/cli.js" ;; esac; done
|
|
45
|
+
CARD=$( [ -n "$CODEC_CLI" ] && printf '%s\n' "$TRAJ_BODY" | node "$CODEC_CLI" parse --type CARD 2>/dev/null | jq -c '.payload // .' 2>/dev/null || true)
|
|
43
46
|
[ -n "$CARD" ] && CARDS="${CARDS}${CARD}"$'\n'
|
|
44
47
|
fi
|
|
45
48
|
done
|
|
@@ -163,7 +166,7 @@ ORCHESTRATION_ENDED_IDLE="${BATCH_FULLY_GATED:-false}"
|
|
|
163
166
|
IDLE_POLICY_DEFERRED_COUNT=0
|
|
164
167
|
for FINDING_NUM in "${DEFERRED_FINDINGS[@]:-}"; do
|
|
165
168
|
[ -z "$FINDING_NUM" ] && continue
|
|
166
|
-
echo "${DEFERRED_REASONS[$FINDING_NUM]:-}" | grep -qi "batch fully human-gated" && \
|
|
169
|
+
echo "${DEFERRED_REASONS[$FINDING_NUM]:-}" | grep -qi "batch fully human-gated" && ! echo "${DEFERRED_REASONS[$FINDING_NUM]:-}" | grep -qi "amplification breaker" && \
|
|
167
170
|
IDLE_POLICY_DEFERRED_COUNT=$((IDLE_POLICY_DEFERRED_COUNT + 1))
|
|
168
171
|
done
|
|
169
172
|
```
|
|
@@ -345,6 +348,7 @@ current session's live wake pick them up automatically if it is still running).
|
|
|
345
348
|
| **Cascade amplification** | **{FINDINGS_SPAWNED:-0}/{MERGED_UNITS:-0} = {AMPLIFICATION_RATIO:-0} findings per merged unit** |
|
|
346
349
|
| Amplification composition | {#REFINEMENT_FINDINGS[@]:-0} same-lineage refinements; {#NEW_SURFACE_FINDINGS[@]:-0} new-surface findings |
|
|
347
350
|
| Amplification bound | {CASCADE_MAX_AMPLIFICATION:-off}; {#AMPLIFICATION_DEFERRED[@]:-0} same-lineage refinements deferred to completion sweep |
|
|
351
|
+
| Amplification breaker | {AMPLIFICATION_BREAKER:-on}; {tripped|clear}; {#AMPLIFICATION_BREAKER_DEFERRED[@]:-0} P3 findings paused ({#BREAKER_HELD[@]:-0} still held at sweep) for bounded P3 batches (never dispatched individually) |
|
|
348
352
|
| Competing recommendations reconciled (Phase 2.5) | {RECONCILED_COUNT} (investigation plans arbitrated in place + serialized) |
|
|
349
353
|
| Findings validated | {N} |
|
|
350
354
|
| False positives | {N} ({%}) |
|
|
@@ -363,7 +367,7 @@ current session's live wake pick them up automatically if it is still running).
|
|
|
363
367
|
|
|
364
368
|
`P3 findings clubbed` and `Findings deferred — token budget` are a DIFFERENT mechanism from the `$`-denominated `Budget limit`/`Projected spend`/`Actual spend`/`Issues deferred (budget ceiling)` rows above (forge#1743, opt-in `--budget N` flag, gates the *original* issue dispatch order). The token-budget row reads the always-on, token-denominated ceiling that scopes ONLY to Step 4C's review-finding cascade dispatch (forge#1858); `SURFACE_BATCH_COUNT` and `SURFACE_BATCHED_FINDINGS` are populated by the same Step 4C surface-area-batching block (forge#1818) that clubs P3 findings sharing a file/leaf-directory into one dispatched pipeline. Both degrade gracefully to `0`/`uncapped` when the batch had no review findings or ran with defaults. <!-- Added: forge#1858 -->
|
|
365
369
|
|
|
366
|
-
**Cascade amplification detail**: render one row per `FINDINGS_BY_SOURCE_PR` entry: `| PR #{source} | {finding count} | {refinement count} | {new-surface count} |`. A ratio at or above 1.0 means the batch is not reducing its open-work count through merges alone; it does **not** mean the findings are low-value. Include any convergence warning and state that the default `max_amplification: off` never defers findings. When an opt-in bound deferred a refinement, list it under Completion Sweep as re-evaluable rather than silently omitting it.
|
|
370
|
+
**Cascade amplification detail**: render one row per `FINDINGS_BY_SOURCE_PR` entry: `| PR #{source} | {finding count} | {refinement count} | {new-surface count} |`. A ratio at or above 1.0 means the batch is not reducing its open-work count through merges alone; it does **not** mean the findings are low-value. Include any convergence warning and state that the default `max_amplification: off` never defers findings. State the amplification breaker (on by default, forge#3060) separately: when it tripped, report the ratio, the number of paused P3 findings, and the operator decision (batch or defer); when opted out via `amplification_breaker: off`, say so. When an opt-in bound deferred a refinement, list it under Completion Sweep as re-evaluable rather than silently omitting it.
|
|
367
371
|
|
|
368
372
|
For `policy: all`, include the full-repo intake counts and list each automated duplicate with its canonical issue. A sweep measures all new open issues, including CI alerts without `review-finding`; it must not claim that the cascade ratio alone measures total queue growth.
|
|
369
373
|
|
package/commands/orchestrate.md
CHANGED
|
@@ -51,7 +51,7 @@ dispatch a compact plan.
|
|
|
51
51
|
|
|
52
52
|
## Execution Order
|
|
53
53
|
|
|
54
|
-
Read and execute phases in sequence. Each phase file is self-contained.
|
|
54
|
+
Read and execute phases in sequence. Each phase file is self-contained. Resolve every path from the spec root defined in the Quick Reference below.
|
|
55
55
|
|
|
56
56
|
| Step | File | Description |
|
|
57
57
|
|------|------|-------------|
|
|
@@ -67,15 +67,19 @@ Read and execute phases in sequence. Each phase file is self-contained.
|
|
|
67
67
|
|
|
68
68
|
## Quick Reference
|
|
69
69
|
|
|
70
|
+
**Spec root (MANDATORY)**: read every file below from `${CLAUDE_PLUGIN_ROOT}` — the install root of the ForgeDock plugin that is running this command (Claude Code fills it in when it loads the spec). Only if that path does not start with `/` (not a Claude Code plugin session: install.sh, Codex, OpenCode) use `$FORGE_HOME` instead. Never read sub-files from `$FORGE_HOME` when the plugin root resolved: an exported `FORGE_HOME` can point at a different, older ForgeDock checkout, and mixing roots runs stale phase specs.
|
|
71
|
+
|
|
72
|
+
**Raw reads are not substituted (MANDATORY)**: Claude Code fills in the plugin root only in the spec it loads (this file), never in files you open with Read. The phase files contain the placeholder written as `$` immediately followed by `{CLAUDE_PLUGIN_ROOT}`. Before running any bash block taken from a phase file, replace every occurrence of that placeholder with `${CLAUDE_PLUGIN_ROOT}` (the resolved root shown here), exactly as Claude Code would have. Left unreplaced it is rejected as a non-absolute path and resolution falls back to `$FORGE_HOME`, which may be stale.
|
|
73
|
+
|
|
70
74
|
```
|
|
71
|
-
Read: $
|
|
72
|
-
Read: $
|
|
73
|
-
Read: $
|
|
74
|
-
Read: $
|
|
75
|
-
Read: $
|
|
76
|
-
Read: $
|
|
77
|
-
Read: $
|
|
78
|
-
Read: $
|
|
75
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/orchestrate/config.md # ALWAYS READ FIRST
|
|
76
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/orchestrate/phase-1-resolve.md
|
|
77
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/orchestrate/phase-2-triage.md
|
|
78
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/orchestrate/phase-2.5-synthesis.md
|
|
79
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/orchestrate/phase-3-dependency.md
|
|
80
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/orchestrate/phase-4-execution.md
|
|
81
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/orchestrate/phase-5-cleanup.md
|
|
82
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/orchestrate/phase-6-report.md
|
|
79
83
|
```
|
|
80
84
|
|
|
81
85
|
The orchestrator reads only the phase file(s) relevant to the current step rather than
|
|
@@ -6,6 +6,8 @@ install: extras
|
|
|
6
6
|
|
|
7
7
|
# /pipeline-health — Phase 4: Generate Improvement Proposals
|
|
8
8
|
|
|
9
|
+
> **Skill names**: `{FORGE_SKILL_PREFIX}` is `forgedock:` (plugin install) or empty (`install.sh`), resolved once per run by `commands/work-on.md` § Skill Name Resolution. If the skill is not found under either name, STOP and report "skill not found" — never run the phase inline.
|
|
10
|
+
|
|
9
11
|
## Phase 4: Generate Improvement Proposals
|
|
10
12
|
|
|
11
13
|
### 4A: Pattern recurrence analysis — check-promotion threshold <!-- Added: forge#1331 -->
|
|
@@ -40,7 +42,7 @@ while IFS= read -r line; do
|
|
|
40
42
|
|
|
41
43
|
# SECURITY: $SLUG is sourced from a <!-- FORGE:PATTERN: ([^\s>]+) --> tag inside
|
|
42
44
|
# a review-finding issue body — untrusted, externally-influenced text. It is
|
|
43
|
-
# interpolated below into a Skill(skill="issue", args="...") string, and
|
|
45
|
+
# interpolated below into a Skill(skill="{FORGE_SKILL_PREFIX}issue", args="...") string, and
|
|
44
46
|
# commands/issue.md's Phase 1A resolves that string via `eval "set -- $ARGUMENTS"`.
|
|
45
47
|
# Any shell metacharacter (quote, backtick, $, ;) in $SLUG would break out of the
|
|
46
48
|
# intended --title token and be eval'd as shell. Whitelist-validate at the
|
|
@@ -108,7 +110,7 @@ ${SOURCE_FINDINGS}
|
|
|
108
110
|
<!-- AUTO-CREATED: pipeline-health Phase 4A — pattern recurrence threshold exceeded -->
|
|
109
111
|
CHECK_EOF
|
|
110
112
|
|
|
111
|
-
Skill(skill="issue", args="--title \"feat(quality-gate): promote pattern '$SLUG' to deterministic registry check (recurred ${COUNT}x)\" --body-file $CHECK_BODY_FILE --label check-promotion --label priority:P2")
|
|
113
|
+
Skill(skill="{FORGE_SKILL_PREFIX}issue", args="--title \"feat(quality-gate): promote pattern '$SLUG' to deterministic registry check (recurred ${COUNT}x)\" --body-file $CHECK_BODY_FILE --label check-promotion --label priority:P2")
|
|
112
114
|
rm -f "$CHECK_BODY_FILE"
|
|
113
115
|
echo "Created check-promotion issue for pattern '$SLUG' (recurred ${COUNT}x from: $SOURCE_FINDINGS)"
|
|
114
116
|
done <<< "$PATTERN_LIST"
|
|
@@ -170,7 +172,7 @@ echo "$SPEC_CONCENTRATION" | while IFS=' ' read -r count spec_file finding_nums;
|
|
|
170
172
|
|
|
171
173
|
# SECURITY (defense-in-depth, consistency with $SLUG above): $spec_file is
|
|
172
174
|
# already constrained by the extraction regex (commands/[a-z0-9_/-]+\.md), but
|
|
173
|
-
# it is interpolated below into a Skill(skill="issue", args="...") string that
|
|
175
|
+
# it is interpolated below into a Skill(skill="{FORGE_SKILL_PREFIX}issue", args="...") string that
|
|
174
176
|
# /issue's Phase 1A resolves via eval. Whitelist-validate again at point of use
|
|
175
177
|
# so this call site does not depend solely on the extraction regex staying safe.
|
|
176
178
|
if ! echo "$spec_file" | grep -qE '^commands/[A-Za-z0-9_/-]+\.md$'; then
|
|
@@ -235,7 +237,7 @@ This issue was auto-created by \`pipeline-health\` Phase 4A.5.
|
|
|
235
237
|
<!-- AUTO-CREATED: pipeline-health Phase 4A.5 — spec concentration threshold exceeded -->
|
|
236
238
|
TRIGGER_EOF
|
|
237
239
|
|
|
238
|
-
Skill(skill="issue", args="--title \"feat(spec-doctor): queue spec evolution pass for $(basename $spec_file .md) (${count} concentrated findings)\" --body-file $TRIGGER_BODY_FILE --label spec-doctor-trigger --label priority:P2")
|
|
240
|
+
Skill(skill="{FORGE_SKILL_PREFIX}issue", args="--title \"feat(spec-doctor): queue spec evolution pass for $(basename $spec_file .md) (${count} concentrated findings)\" --body-file $TRIGGER_BODY_FILE --label spec-doctor-trigger --label priority:P2")
|
|
239
241
|
rm -f "$TRIGGER_BODY_FILE"
|
|
240
242
|
echo "Created spec-doctor trigger for '$spec_file' (${count} findings: $finding_nums)"
|
|
241
243
|
done
|
|
@@ -6,6 +6,8 @@ install: extras
|
|
|
6
6
|
|
|
7
7
|
# /pipeline-health — Phase 5: Report & Track
|
|
8
8
|
|
|
9
|
+
> **Skill names**: `{FORGE_SKILL_PREFIX}` is `forgedock:` (plugin install) or empty (`install.sh`), resolved once per run by `commands/work-on.md` § Skill Name Resolution. If the skill is not found under either name, STOP and report "skill not found" — never run the phase inline.
|
|
10
|
+
|
|
9
11
|
## Phase 5: Report & Track
|
|
10
12
|
|
|
11
13
|
### 5A: Post health report
|
|
@@ -279,7 +281,7 @@ restore_forge_yaml() {
|
|
|
279
281
|
trap restore_forge_yaml EXIT
|
|
280
282
|
cp "$FORGE_ISSUE_HOOK_CONFIG" forge.yaml
|
|
281
283
|
|
|
282
|
-
ISSUE_RESULT=$(Skill(skill="issue", args="--title \"Pipeline Health: $REPO — $(date +%Y-%m-%d) — Score: [SCORE]/100\" --body-file $HEALTH_BODY_FILE --label health-report"))
|
|
284
|
+
ISSUE_RESULT=$(Skill(skill="{FORGE_SKILL_PREFIX}issue", args="--title \"Pipeline Health: $REPO — $(date +%Y-%m-%d) — Score: [SCORE]/100\" --body-file $HEALTH_BODY_FILE --label health-report"))
|
|
283
285
|
echo "$ISSUE_RESULT"
|
|
284
286
|
|
|
285
287
|
# Restore the original forge.yaml immediately — so a subsequent Bash call in this same
|
|
@@ -390,7 +392,7 @@ restore_forge_yaml() {
|
|
|
390
392
|
trap restore_forge_yaml EXIT
|
|
391
393
|
cp "$FORGE_ISSUE_HOOK_CONFIG" forge.yaml
|
|
392
394
|
|
|
393
|
-
ISSUE_RESULT=$(Skill(skill="issue", args="--title \"fix([command]): [description]\" --body-file $PROPOSAL_BODY_FILE --label \"[bug|enhancement|feature]\" --label \"[P1|P2|P3]\""))
|
|
395
|
+
ISSUE_RESULT=$(Skill(skill="{FORGE_SKILL_PREFIX}issue", args="--title \"fix([command]): [description]\" --body-file $PROPOSAL_BODY_FILE --label \"[bug|enhancement|feature]\" --label \"[P1|P2|P3]\""))
|
|
394
396
|
echo "$ISSUE_RESULT"
|
|
395
397
|
|
|
396
398
|
# Restore the original forge.yaml immediately — same rationale as 5A. Clears the EXIT trap
|
|
@@ -31,14 +31,16 @@ Read and execute phases in sequence. Each phase file is self-contained.
|
|
|
31
31
|
|
|
32
32
|
## Quick Reference
|
|
33
33
|
|
|
34
|
+
**Spec root (MANDATORY)**: read every file below from `${CLAUDE_PLUGIN_ROOT}` — the install root of the ForgeDock plugin that is running this command (Claude Code fills it in when it loads the spec). Only if that path does not start with `/` (not a Claude Code plugin session: install.sh, Codex, OpenCode) use `$FORGE_HOME` instead. Never read sub-files from `$FORGE_HOME` when the plugin root resolved: an exported `FORGE_HOME` can point at a different, older ForgeDock checkout, and mixing roots runs stale phase specs.
|
|
35
|
+
|
|
34
36
|
```
|
|
35
|
-
Read: $
|
|
36
|
-
Read: $
|
|
37
|
-
Read: $
|
|
38
|
-
Read: $
|
|
39
|
-
Read: $
|
|
40
|
-
Read: $
|
|
41
|
-
Read: $
|
|
37
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/pipeline-health/config.md # ALWAYS READ FIRST
|
|
38
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/pipeline-health/phase-1-context.md
|
|
39
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/pipeline-health/phase-2-metrics.md
|
|
40
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/pipeline-health/phase-3-analyze.md
|
|
41
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/pipeline-health/phase-4-proposals.md
|
|
42
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/pipeline-health/phase-5-report.md
|
|
43
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/pipeline-health/phase-6-summary.md
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
The command reads only the phase file(s) relevant to the current step rather than loading
|
|
@@ -6,13 +6,15 @@ install: extras
|
|
|
6
6
|
|
|
7
7
|
# /pipeline-resume — Context Recovery After Compaction
|
|
8
8
|
|
|
9
|
+
> **Skill names**: `{FORGE_SKILL_PREFIX}` is `forgedock:` (plugin install) or empty (`install.sh`), resolved once per run by `commands/work-on.md` § Skill Name Resolution. If the skill is not found under either name, STOP and report "skill not found" — never run the phase inline.
|
|
10
|
+
|
|
9
11
|
**Input**: $ARGUMENTS
|
|
10
12
|
|
|
11
13
|
You are the context-recovery agent. When a session ends or context is compressed, all working state is lost. Your job is to restore it instantly — find the most recently active issue, read all FORGE annotations, reconstruct the current pipeline phase, and re-enter the pipeline exactly where it left off.
|
|
12
14
|
|
|
13
15
|
**Agent model policy**: `model: "haiku"`, `effort: low` (mechanical tier — context recovery, annotation reading, state reconstruction). Fallback: `model: "sonnet"` if rate-limited. Feature gate: pass `effort` only on Claude Code >= 2.1.154.
|
|
14
16
|
**NEVER use plan mode (EnterPlanMode).**
|
|
15
|
-
**NEVER use the Agent tool** — pipeline-resume re-enters the pipeline via `Skill(skill="work-on", ...)` only. Using the Agent tool would restart the pipeline in an untracked subprocess rather than resuming the existing tracked session.
|
|
17
|
+
**NEVER use the Agent tool** — pipeline-resume re-enters the pipeline via `Skill(skill="{FORGE_SKILL_PREFIX}work-on", ...)` only. Using the Agent tool would restart the pipeline in an untracked subprocess rather than resuming the existing tracked session.
|
|
16
18
|
|
|
17
19
|
<!-- FORGE:SPEC_LOADED — pipeline-resume.md loaded and active. Agent is bound by this spec. -->
|
|
18
20
|
|
|
@@ -267,7 +269,7 @@ Emit a structured context block before resuming — this is what anchors the age
|
|
|
267
269
|
Invoke `/work-on` to re-enter the pipeline. Work-on reads GitHub state independently and routes to the correct phase automatically.
|
|
268
270
|
|
|
269
271
|
```
|
|
270
|
-
Skill("work-on", args="{TARGET_NUMBER}")
|
|
272
|
+
Skill("{FORGE_SKILL_PREFIX}work-on", args="{TARGET_NUMBER}")
|
|
271
273
|
```
|
|
272
274
|
|
|
273
275
|
**Why delegation works**: `/work-on` Phase 0B already implements "Determine resume point" — it reads issue labels and FORGE annotations to route to the correct phase. `/pipeline-resume` provides the human-readable context summary, then hands off to `/work-on` for execution.
|
package/commands/qa-sweep.md
CHANGED
|
@@ -8,6 +8,8 @@ install: extras
|
|
|
8
8
|
|
|
9
9
|
# /qa-sweep — Platform-Wide QA Testing
|
|
10
10
|
|
|
11
|
+
> **Skill names**: `{FORGE_SKILL_PREFIX}` is `forgedock:` (plugin install) or empty (`install.sh`), resolved once per run by `commands/work-on.md` § Skill Name Resolution. If the skill is not found under either name, STOP and report "skill not found" — never run the phase inline.
|
|
12
|
+
|
|
11
13
|
**Input**: $ARGUMENTS
|
|
12
14
|
|
|
13
15
|
You are the QA orchestrator. Auto-discover every page across the platform (dashboard, marketing, blog, auth, docs, pricing), then systematically test every UI element, interaction, workflow, and state transition using browser automation. Create GitHub issues for every finding.
|
|
@@ -339,7 +341,7 @@ BODY_EOF
|
|
|
339
341
|
```
|
|
340
342
|
|
|
341
343
|
```
|
|
342
|
-
Skill(skill="issue", args="--title \"{fix|feat}: {title} on {page}\" --body-file \"$QA_BODY_FILE\" --label \"qa\" --label \"{bug_or_enhancement}\" --label \"{priority}\"")
|
|
344
|
+
Skill(skill="{FORGE_SKILL_PREFIX}issue", args="--title \"{fix|feat}: {title} on {page}\" --body-file \"$QA_BODY_FILE\" --label \"qa\" --label \"{bug_or_enhancement}\" --label \"{priority}\"")
|
|
343
345
|
```
|
|
344
346
|
|
|
345
347
|
`qa`, `{bug_or_enhancement}`, and `{priority}` are each passed as their own `--label` flag — `/issue`'s programmatic mode does not comma-split a single `--label` value.
|
package/commands/quality-gate.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
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
|
+
context: fork
|
|
4
5
|
---
|
|
5
6
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
7
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
@@ -1712,13 +1713,13 @@ OR:
|
|
|
1712
1713
|
|
|
1713
1714
|
Return the findings list directly. Do NOT post to GitHub. Do NOT create issues. The builder will fix the findings and re-run the gate if needed.
|
|
1714
1715
|
|
|
1715
|
-
**
|
|
1716
|
+
**Result delivery**: This skill runs in an isolated forked context, so your final reply is the only thing the caller sees. The PASS or FINDINGS block below MUST be your final reply, in ALL cases, with nothing after it. The caller (the validate quality-gate loop) decides what happens next.
|
|
1716
1717
|
|
|
1717
1718
|
If 0 findings:
|
|
1718
1719
|
```
|
|
1719
1720
|
QUALITY GATE: PASS — no issues detected.
|
|
1720
1721
|
|
|
1721
|
-
|
|
1722
|
+
Return this result to the caller.
|
|
1722
1723
|
```
|
|
1723
1724
|
|
|
1724
1725
|
If findings exist:
|
|
@@ -1726,5 +1727,5 @@ If findings exist:
|
|
|
1726
1727
|
QUALITY GATE FINDINGS: N issues detected.
|
|
1727
1728
|
{findings list}
|
|
1728
1729
|
|
|
1729
|
-
|
|
1730
|
+
Return this result to the caller; the caller fixes each HIGH and MEDIUM finding and re-runs the gate.
|
|
1730
1731
|
```
|
|
@@ -7,13 +7,15 @@ argument-hint: "[--dry-run | --since <hours> | --issue <number>]"
|
|
|
7
7
|
|
|
8
8
|
# /recover-orphans — Pipeline Orphan Recovery
|
|
9
9
|
|
|
10
|
+
> **Skill names**: `{FORGE_SKILL_PREFIX}` is `forgedock:` (plugin install) or empty (`install.sh`), resolved once per run by `commands/work-on.md` § Skill Name Resolution. If the skill is not found under either name, STOP and report "skill not found" — never run the phase inline.
|
|
11
|
+
|
|
10
12
|
**Input**: $ARGUMENTS
|
|
11
13
|
|
|
12
14
|
Scan ALL open issues with intermediate workflow labels for orphaned state — issues where the agent died mid-pipeline (context expired, rate-limited, crashed) and no active agent is continuing. Diagnose each orphan's actual GitHub state and apply the appropriate recovery action.
|
|
13
15
|
|
|
14
16
|
**Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet". Fallback: `model: "opus"` if rate-limited.
|
|
15
17
|
**NEVER use plan mode (EnterPlanMode).**
|
|
16
|
-
**NEVER use the Agent tool** — recover-orphans re-enters the pipeline via `Skill(skill="work-on", ...)` and `Skill(skill="review-pr", ...)` only.
|
|
18
|
+
**NEVER use the Agent tool** — recover-orphans re-enters the pipeline via `Skill(skill="{FORGE_SKILL_PREFIX}work-on", ...)` and `Skill(skill="{FORGE_SKILL_PREFIX}review-pr", ...)` only.
|
|
17
19
|
|
|
18
20
|
<!-- FORGE:SPEC_LOADED — recover-orphans.md loaded and active. Agent is bound by this spec. -->
|
|
19
21
|
|
|
@@ -171,7 +173,6 @@ for NUM in $ORPHAN_LIST; do
|
|
|
171
173
|
DIAG_REASON[$NUM]="Already in terminal state: $ISSUE_LABELS"
|
|
172
174
|
continue
|
|
173
175
|
fi
|
|
174
|
-
|
|
175
176
|
# Check for merged PR referencing this issue
|
|
176
177
|
MERGED_PR=$(gh pr list ${GH_FLAG} \
|
|
177
178
|
--state merged \
|
|
@@ -198,6 +199,15 @@ for NUM in $ORPHAN_LIST; do
|
|
|
198
199
|
continue
|
|
199
200
|
fi
|
|
200
201
|
|
|
202
|
+
# forge#3148: checked after the merged-PR label cleanup above, so a merged orphan is still closed out.
|
|
203
|
+
# An escalated orphan is waiting on a human — re-running a recovery action every sweep
|
|
204
|
+
# (e.g. re-invoking /review-pr against an unrepaired phase trail) only repeats the same refusal.
|
|
205
|
+
if echo ", $ISSUE_LABELS," | grep -q ", needs-human,"; then
|
|
206
|
+
DIAG_ACTION[$NUM]="skip"
|
|
207
|
+
DIAG_REASON[$NUM]="Escalated (needs-human) — waiting on a human"
|
|
208
|
+
continue
|
|
209
|
+
fi
|
|
210
|
+
|
|
201
211
|
# Check for an open PR on any branch associated with this issue
|
|
202
212
|
# Strategy: look for branches with the issue number in the name
|
|
203
213
|
ISSUE_BRANCH=$(git ls-remote --heads origin 2>/dev/null | grep "/$NUM" | sed 's|.*refs/heads/||' | head -1)
|
|
@@ -307,6 +317,68 @@ done
|
|
|
307
317
|
|
|
308
318
|
For each diagnosed issue, apply the recovery action. All mutating actions are skipped when `DRY_RUN=true`.
|
|
309
319
|
|
|
320
|
+
**Claim before re-entering the pipeline** (forge#3158): the `review-pr` first-refusal resume and `create-pr` re-enter `/work-on` inline. A repeated or concurrent sweep, or a live `/orchestrate` agent, could otherwise run the pipeline twice on one issue. Every inline `/work-on` resume MUST go through `claim_orphan` first and call `release_orphan` afterwards. A sweep is not an orchestrator, so it takes an issue-scoped `FORGE:RECOVERY_CLAIM` marker (visible to other sweeps) and defers to any live orchestrator signal (a fresh `FORGE:HEARTBEAT`, which `/work-on --under-orchestration` posts at every phase entry).
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
RECOVERY_CLAIM_TTL_MIN="${RECOVERY_CLAIM_TTL_MIN:-30}" # a claim or heartbeat older than this is treated as dead
|
|
324
|
+
SWEEP_ID="sweep-$(date -u +%Y%m%dT%H%M%SZ)-$$"
|
|
325
|
+
|
|
326
|
+
# claim_orphan <issue> — returns 0 if this sweep now holds the claim, 1 if it must skip (reason in CLAIM_SKIP_REASON).
|
|
327
|
+
# Fails closed: an unreadable comment list (initial read or race-check re-read) is treated as "held by someone else".
|
|
328
|
+
# Both reads run under pipefail in a subshell: the pipeline status would otherwise be jq's, and a gh failure
|
|
329
|
+
# (rate limit, 403) would yield "[]" with exit 0, indistinguishable from "no live holder".
|
|
330
|
+
claim_orphan() {
|
|
331
|
+
local num="$1" now cutoff comments live
|
|
332
|
+
[ "$DRY_RUN" = "true" ] && return 0 # dry-run: no claim comment is posted (callers already skip dry-run resumes)
|
|
333
|
+
now=$(date -u +%s); cutoff=$(( now - RECOVERY_CLAIM_TTL_MIN * 60 ))
|
|
334
|
+
comments=$(set -o pipefail; gh api --paginate "repos/${GH_REPO}/issues/${num}/comments" 2>/dev/null \
|
|
335
|
+
| jq -s 'add // []') || { CLAIM_SKIP_REASON="could not read comments to check for a live holder"; return 1; }
|
|
336
|
+
|
|
337
|
+
# Live orchestrator signal: a recent heartbeat from /work-on --under-orchestration, or a recent claim of any sweep.
|
|
338
|
+
# A claim is "released" only by a later RELEASED marker naming its sweep id.
|
|
339
|
+
live=$(echo "$comments" | jq --argjson cutoff "$cutoff" '
|
|
340
|
+
([.[] | select(.body | contains("FORGE:RECOVERY_CLAIM_RELEASED")) | .body | capture("Sweep: (?<id>[^ \n]+)").id]) as $rel
|
|
341
|
+
| [ .[] | select(
|
|
342
|
+
((.body | contains("FORGE:HEARTBEAT")) or (.body | contains("<!-- FORGE:RECOVERY_CLAIM -->")))
|
|
343
|
+
and ((.body | contains("FORGE:RECOVERY_CLAIM_RELEASED")) | not)
|
|
344
|
+
and ((.updated_at | fromdateiso8601) >= $cutoff)
|
|
345
|
+
and ((.body | capture("Sweep: (?<id>[^ \n]+)")? // {id:""}).id as $sid | ($rel | index($sid)) == null)
|
|
346
|
+
) ] | length') || live=1
|
|
347
|
+
if [ "${live:-1}" -gt 0 ]; then
|
|
348
|
+
CLAIM_SKIP_REASON="live holder (heartbeat or unreleased recovery claim within ${RECOVERY_CLAIM_TTL_MIN}m)"
|
|
349
|
+
return 1
|
|
350
|
+
fi
|
|
351
|
+
|
|
352
|
+
gh issue comment "$num" ${GH_FLAG} --body "<!-- FORGE:RECOVERY_CLAIM -->
|
|
353
|
+
**Sweep: ${SWEEP_ID}**
|
|
354
|
+
Holder: /recover-orphans — resuming /work-on #${num}. Other sweeps and dispatchers: do not re-enter this issue until the matching release marker is posted." >/dev/null 2>&1 \
|
|
355
|
+
|| { CLAIM_SKIP_REASON="could not post FORGE:RECOVERY_CLAIM"; return 1; }
|
|
356
|
+
|
|
357
|
+
# Race check: re-read; the earliest unreleased claim wins. Losing means another sweep claimed first.
|
|
358
|
+
local first
|
|
359
|
+
first=$(set -o pipefail; gh api --paginate "repos/${GH_REPO}/issues/${num}/comments" 2>/dev/null | jq -rs --argjson cutoff "$cutoff" '
|
|
360
|
+
add // [] | ([.[] | select(.body | contains("FORGE:RECOVERY_CLAIM_RELEASED")) | .body | capture("Sweep: (?<id>[^ \n]+)").id]) as $rel
|
|
361
|
+
| [.[] | select(.body | contains("<!-- FORGE:RECOVERY_CLAIM -->"))
|
|
362
|
+
| select((.updated_at | fromdateiso8601) >= $cutoff)
|
|
363
|
+
| select((.body | capture("Sweep: (?<id>[^ \n]+)").id) as $sid | ($rel | index($sid)) == null)]
|
|
364
|
+
| first | .body // ""' | sed -n 's/^\*\*Sweep: \(.*\)\*\*$/\1/p') \
|
|
365
|
+
|| { release_orphan "$num"; CLAIM_SKIP_REASON="could not re-read comments to confirm the claim race"; return 1; }
|
|
366
|
+
if [ "$first" != "$SWEEP_ID" ]; then
|
|
367
|
+
release_orphan "$num"
|
|
368
|
+
CLAIM_SKIP_REASON="lost claim race to ${first:-unknown}"
|
|
369
|
+
return 1
|
|
370
|
+
fi
|
|
371
|
+
return 0
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
release_orphan() {
|
|
375
|
+
[ "$DRY_RUN" = "true" ] && return 0
|
|
376
|
+
gh issue comment "$1" ${GH_FLAG} --body "<!-- FORGE:RECOVERY_CLAIM_RELEASED -->
|
|
377
|
+
**Sweep: ${SWEEP_ID}**
|
|
378
|
+
Released by /recover-orphans." >/dev/null 2>&1 || true
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
310
382
|
```bash
|
|
311
383
|
RECOVERY_RESULTS=""
|
|
312
384
|
|
|
@@ -352,8 +424,31 @@ for NUM in $ORPHAN_LIST; do
|
|
|
352
424
|
if [ "$DRY_RUN" = "true" ]; then
|
|
353
425
|
echo " [DRY-RUN] Would: gh pr merge $PR_NUM --merge --auto"
|
|
354
426
|
else
|
|
355
|
-
|
|
356
|
-
|
|
427
|
+
# CI gate (MANDATORY before any autonomous merge): merge only when every check on the PR is
|
|
428
|
+
# green. Field test: PRs merged to staging with checks pending or red (#3165), because branch
|
|
429
|
+
# protection required none and an auto-merge waits only for *required* checks.
|
|
430
|
+
CI_GATE_SCRIPT=""
|
|
431
|
+
_l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
|
|
432
|
+
for _c in '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l"; do
|
|
433
|
+
case "$_c" in /*) [ -z "$CI_GATE_SCRIPT" ] && [ -f "$_c/scripts/wait-ci-green.sh" ] && CI_GATE_SCRIPT="$_c/scripts/wait-ci-green.sh" ;; esac
|
|
434
|
+
done
|
|
435
|
+
if [ -n "$CI_GATE_SCRIPT" ]; then CI_GATE_OUT=$(bash "$CI_GATE_SCRIPT" "$PR_NUM" ${GH_FLAG}); CI_GATE_RC=$?
|
|
436
|
+
else CI_GATE_OUT="CI_GATE: ERROR — scripts/wait-ci-green.sh not resolvable (fail closed)"; CI_GATE_RC=2; fi
|
|
437
|
+
echo "$CI_GATE_OUT"
|
|
438
|
+
GATED_HEAD=$(printf '%s\n' "$CI_GATE_OUT" | sed -n 's/^CI_GATE_HEAD: //p' | head -1)
|
|
439
|
+
# rc 3 = CI still running: re-run this block (up to 3 more times) before treating it as a failure.
|
|
440
|
+
if [ "$CI_GATE_RC" -eq 0 ]; then
|
|
441
|
+
MERGE_RESULT=$(gh pr merge "$PR_NUM" ${GH_FLAG} --merge --auto --match-head-commit "$GATED_HEAD" 2>&1)
|
|
442
|
+
MERGE_EXIT=$?
|
|
443
|
+
else
|
|
444
|
+
MERGE_RESULT="not merged: CI gate rc=${CI_GATE_RC}"; MERGE_EXIT=1
|
|
445
|
+
CI_MSG="⛔ /recover-orphans did not merge PR #${PR_NUM}: CI is not green.
|
|
446
|
+
\`\`\`
|
|
447
|
+
${CI_GATE_OUT}
|
|
448
|
+
\`\`\`"
|
|
449
|
+
gh issue comment "$NUM" ${GH_FLAG} --body "$CI_MSG" 2>/dev/null || true # allowlist:check-command-side-effects
|
|
450
|
+
gh issue edit "$NUM" ${GH_FLAG} --add-label "needs-human" 2>/dev/null || true # allowlist:check-command-side-effects
|
|
451
|
+
fi
|
|
357
452
|
echo " Merge result (exit $MERGE_EXIT): $MERGE_RESULT"
|
|
358
453
|
if [ $MERGE_EXIT -eq 0 ]; then
|
|
359
454
|
# Close the issue explicitly (Closes # only auto-closes on default branch)
|
|
@@ -373,25 +468,89 @@ for NUM in $ORPHAN_LIST; do
|
|
|
373
468
|
# Open PR awaiting review — invoke /review-pr
|
|
374
469
|
echo " Applying review-pr: invoking /review-pr on PR #$PR_NUM"
|
|
375
470
|
if [ "$DRY_RUN" = "true" ]; then
|
|
376
|
-
echo " [DRY-RUN] Would: Skill(skill='review-pr', args='$PR_NUM --auto-merge --issue $NUM --gh-flag $GH_FLAG')"
|
|
471
|
+
echo " [DRY-RUN] Would: Skill(skill='{FORGE_SKILL_PREFIX}review-pr', args='$PR_NUM --auto-merge --issue $NUM --gh-flag $GH_FLAG')"
|
|
377
472
|
else
|
|
378
|
-
|
|
379
|
-
#
|
|
380
|
-
|
|
381
|
-
|
|
473
|
+
# forge#3148: count trail refusals that predate this sweep, so a refusal from an earlier sweep escalates
|
|
474
|
+
# instead of re-reviewing the same unrepaired trail every run.
|
|
475
|
+
# forge#3159: count only TRUSTED comments (pipeline bot, or OWNER/MEMBER/COLLABORATOR) newer than the latest
|
|
476
|
+
# repair (the most recent needs-human unlabel event), so a stranger's comment or a refusal a human already
|
|
477
|
+
# repaired never forces an escalation. Epoch fallback on a failed lookup counts more, i.e. fails toward escalation.
|
|
478
|
+
# --paginate: the comments endpoint returns 30 per page, oldest first. An unreadable count fails toward escalation.
|
|
479
|
+
PRIOR_TRAIL_CUTOFF=$(gh api --paginate "repos/${GH_REPO}/issues/${NUM}/events" \
|
|
480
|
+
--jq '.[] | select(.event == "unlabeled" and .label.name == "needs-human") | .created_at' 2>/dev/null \
|
|
481
|
+
| sort | tail -1)
|
|
482
|
+
PRIOR_TRAIL_CUTOFF="${PRIOR_TRAIL_CUTOFF:-1970-01-01T00:00:00Z}"
|
|
483
|
+
# gh api --jq takes one expression and no --arg, so the cutoff goes through a real jq pipe.
|
|
484
|
+
if PRIOR_TRAIL_IDS=$(set -o pipefail; gh api --paginate "repos/${GH_REPO}/issues/${NUM}/comments" 2>/dev/null \
|
|
485
|
+
| jq -r --arg cutoff "$PRIOR_TRAIL_CUTOFF" '.[] | select(.body | contains("FORGE:PHASE_TRAIL_FAILED"))
|
|
486
|
+
| select(.created_at > $cutoff)
|
|
487
|
+
| select(.user.type == "Bot" or (.author_association | IN("OWNER","MEMBER","COLLABORATOR"))) | .id'); then
|
|
488
|
+
PRIOR_TRAIL_FAILS=$(printf '%s\n' "$PRIOR_TRAIL_IDS" | grep -c '[0-9]')
|
|
489
|
+
else
|
|
490
|
+
PRIOR_TRAIL_FAILS=1
|
|
491
|
+
fi
|
|
492
|
+
REVIEW_STATUS=""; REVIEW_BLOCKER="" # reset per orphan so a previous orphan's result never leaks into this one
|
|
493
|
+
Skill(skill="{FORGE_SKILL_PREFIX}review-pr", args="${PR_NUM} --auto-merge --issue ${NUM} --gh-flag ${GH_FLAG}")
|
|
494
|
+
# REVIEW_STATUS = the `status:` field of the REVIEW_RESULT block the Skill call returned.
|
|
495
|
+
if [ "$REVIEW_STATUS" = "PHASE_TRAIL_FAILED" ]; then
|
|
496
|
+
# Phase 8 refused the merge: the issue's phase trail is incomplete (review-pr posted the
|
|
497
|
+
# MISSING lines as FORGE:PHASE_TRAIL_FAILED). Do NOT set workflow:in-review — that only re-queues
|
|
498
|
+
# the same refusal for the next sweep. Never hand-post the missing markers.
|
|
499
|
+
if [ "${PRIOR_TRAIL_FAILS:-0}" -gt 0 ]; then
|
|
500
|
+
gh issue edit "$NUM" ${GH_FLAG} --add-label "needs-human" 2>/dev/null || true
|
|
501
|
+
gh issue comment "$NUM" ${GH_FLAG} --body "<!-- FORGE:ORPHAN_RECOVERED -->
|
|
502
|
+
## Orphan Recovery — Escalated
|
|
503
|
+
|
|
504
|
+
**Action**: \`/review-pr\` refused to merge PR #${PR_NUM} again because the phase trail is incomplete, and an earlier refusal was not repaired.
|
|
505
|
+
**Recovered by**: /recover-orphans
|
|
506
|
+
|
|
507
|
+
See the latest FORGE:PHASE_TRAIL_FAILED comment for the missing phases. Re-run each one via its Skill (or \`/work-on ${NUM}\`), then remove \`needs-human\`." 2>/dev/null || true
|
|
508
|
+
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | review-pr | PR #${PR_NUM} refused (phase trail) again — needs-human added |\n"
|
|
509
|
+
else
|
|
510
|
+
# First refusal: resume /work-on, whose resume preflight re-runs each missing phase via Skill(...)
|
|
511
|
+
# and then re-enters review (work-on.md Phase 0B resume → work-on/review.md).
|
|
512
|
+
if claim_orphan "$NUM"; then
|
|
513
|
+
Skill(skill="{FORGE_SKILL_PREFIX}work-on", args="${NUM}")
|
|
514
|
+
release_orphan "$NUM"
|
|
515
|
+
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | review-pr | PR #${PR_NUM} refused (phase trail) — resumed /work-on to re-run missing phases |\n"
|
|
516
|
+
else
|
|
517
|
+
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | review-pr | PR #${PR_NUM} refused (phase trail) — /work-on resume skipped: ${CLAIM_SKIP_REASON} |\n"
|
|
518
|
+
fi
|
|
519
|
+
fi
|
|
520
|
+
elif [ "$REVIEW_STATUS" = "BLOCKED" ] && echo "$REVIEW_BLOCKER" | grep -qE 'phase trail|auto-merge requires --issue'; then
|
|
521
|
+
# REVIEW_BLOCKER = the `blocker:` field of the same REVIEW_RESULT block. The gate could not run (verifier
|
|
522
|
+
# rc>=2 / unresolvable) or had no issue to verify (forge#3147). review-pr already added needs-human for the
|
|
523
|
+
# rc>=2 case; add it here too so the diagnosis skip above stops re-sweeping this orphan.
|
|
524
|
+
gh issue edit "$NUM" ${GH_FLAG} --add-label "needs-human" 2>/dev/null || true
|
|
525
|
+
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | review-pr | PR #${PR_NUM} blocked by the merge gate — needs-human added |\n"
|
|
526
|
+
elif case "$REVIEW_STATUS" in COMPLETE|ALREADY_MERGED|BLOCKED) false ;; *) true ;; esac; then
|
|
527
|
+
# forge#3159: an empty or unrecognized status means the review result could not be read. Do not report
|
|
528
|
+
# "submitted for review" or re-queue it; escalate to a human.
|
|
529
|
+
gh issue edit "$NUM" ${GH_FLAG} --add-label "needs-human" 2>/dev/null || true
|
|
530
|
+
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | review-pr | PR #${PR_NUM} unrecognized review status '${REVIEW_STATUS}' — needs-human added |\n"
|
|
531
|
+
else
|
|
532
|
+
# After review: update label
|
|
533
|
+
gh issue edit "$NUM" ${GH_FLAG} --add-label "workflow:in-review" \
|
|
534
|
+
--remove-label "workflow:building,workflow:awaiting-merge" 2>/dev/null || true
|
|
535
|
+
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | review-pr | PR #${PR_NUM} submitted for review |\n"
|
|
536
|
+
fi
|
|
382
537
|
fi
|
|
383
|
-
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | review-pr | PR #${PR_NUM} submitted for review |\n"
|
|
538
|
+
[ "$DRY_RUN" = "true" ] && RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | review-pr | PR #${PR_NUM} would be submitted for review |\n"
|
|
384
539
|
;;
|
|
385
540
|
|
|
386
541
|
create-pr)
|
|
387
542
|
# Branch has commits but no PR — resume /work-on to create PR
|
|
388
543
|
echo " Applying create-pr: resuming /work-on to advance from build to PR creation"
|
|
389
544
|
if [ "$DRY_RUN" = "true" ]; then
|
|
390
|
-
echo " [DRY-RUN] Would: Skill(skill='work-on', args='$NUM')"
|
|
545
|
+
echo " [DRY-RUN] Would: Skill(skill='{FORGE_SKILL_PREFIX}work-on', args='$NUM')"
|
|
546
|
+
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | create-pr | Would resume /work-on — branch $BRANCH has commits, no PR |\n"
|
|
547
|
+
elif claim_orphan "$NUM"; then
|
|
548
|
+
Skill(skill="{FORGE_SKILL_PREFIX}work-on", args="${NUM}")
|
|
549
|
+
release_orphan "$NUM"
|
|
550
|
+
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | create-pr | Resumed /work-on — branch $BRANCH has commits, no PR |\n"
|
|
391
551
|
else
|
|
392
|
-
|
|
552
|
+
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | create-pr | /work-on resume skipped: ${CLAIM_SKIP_REASON} |\n"
|
|
393
553
|
fi
|
|
394
|
-
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | create-pr | Resumed /work-on — branch $BRANCH has commits, no PR |\n"
|
|
395
554
|
;;
|
|
396
555
|
|
|
397
556
|
reset-labels)
|
|
@@ -37,7 +37,7 @@ For each agent selected in Phase 3B, read **only** the files relevant to that ag
|
|
|
37
37
|
|
|
38
38
|
1. **Always read first** (shared protocols, all agents require this):
|
|
39
39
|
```
|
|
40
|
-
Read: $
|
|
40
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/review-pr-agents/protocols.md
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
2. **Then read the persona file for each selected agent**:
|
|
@@ -70,11 +70,11 @@ For each agent selected in Phase 3B, read **only** the files relevant to that ag
|
|
|
70
70
|
|
|
71
71
|
## Loading Instructions for Phase 3C
|
|
72
72
|
|
|
73
|
-
Replace the single `Read: $
|
|
73
|
+
Replace the single `Read: ${CLAUDE_PLUGIN_ROOT}/commands/review-pr-agents.md` call with:
|
|
74
74
|
|
|
75
75
|
```
|
|
76
|
-
Read: $
|
|
77
|
-
Read: $
|
|
76
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/review-pr-agents/protocols.md
|
|
77
|
+
Read: ${CLAUDE_PLUGIN_ROOT}/commands/review-pr-agents/<persona>.md (one per selected agent)
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
Each persona file contains:
|
|
@@ -87,3 +87,5 @@ The `protocols.md` file contains:
|
|
|
87
87
|
- Tool-Result Truncation Discipline
|
|
88
88
|
- Evidence-Based Review Protocol (all agents follow)
|
|
89
89
|
- Structured Findings Protocol (machine-readable findings block format)
|
|
90
|
+
|
|
91
|
+
**Spec root (MANDATORY)**: read every file named in this catalog from `${CLAUDE_PLUGIN_ROOT}` — the install root of the ForgeDock plugin that is running this command (Claude Code fills it in when it loads the spec). Only if that path does not start with `/` (not a Claude Code plugin session: install.sh, Codex, OpenCode) use `$FORGE_HOME` instead. Never read sub-files from `$FORGE_HOME` when the plugin root resolved: an exported `FORGE_HOME` can point at a different, older ForgeDock checkout, and mixing roots runs stale phase specs.
|
|
@@ -9,7 +9,9 @@ install: core
|
|
|
9
9
|
|
|
10
10
|
# Staging Review
|
|
11
11
|
|
|
12
|
-
**
|
|
12
|
+
> **Skill names**: `{FORGE_SKILL_PREFIX}` is `forgedock:` (plugin install) or empty (`install.sh`), resolved once per run by `commands/work-on.md` § Skill Name Resolution. If the skill is not found under either name, STOP and report "skill not found" — never run the phase inline.
|
|
13
|
+
|
|
14
|
+
**Trigger**: Invoked by the orchestrator via `Skill("{FORGE_SKILL_PREFIX}review-pr-staging", $ARGUMENTS)`, or directly with `$ARGUMENTS` = "staging", a PR number targeting main, or "staging:feature".
|
|
13
15
|
|
|
14
16
|
Performs comprehensive review of `staging` before merging to `main`. Handles large diffs (1,000-10,000+ lines), diverse changes, deep analysis, and business impact assessment.
|
|
15
17
|
|
|
@@ -502,7 +504,13 @@ Missing persona templates are a fatal setup error, not permission to skip multi-
|
|
|
502
504
|
|
|
503
505
|
```bash
|
|
504
506
|
TEMPLATE_BASE=""
|
|
505
|
-
|
|
507
|
+
# Tier 0: the running plugin's own root (Claude Code substitutes this exact spelling; elsewhere it stays a
|
|
508
|
+
# literal that the /* check rejects). Wins over an exported FORGE_HOME, which may name an older checkout.
|
|
509
|
+
_PLUGIN_ROOT='${CLAUDE_PLUGIN_ROOT}'; case "$_PLUGIN_ROOT" in /*) ;; *) _PLUGIN_ROOT="" ;; esac
|
|
510
|
+
if [[ -n "$_PLUGIN_ROOT" && -f "$_PLUGIN_ROOT/commands/review-pr-agents/protocols.md" ]]; then
|
|
511
|
+
TEMPLATE_BASE="$_PLUGIN_ROOT/commands/review-pr-agents"
|
|
512
|
+
TEMPLATE_SOURCE="plugin_root"
|
|
513
|
+
elif [[ -f "$FORGE_HOME/commands/review-pr-agents/protocols.md" ]]; then
|
|
506
514
|
TEMPLATE_BASE="$FORGE_HOME/commands/review-pr-agents"
|
|
507
515
|
TEMPLATE_SOURCE="forge_home"
|
|
508
516
|
else
|
|
@@ -530,7 +538,7 @@ fi
|
|
|
530
538
|
|
|
531
539
|
**If `TEMPLATE_SOURCE` is `none`**: HARD STOP. Post a PR comment explaining the setup is broken, instructing the user to run `npx forgedock update` to repair the install, add `needs-human`, and exit without posting any findings or a verdict. **NEVER perform the review inline in the main agent context as a substitute.**
|
|
532
540
|
|
|
533
|
-
**If `TEMPLATE_SOURCE` is `forge_home` or `repo_path`** (normal cases — behavior unchanged): `Read: $TEMPLATE_BASE/protocols.md` and `Read: $TEMPLATE_BASE/<persona>.md` per selected agent.
|
|
541
|
+
**If `TEMPLATE_SOURCE` is `plugin_root`, `forge_home` or `repo_path`** (normal cases — behavior unchanged): `Read: $TEMPLATE_BASE/protocols.md` and `Read: $TEMPLATE_BASE/<persona>.md` per selected agent.
|
|
534
542
|
|
|
535
543
|
**If `TEMPLATE_SOURCE` is `monolithic_catalog`** (last resort): `Read: $MONOLITHIC_CATALOG` and extract the shared protocols section plus each selected persona's section from within that single file.
|
|
536
544
|
|
|
@@ -593,7 +601,7 @@ TEST_GATE_REASON="Phase 6.5 not yet run"
|
|
|
593
601
|
|
|
594
602
|
# Invoke /test-gate with the bundle PRs already computed in Phase 0A
|
|
595
603
|
# ALL_PR_NUMBERS is the de-duplicated union of both scan methods (commit log + merge subjects)
|
|
596
|
-
GATE_OUTPUT=$(Skill("test-gate", "--prs \"$(echo $ALL_PR_NUMBERS | tr '\n' ' ' | xargs)\" --base $DEFAULT_BRANCH"))
|
|
604
|
+
GATE_OUTPUT=$(Skill("{FORGE_SKILL_PREFIX}test-gate", "--prs \"$(echo $ALL_PR_NUMBERS | tr '\n' ' ' | xargs)\" --base $DEFAULT_BRANCH"))
|
|
597
605
|
|
|
598
606
|
# Extract machine-readable verdict from Skill output
|
|
599
607
|
TEST_GATE_VERDICT=$(echo "$GATE_OUTPUT" | grep -oP '(?<=FORGE:TEST_GATE:RESULT=)(BLOCK|PASS|SKIP)' | tail -1 || echo "SKIP")
|
|
@@ -872,7 +880,7 @@ else
|
|
|
872
880
|
# --label is repeatable (not comma-joined) per the /issue programmatic contract.
|
|
873
881
|
# ${MILESTONE_FLAG} carries the Phase 7D derivation through — empty string is
|
|
874
882
|
# a no-op arg when the reviewed branch has no milestone (plain staging→main).
|
|
875
|
-
ISSUE_SKILL_OUTPUT=$(Skill(skill="issue", args="--title \"$STAGING_FINDING_TITLE\" --body-file \"$STAGING_FINDING_BODY_FILE\" --label review-finding --label needs-validation --label staging-review --label \"$STAGING_FINDING_PRIORITY\" ${MILESTONE_FLAG}"))
|
|
883
|
+
ISSUE_SKILL_OUTPUT=$(Skill(skill="{FORGE_SKILL_PREFIX}issue", args="--title \"$STAGING_FINDING_TITLE\" --body-file \"$STAGING_FINDING_BODY_FILE\" --label review-finding --label needs-validation --label staging-review --label \"$STAGING_FINDING_PRIORITY\" ${MILESTONE_FLAG}"))
|
|
876
884
|
# /issue re-reads the created issue and hard-fails unless this exact marker is present.
|
|
877
885
|
rm -f "$STAGING_FINDING_BODY_FILE"
|
|
878
886
|
|