forgedock 1.1.8 → 1.1.9
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 +10 -33
- package/bin/cinema.mjs +23 -5
- package/bin/engine-cli.mjs +137 -11
- package/bin/env-detect.mjs +249 -0
- package/bin/forge-utils.mjs +364 -4
- package/bin/forgedock.mjs +1064 -135
- package/bin/hooks/session-start.mjs +68 -3
- package/bin/init-detect.mjs +101 -12
- package/bin/journey.mjs +917 -16
- package/bin/recall.mjs +24 -7
- package/bin/registry.mjs +91 -3
- package/bin/runner.mjs +38 -2
- package/bin/tui.mjs +49 -3
- package/commands/adopt.md +1 -1
- package/commands/analytics.md +8 -8
- package/commands/audit.md +1 -1
- package/commands/autopilot.md +1 -1
- package/commands/ci-audit.md +1 -1
- package/commands/cleanup.md +87 -52
- package/commands/compat-audit.md +1 -1
- package/commands/deploy-info.md +1 -1
- package/commands/deploy-pr.md +1 -1
- package/commands/diagnose.md +1 -1
- package/commands/explain.md +1 -1
- package/commands/fix-ci.md +1 -1
- package/commands/forgedock-init.md +1 -1
- package/commands/incident-response.md +3 -3
- package/commands/issue.md +1 -1
- package/commands/optimize.md +1 -1
- package/commands/orchestrate/config.md +13 -3
- package/commands/orchestrate/phase-1-resolve.md +1 -1
- package/commands/orchestrate/phase-2-triage.md +1 -1
- package/commands/orchestrate/phase-2.5-synthesis.md +1 -1
- package/commands/orchestrate/phase-3-dependency.md +128 -20
- package/commands/orchestrate/phase-4-execution.md +598 -43
- package/commands/orchestrate/phase-5-cleanup.md +1 -1
- package/commands/orchestrate/phase-6-report.md +51 -3
- package/commands/orchestrate/safety.md +2 -2
- package/commands/qa-sweep.md +1 -1
- package/commands/quality-gate.md +1 -1
- package/commands/recover-orphans.md +5 -5
- package/commands/replay.md +1 -1
- package/commands/review-pr-agents/api.md +2 -2
- package/commands/review-pr-agents/auth.md +2 -2
- package/commands/review-pr-agents/billing.md +2 -2
- package/commands/review-pr-agents/concurrency.md +2 -2
- package/commands/review-pr-agents/database.md +2 -2
- package/commands/review-pr-agents/frontend.md +2 -2
- package/commands/review-pr-agents/infra.md +2 -2
- package/commands/review-pr-agents/protocols.md +1 -1
- package/commands/review-pr-agents/scraper.md +2 -2
- package/commands/review-pr-agents/security.md +2 -2
- package/commands/review-pr-staging.md +41 -3
- package/commands/review-pr.md +71 -6
- package/commands/rollback.md +1 -1
- package/commands/scope.md +1 -1
- package/commands/security-audit.md +1 -1
- package/commands/test-gate.md +1 -1
- package/commands/validate.md +1 -1
- package/commands/work-on/build/architect.md +1 -1
- package/commands/work-on/build/context.md +9 -6
- package/commands/work-on/build/implement.md +1 -1
- package/commands/work-on/build/validate.md +1 -1
- package/commands/work-on/build.md +31 -6
- package/commands/work-on/close.md +3 -2
- package/commands/work-on/decompose.md +1 -1
- package/commands/work-on/investigate.md +14 -5
- package/commands/work-on/remediate.md +331 -0
- package/commands/work-on/review.md +1 -1
- package/commands/work-on-monolithic.md +1 -1
- package/commands/work-on.md +194 -56
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +1 -1
- package/docs/CONFIG.md +186 -0
- package/docs/site/command-reference.md +31 -1
- package/docs/site/getting-started.md +9 -34
- package/docs/site/index.md +1 -1
- package/docs/spec/label-state-machine.md +1 -1
- package/package.json +2 -1
- package/packages/protocol/src/types.js +1 -1
- package/scripts/check-command-docs-drift.sh +4 -2
- package/scripts/danger-zones.mjs +19 -12
- package/scripts/transition-label.sh +101 -13
- package/scripts/refresh-bot-token.sh +0 -97
|
@@ -335,24 +335,27 @@ if [ -n "${FORGE_COORD_ISSUE:-}" ]; then
|
|
|
335
335
|
# Extract active claims: FORGE:CLAIM comments that are NOT followed by FORGE:CLAIM_RELEASED
|
|
336
336
|
# from the same Holder. We identify active claims by the presence of CLAIM:COMPLETE
|
|
337
337
|
# and the absence of a subsequent CLAIM_RELEASED referencing the same Holder.
|
|
338
|
-
|
|
338
|
+
# Self-exclusion is done via jq select() against the **Holder**: #{NUMBER} field —
|
|
339
|
+
# the actual encoding of the owning issue number — not by grep-matching flattened text
|
|
340
|
+
# (claim comment lines never start with a bare #N, so a line-prefix grep can never match).
|
|
341
|
+
ACTIVE_PEER_CLAIMS=$(echo "$COORD_COMMENTS" | jq -r --arg num "$NUMBER" '.[] |
|
|
339
342
|
select(.body | contains("<!-- CLAIM:COMPLETE -->")) |
|
|
340
343
|
select(.body | contains("<!-- FORGE:CLAIM_RELEASED -->") | not) |
|
|
344
|
+
select((.body | capture("\\*\\*Holder\\*\\*: #(?<n>[0-9]+)").n // "") != $num) |
|
|
341
345
|
"Holder: " + (.body | capture("\\*\\*Holder\\*\\*: (?P<h>[^\n]+)").h // "unknown") +
|
|
342
346
|
"\nFiles: " + (.body | capture("\\*\\*Files\\*\\*: (?P<f>[^\n]+)").f // "none")
|
|
343
|
-
' 2>/dev/null
|
|
344
|
-
# Note: grep -v filters out this agent's own claim (same issue number) — peers only
|
|
347
|
+
' 2>/dev/null || echo "")
|
|
345
348
|
|
|
346
349
|
if [ -n "$ACTIVE_PEER_CLAIMS" ]; then
|
|
347
350
|
echo "Active peer claims found:"
|
|
348
351
|
echo "$ACTIVE_PEER_CLAIMS"
|
|
349
352
|
|
|
350
|
-
# Extract claimed file paths from peer claims
|
|
351
|
-
PEER_CLAIMED_FILES=$(echo "$COORD_COMMENTS" | jq -r '.[] |
|
|
353
|
+
# Extract claimed file paths from peer claims (self already excluded above via jq select)
|
|
354
|
+
PEER_CLAIMED_FILES=$(echo "$COORD_COMMENTS" | jq -r --arg num "$NUMBER" '.[] |
|
|
352
355
|
select(.body | contains("<!-- CLAIM:COMPLETE -->")) |
|
|
353
356
|
select(.body | contains("<!-- FORGE:CLAIM_RELEASED -->") | not) |
|
|
357
|
+
select((.body | capture("\\*\\*Holder\\*\\*: #(?<n>[0-9]+)").n // "") != $num) |
|
|
354
358
|
.body' 2>/dev/null \
|
|
355
|
-
| grep -v "^#${NUMBER}" \
|
|
356
359
|
| awk '/\*\*Files\*\*:/{found=1; next} /\*\*Interfaces\*\*:/{found=0} found{print}' \
|
|
357
360
|
| grep -oP '[a-zA-Z0-9._/-]+\.(py|tsx?|jsx?|sql|json|ya?ml|md|mjs|sh)' \
|
|
358
361
|
| sort -u || echo "")
|
|
@@ -12,7 +12,7 @@ argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--worktree P
|
|
|
12
12
|
**Invoked by**: `work-on.md` Step 3F, after worktree is created and context is gathered.
|
|
13
13
|
**Output**: Write code, commit(s), post `<!-- FORGE:BUILDER -->` comment, return result to caller.
|
|
14
14
|
|
|
15
|
-
**Agent model policy**: `model: "
|
|
15
|
+
**Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
|
|
16
16
|
**NEVER use plan mode (EnterPlanMode).**
|
|
17
17
|
|
|
18
18
|
<!-- FORGE:SPEC_LOADED — work-on/build/implement.md loaded and active. Agent is bound by this spec. -->
|
|
@@ -12,7 +12,7 @@ argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--worktree P
|
|
|
12
12
|
**Invoked by**: `work-on.md` Step 3F.5, after `implement.md` has written and staged code (not committed).
|
|
13
13
|
**Output**: Return `GATE_PASSED: true/false` to caller. On failure after max iterations, post comment and set `needs-human`.
|
|
14
14
|
|
|
15
|
-
**Agent model policy**: `model: "
|
|
15
|
+
**Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
|
|
16
16
|
**NEVER use plan mode (EnterPlanMode).**
|
|
17
17
|
|
|
18
18
|
<!-- FORGE:SPEC_LOADED — work-on/build/validate.md loaded and active. Agent is bound by this spec. -->
|
|
@@ -12,7 +12,7 @@ argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--base PR_BA
|
|
|
12
12
|
**Invoked by**: `work-on.md` Phase 3 — entered when the issue carries label `workflow:ready-to-build` or `workflow:building` (see Universal Phase Dispatcher in work-on.md).
|
|
13
13
|
**Output**: Create worktree, post contract, run build phases, return result to work-on.md.
|
|
14
14
|
|
|
15
|
-
**Agent model policy**: `model: "
|
|
15
|
+
**Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154. This file's mechanical bits (3B classification, 3D label transitions) stay at this tier because they're interleaved with the reasoning-heavy build steps (3C.5/3C.6/3F) in the same `Skill()` invocation — see `work-on.md` section "Model and Effort Tiering — What Actually Applies". <!-- Added: forge#1827 -->
|
|
16
16
|
**NEVER use plan mode (EnterPlanMode).**
|
|
17
17
|
|
|
18
18
|
**CRITICAL: You MUST execute ALL phases B0–B6 in order. Phases B3 (context) and B4 (architect) are skipped ONLY when COMPLEXITY_BAND: TRIVIAL (read from FORGE:FAST_PATH comment in Phase B0). For STANDARD and COMPLEX tasks they are NOT optional — skipping them degrades build quality.**
|
|
@@ -373,17 +373,42 @@ The acceptance spec contains only a skip sentinel (\`type=skipped\`). No automat
|
|
|
373
373
|
<!-- FORGE:ACCEPTANCE_GATE:PASSED -->"
|
|
374
374
|
```
|
|
375
375
|
|
|
376
|
-
**Otherwise — execute each check**:
|
|
376
|
+
**Otherwise — execute each check**: <!-- Added: forge#1829 -->
|
|
377
377
|
|
|
378
378
|
```bash
|
|
379
379
|
GATE_PASS=true
|
|
380
380
|
FAILED_CHECKS=""
|
|
381
381
|
|
|
382
382
|
while IFS= read -r check_line; do
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
383
|
+
# Fields are extracted by anchoring each sed pattern to `^ACCEPTANCE_CHECK:` and walking
|
|
384
|
+
# through the fixed field order from investigate.md (id= type= target="..." matcher="..."
|
|
385
|
+
# description=<free text to EOL>) instead of truncating the line before extraction. This
|
|
386
|
+
# is deliberate: description= is unconstrained free text and can legitimately contain
|
|
387
|
+
# key="value"-shaped substrings (e.g. `description=works when target="prod"`), while
|
|
388
|
+
# target=/matcher= are themselves free-form shell commands/regexes and can legitimately
|
|
389
|
+
# contain the literal substring "description=" (e.g. `target="grep -c description= file"`).
|
|
390
|
+
# A prior fix truncated the line at the first description= to keep free text from being
|
|
391
|
+
# mistaken for a real field — but that truncation then broke whenever a *real* target=/
|
|
392
|
+
# matcher= value contained "description=" text, cutting mid-quote. Anchoring from the start
|
|
393
|
+
# of the line through each preceding field in order avoids truncation entirely: every
|
|
394
|
+
# anchored pattern matches only the one fixed position where that field can occur, so
|
|
395
|
+
# neither direction of collision (fake fields inside description=, or description=-like
|
|
396
|
+
# text inside target=/matcher=) can hijack extraction. Falls back to the full line
|
|
397
|
+
# unchanged if a field is absent (no-op, safe for malformed lines).
|
|
398
|
+
ID=$(echo "$check_line" | sed -n 's/^ACCEPTANCE_CHECK: id=\([^ ]*\) type=.*/\1/p')
|
|
399
|
+
TYPE=$(echo "$check_line" | sed -n 's/^ACCEPTANCE_CHECK: id=[^ ]* type=\([^ ]*\) target=.*/\1/p')
|
|
400
|
+
# target=/matcher= are quoted (target="..." matcher="...") per the investigate.md wire format —
|
|
401
|
+
# quoting is required so multi-word/piped shell-command values (e.g. `target="grep -qE '...' file"`)
|
|
402
|
+
# survive extraction instead of being truncated at the first space. The quote-bounded pattern
|
|
403
|
+
# (`"\([^"]*\)"`) captures everything up to the next literal quote verbatim, including a literal
|
|
404
|
+
# "description=" substring inside the value. Fall back to the legacy unquoted [^ ]* extraction
|
|
405
|
+
# only for older ACCEPTANCE_CHECK comments emitted before this fix (still correct for
|
|
406
|
+
# single-token exists/contains targets; multi-word legacy targets remain truncated until the
|
|
407
|
+
# issue's investigation is re-run to emit the quoted format).
|
|
408
|
+
TARGET=$(echo "$check_line" | sed -n 's/^ACCEPTANCE_CHECK: id=[^ ]* type=[^ ]* target="\([^"]*\)".*/\1/p')
|
|
409
|
+
[ -z "$TARGET" ] && TARGET=$(echo "$check_line" | sed -n 's/^ACCEPTANCE_CHECK: id=[^ ]* type=[^ ]* target=\([^ ]*\).*/\1/p')
|
|
410
|
+
MATCHER=$(echo "$check_line" | sed -n 's/^ACCEPTANCE_CHECK: id=[^ ]* type=[^ ]* target="[^"]*" matcher="\([^"]*\)".*/\1/p')
|
|
411
|
+
[ -z "$MATCHER" ] && MATCHER=$(echo "$check_line" | sed -n 's/^ACCEPTANCE_CHECK: id=[^ ]* type=[^ ]* target=[^ ]* matcher=\([^ ]*\).*/\1/p')
|
|
387
412
|
DESC=$(echo "$check_line" | sed -n 's/.*description=\(.*\)/\1/p')
|
|
388
413
|
|
|
389
414
|
[ "$TYPE" = "skipped" ] && continue
|
|
@@ -12,7 +12,7 @@ argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--pr PR_NUMB
|
|
|
12
12
|
**Invoked by**: `work-on.md` Phase 6–7, after `review.md` returns `REVIEW_RESULT: status: COMPLETE`.
|
|
13
13
|
**Output**: Update project board, close issue, update parent tracker, post trajectory log. Return final summary.
|
|
14
14
|
|
|
15
|
-
**Agent model policy**: `
|
|
15
|
+
**Agent model policy**: `effort: low` (mechanical tier — label transitions, annotation posting, board updates; this file is mechanical end-to-end, so a low effort level is safe here). Fallback: `model: "sonnet"` if rate-limited. Feature gate: pass `effort` only on Claude Code >= 2.1.154. **Note**: this file is dispatched via `Skill("work-on:close", ...)`, which does not support a `model` override — see `work-on.md` section "Model and Effort Tiering — What Actually Applies" for why a `model: "haiku"` claim here would not take effect. <!-- Corrected: forge#1827 -->
|
|
16
16
|
**NEVER use plan mode (EnterPlanMode).**
|
|
17
17
|
|
|
18
18
|
<!-- FORGE:SPEC_LOADED — work-on/close.md loaded and active. Agent is bound by this spec. -->
|
|
@@ -476,9 +476,10 @@ gh issue edit {NUMBER} {GH_FLAG} \
|
|
|
476
476
|
|
|
477
477
|
**Skip if**: Issue body does NOT contain a parent issue reference (e.g. `Part of #NNN`) or the issue has no parent in its milestone tracker.
|
|
478
478
|
|
|
479
|
-
Detect parent reference:
|
|
479
|
+
Detect parent reference. Markdown emphasis markers (`**bold**`, `__bold__`, `*italic*`) are stripped before matching, since sub-issue bodies commonly render the label as `**Parent**: #NNN` and the bare label alternation below would otherwise fail to match past the emphasis characters:
|
|
480
480
|
```bash
|
|
481
481
|
PARENT_REF=$(gh issue view {NUMBER} {GH_FLAG} --json body --jq '.body' \
|
|
482
|
+
| sed -E 's/[*_]+//g' \
|
|
482
483
|
| grep -oP '(?i)(part of|spawned from|sub-issue of|parent issue[:]?|parent[:])\s*#\K\d+' \
|
|
483
484
|
| head -1)
|
|
484
485
|
```
|
|
@@ -12,7 +12,7 @@ argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG]
|
|
|
12
12
|
**Invoked by**: `work-on.md` routing loop, when `INVESTIGATE_RESULT.decompose = YES`.
|
|
13
13
|
**Output**: Create sub-issues, update parent tracker, post `<!-- FORGE:DECOMPOSED -->` comment, set labels. STOP — each sub-issue runs its own /work-on.
|
|
14
14
|
|
|
15
|
-
**Agent model policy**: `model: "
|
|
15
|
+
**Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154. This file's mechanical bits (label transitions, sub-issue creation) stay at this tier because they're interleaved with the reasoning-heavy sub-issue design steps in the same `Skill()` invocation — see `work-on.md` section "Model and Effort Tiering — What Actually Applies". <!-- Added: forge#1827 -->
|
|
16
16
|
**NEVER use plan mode (EnterPlanMode).**
|
|
17
17
|
|
|
18
18
|
---
|
|
@@ -11,7 +11,7 @@ argument-hint: [issue number] [--repo {owner}/{repo}] [--gh-flag "-R {owner}/{re
|
|
|
11
11
|
|
|
12
12
|
Standalone investigation phase for the work-on pipeline. Validates whether an issue is real, determines root cause, posts a structured FORGE:INVESTIGATOR comment to GitHub, and updates workflow labels.
|
|
13
13
|
|
|
14
|
-
**Agent model policy**: `model: "
|
|
14
|
+
**Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154. This file's mechanical bits (1A label set, `FORGE:CHECKPOINT` writes) stay at this tier because they're interleaved with the reasoning-heavy investigation steps in the same `Skill()` invocation — see `work-on.md` section "Model and Effort Tiering — What Actually Applies". <!-- Added: forge#1827 -->
|
|
15
15
|
**NEVER use plan mode (EnterPlanMode).**
|
|
16
16
|
|
|
17
17
|
<!-- FORGE:SPEC_LOADED — work-on/investigate.md loaded and active. Agent is bound by this spec. -->
|
|
@@ -425,12 +425,19 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:INVESTIGATOR -->
|
|
|
425
425
|
**{YES|NO}** — {reason}
|
|
426
426
|
{if YES: proposed sub-issues with titles and dependencies}
|
|
427
427
|
|
|
428
|
-
### Acceptance Spec
|
|
428
|
+
### Acceptance Spec <!-- Added: forge#1829 -->
|
|
429
429
|
{For each item in the issue's ## Acceptance Criteria section, emit one machine-checkable check line using the format below. If the issue has no Acceptance Criteria section, derive checks from the Recommendation above. Each check MUST be specific, observable, and testable — not vague prose. Checks are consumed by build/validate Phase B6.5 as the merge gate.}
|
|
430
430
|
|
|
431
|
+
**Quoting (MANDATORY)**: `target=` and `matcher=` MUST always be wrapped in double quotes — `target="..."` / `matcher="..."` — even when the value is a single token (e.g. a plain file path). The downstream Phase B6.5 parser only extracts quoted values; an unquoted `target=`/`matcher=` will silently truncate at the first space and cause a false-negative gate failure for any multi-word value (shell commands with flags/arguments/pipes are almost always multi-word). Neither `target` nor `matcher` may contain a literal `"` character — use single quotes for any embedded string/regex literal inside the value, as shown below. `id=` and `type=` are always single tokens and are never quoted. `description=` is always the last field on the line and is captured to end-of-line — it does not need quoting.
|
|
432
|
+
|
|
431
433
|
```
|
|
432
|
-
ACCEPTANCE_CHECK: id={ac-1} type={exists|contains|command|behavior} target={file_path|command|url} matcher={string|exit_0|regex} description={one-line human description}
|
|
433
|
-
ACCEPTANCE_CHECK: id={ac-2} type={exists|contains|command|behavior} target={file_path|command|url} matcher={string|exit_0|regex} description={one-line human description}
|
|
434
|
+
ACCEPTANCE_CHECK: id={ac-1} type={exists|contains|command|behavior} target="{file_path|command|url}" matcher="{string|exit_0|regex}" description={one-line human description}
|
|
435
|
+
ACCEPTANCE_CHECK: id={ac-2} type={exists|contains|command|behavior} target="{file_path|command|url}" matcher="{string|exit_0|regex}" description={one-line human description}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Example with a multi-word shell command target (the case that previously broke — note the embedded single quotes around the regex, and the double quotes wrapping the whole target):
|
|
439
|
+
```
|
|
440
|
+
ACCEPTANCE_CHECK: id=ac-4 type=command target="grep -qE '(>= ?2|2\+)' commands/orchestrate/phase-1-resolve.md" matcher="exit_0" description=Fan-out cap is documented as >=2 or 2+
|
|
434
441
|
```
|
|
435
442
|
|
|
436
443
|
**Check types**:
|
|
@@ -439,7 +446,9 @@ ACCEPTANCE_CHECK: id={ac-2} type={exists|contains|command|behavior} target={file
|
|
|
439
446
|
- `command` — run a shell command and assert exit 0 (`target` = shell command, `matcher` = `exit_0`)
|
|
440
447
|
- `behavior` — assert a runtime/observable behavior via shell command (`target` = shell command, `matcher` = expected output string or regex)
|
|
441
448
|
|
|
442
|
-
**
|
|
449
|
+
**Self-defeating pipe guideline**: do NOT chain a `-q`/`--quiet` command into a downstream pipe consumer (e.g. `grep -q ... | grep ...`). A `-q` flag suppresses all stdout, so the next command in the pipe always receives empty input and the check can never pass regardless of the actual file content. If a check needs to verify two conditions against the same output, sequence them instead — e.g. `grep -qE 'first' file && grep -qE 'second' file` — or capture the output once and grep the captured variable.
|
|
450
|
+
|
|
451
|
+
**Skipping**: if the issue has no verifiable acceptance criteria and none can be derived from the recommendation, emit a single sentinel: `ACCEPTANCE_CHECK: id=ac-skip type=skipped target="none" matcher="none" description=No machine-checkable criteria available — human review required`
|
|
443
452
|
${ANNOTATION_LINK_FOOTER}
|
|
444
453
|
<!-- INVESTIGATION:COMPLETE -->"
|
|
445
454
|
```
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Remediate subcommand — checkout a needs-human PR, fix review findings, re-review, and re-gate with a FORGE:REMEDIATION paper trail
|
|
3
|
+
argument-hint: [PR number] [--issue N] [--repo GH_REPO] [--gh-flag GH_FLAG] [--base PR_BASE]
|
|
4
|
+
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
7
|
+
|
|
8
|
+
# work-on/remediate — Remediation Subcommand
|
|
9
|
+
|
|
10
|
+
**Input**: $ARGUMENTS
|
|
11
|
+
|
|
12
|
+
**Invoked by**:
|
|
13
|
+
- `work-on.md` Phase 0A, standalone: `/work-on <pr> --remediate` (see forge#1813).
|
|
14
|
+
- `commands/orchestrate/phase-4-execution.md` item 6.4, auto-dispatched against a `needs-human`-gated predecessor's own open PR.
|
|
15
|
+
|
|
16
|
+
**Output**: Checkout the PR's existing branch → classify the block reason (fixable vs. policy escalation) → apply fixes → quality-gate → commit/push → re-invoke `/review-pr --auto-merge` → compute the #1809 Q1 auto-land bar → merge-if-verified or hold at `workflow:awaiting-merge` → emit a `FORGE:REMEDIATION` paper trail. Return result to caller.
|
|
17
|
+
|
|
18
|
+
**Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`.
|
|
19
|
+
**NEVER use plan mode (EnterPlanMode).**
|
|
20
|
+
|
|
21
|
+
**Scope note**: This mode owns exactly one gap — re-driving a `needs-human` PR's own remediation. It does NOT implement the `needs-human` sub-label taxonomy (#1815's scope) and it does NOT edit `review-pr.md`'s Phase 8 guard (forge#1810) — that guard's existing safe-default (`workflow:awaiting-merge` on any clean re-review of a previously-escalated PR) is reused as-is; this file only adds a bar-check *after* that guard has already fired.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Inputs
|
|
26
|
+
|
|
27
|
+
Parse from $ARGUMENTS:
|
|
28
|
+
- `{PR_NUMBER}` — PR number to remediate (required, first positional arg). This is the `needs-human`-gated PR itself, NOT the linked issue number.
|
|
29
|
+
- `--issue {ISSUE_NUMBER}` — linked issue number (optional). If absent, resolved in Phase M0 from the PR body's `Closes #N` reference.
|
|
30
|
+
- `--repo {GH_REPO}` — GitHub repo (resolved from `forge.yaml → project` if omitted)
|
|
31
|
+
- `--gh-flag {GH_FLAG}` — gh CLI repo flag
|
|
32
|
+
- `--base {PR_BASE}` — PR target branch (optional; resolved from the PR's `baseRefName` if omitted)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Phase M0: Load State & Guard Rails (MANDATORY)
|
|
37
|
+
|
|
38
|
+
Re-read current state before doing anything:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
PR_STATE=$(gh pr view {PR_NUMBER} {GH_FLAG} --json state,headRefName,baseRefName,body,mergeable,mergeStateStatus,url)
|
|
42
|
+
PR_OPEN_STATE=$(echo "$PR_STATE" | jq -r '.state')
|
|
43
|
+
HEAD_BRANCH=$(echo "$PR_STATE" | jq -r '.headRefName')
|
|
44
|
+
PR_BASE="${PR_BASE:-$(echo "$PR_STATE" | jq -r '.baseRefName')}"
|
|
45
|
+
PR_BODY=$(echo "$PR_STATE" | jq -r '.body')
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**PR state guard**:
|
|
49
|
+
- `PR_OPEN_STATE = MERGED` → EXIT `REMEDIATE_RESULT: status: ALREADY_DONE` (nothing to remediate — already landed).
|
|
50
|
+
- `PR_OPEN_STATE = CLOSED` (not merged) → EXIT `REMEDIATE_RESULT: status: BLOCKED`, blocker: "PR #{PR_NUMBER} is closed, not merged — nothing to remediate."
|
|
51
|
+
|
|
52
|
+
**Resolve the linked issue** (`--issue` flag takes precedence; else parse from the PR body — anchored, matching the `"Closes #N" in:body` precedent from forge#1634/#1646, never a bare-number scan):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
ISSUE_NUMBER="${ISSUE_NUMBER:-$(echo "$PR_BODY" | grep -oP '(?i)\bCloses #\K\d+' | head -1)}"
|
|
56
|
+
if [ -z "$ISSUE_NUMBER" ]; then
|
|
57
|
+
echo "BLOCKED: cannot resolve linked issue — pass --issue explicitly"
|
|
58
|
+
# EXIT REMEDIATE_RESULT: status: BLOCKED, blocker: "cannot resolve linked issue — pass --issue explicitly"
|
|
59
|
+
fi
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Load the linked issue and validate it is a genuine remediation target**:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
ISSUE_STATE=$(gh issue view {ISSUE_NUMBER} {GH_FLAG} --json labels,state,body,milestone)
|
|
66
|
+
ISSUE_LABELS=$(echo "$ISSUE_STATE" | jq -r '[.labels[].name] | join(",")')
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- If `needs-human` is NOT among `ISSUE_LABELS` → EXIT `REMEDIATE_RESULT: status: BLOCKED`, blocker: "issue #{ISSUE_NUMBER} is not `needs-human` — remediation mode only targets `needs-human`-gated PRs; use the normal `/work-on {ISSUE_NUMBER}` resume path instead." This keeps blast radius scoped to exactly the gap this mode fills — it is not a general-purpose re-review trigger.
|
|
70
|
+
|
|
71
|
+
**Idempotency / resume check** — the paper trail lives on **both** the PR (primary — checked by the orchestrator's item 6.4 dispatch guard) and the linked issue (mirror — keeps `/work-on`'s standard FORGE-annotation trajectory and resume logic consistent with every other phase):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
PR_REMEDIATION_COMMENT=$(gh api repos/{GH_REPO}/issues/{PR_NUMBER}/comments \
|
|
75
|
+
--jq '[.[] | select(.body | contains("FORGE:REMEDIATION"))] | last')
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- If a comment is found AND its body contains `FORGE:REMEDIATION:COMPLETE` → EXIT `REMEDIATE_RESULT: status: ALREADY_DONE`. **Single-attempt semantics (AC5)**: once a `FORGE:REMEDIATION:COMPLETE` marker exists for this PR, do NOT re-attempt fixes on a subsequent invocation, regardless of the prior verdict — this is what prevents an infinite remediation retry loop on a genuinely-blocked PR.
|
|
79
|
+
- If a comment is found WITHOUT `:COMPLETE` → a prior attempt was interrupted mid-flight (same failure mode as the investigation phase's partial-comment case). Delete the partial comment(s) on both the PR and the issue, then continue below as a fresh attempt:
|
|
80
|
+
```bash
|
|
81
|
+
gh api repos/{GH_REPO}/issues/comments/{PARTIAL_PR_COMMENT_ID} -X DELETE 2>/dev/null || true
|
|
82
|
+
gh api repos/{GH_REPO}/issues/comments/{PARTIAL_ISSUE_COMMENT_ID} -X DELETE 2>/dev/null || true
|
|
83
|
+
```
|
|
84
|
+
- If no comment is found → fresh attempt, continue below.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Phase M1: Load Prior Findings & Classify the Block Reason
|
|
89
|
+
|
|
90
|
+
Gather everything that caused (or is still causing) `needs-human`:
|
|
91
|
+
|
|
92
|
+
**M1a — Open review-finding issues spawned from this PR** (same title-match precedent as `review-pr.md` Phase 8B/9A):
|
|
93
|
+
```bash
|
|
94
|
+
FINDINGS=$(gh issue list {GH_FLAG} --state open --label "review-finding" --limit 100 \
|
|
95
|
+
--json number,title,body \
|
|
96
|
+
--jq "[.[] | select(.title | test(\"PR #{PR_NUMBER}\"))]")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**M1b — PR review verdicts and merge-block reasons** (Phase 8 of `review-pr.md` records the exact block reason on the linked issue when it aborts auto-merge — read that trail rather than re-deriving it):
|
|
100
|
+
```bash
|
|
101
|
+
BLOCK_COMMENTS=$(gh api repos/{GH_REPO}/issues/{ISSUE_NUMBER}/comments \
|
|
102
|
+
--jq '[.[] | select(.body | test("Auto-merge aborted|not mergeable|Pre-Push Ancestry Guard Failed|Push Failed|Quality Gate Failed"; "i"))] | last')
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Classify into FIXABLE vs. UNFIXABLE**:
|
|
106
|
+
- **FIXABLE** — open `review-finding` issues (CONFIRMED/LIKELY code defects), a `VERDICT=CHANGES REQUESTED` block with concrete findings attached, a mergeability guard failure (`CONFLICTING`/`DIRTY`/`BLOCKED` — resolvable by rebasing onto `{PR_BASE}`), or a quality-gate/build failure.
|
|
107
|
+
- **UNFIXABLE (policy escalation)** — `HAS_PURPOSE_REGRESSION=true` (the PR's behavior diverges from the issue's intent — a judgment call, not a code defect), `CALIBRATION_NEEDS_HUMAN=true` (statistical trust threshold), or `TRUST_NEEDS_HUMAN=true` (provenance `NOVEL_NEEDS_HUMAN` tier, insufficient prior data — a policy gate, not a bug). None of these are mechanically "fixable" by re-editing code.
|
|
108
|
+
|
|
109
|
+
**If the block reason classifies as UNFIXABLE** (and no FIXABLE item accompanies it): do NOT attempt any fix. Skip directly to Phase M8 with verdict `UNFIXABLE`, re-affirm `needs-human` (it should already be present), and return `REMEDIATE_RESULT: status: UNFIXABLE`. This satisfies AC5 — "genuinely-blocked PRs still terminate at `needs-human`."
|
|
110
|
+
|
|
111
|
+
**If at least one FIXABLE item exists**: proceed to Phase M2.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Phase M2: Checkout the PR's Existing Branch
|
|
116
|
+
|
|
117
|
+
Remediation always fixes forward on top of the PR's existing head commit — never rebase onto a different base and never force-push over the PR's history unless a fix genuinely requires it (e.g. resolving a merge conflict per the mergeability guard case, in which case use `git rebase`/`git merge` onto `origin/{PR_BASE}` exactly as the branch's own commit history would, then `--force-with-lease`).
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
cd {REPO_PATH}
|
|
121
|
+
git fetch origin
|
|
122
|
+
WORKTREE_PATH="{WORKTREE_BASE}/remediate-{HEAD_BRANCH_SLUG}-{PR_NUMBER}"
|
|
123
|
+
if [ -d "{WORKTREE_PATH}" ]; then
|
|
124
|
+
git -C "{WORKTREE_PATH}" fetch origin
|
|
125
|
+
git -C "{WORKTREE_PATH}" checkout {HEAD_BRANCH}
|
|
126
|
+
git -C "{WORKTREE_PATH}" reset --hard "origin/{HEAD_BRANCH}"
|
|
127
|
+
else
|
|
128
|
+
git worktree add "{WORKTREE_PATH}" {HEAD_BRANCH} "origin/{HEAD_BRANCH}"
|
|
129
|
+
fi
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
If the worktree/branch checkout fails for any reason (branch deleted, force-pushed out from under us, etc.): post a comment, add `needs-human`, EXIT `REMEDIATE_RESULT: status: BLOCKED`.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Phase M3: Apply Fixes
|
|
137
|
+
|
|
138
|
+
For each FIXABLE item from Phase M1: read the affected file(s) in `{WORKTREE_PATH}` before editing (never assume current state), apply the fix. Follow the same implementation discipline as `work-on.md` Phase 3F (cross-lane import guard, library-callback verification, deliverable-type consistency, no unrequested scope) — this file does not restate those rules, it inherits them.
|
|
139
|
+
|
|
140
|
+
**If the block reason was a mergeability conflict** (`CONFLICTING`/`DIRTY`/`BLOCKED`): resolve it by rebasing `{HEAD_BRANCH}` onto `origin/{PR_BASE}` (or merging `{PR_BASE}` in, whichever preserves a clean, reviewable history) — resolve conflicts manually, do not blindly take "ours"/"theirs".
|
|
141
|
+
|
|
142
|
+
**Quality Gate** (same loop as Phase 3G, max 3 iterations):
|
|
143
|
+
```
|
|
144
|
+
iteration = 0
|
|
145
|
+
while iteration < 3:
|
|
146
|
+
iteration += 1
|
|
147
|
+
Skill("quality-gate", args="{CHANGED_FILES} --worktree {WORKTREE_PATH}")
|
|
148
|
+
if result == "QUALITY GATE: PASS": GATE_PASSED=true; break
|
|
149
|
+
else: fix each HIGH/MEDIUM finding, re-stage
|
|
150
|
+
```
|
|
151
|
+
If still failing after 3 iterations: post a comment, re-affirm `needs-human`, EXIT `REMEDIATE_RESULT: status: BLOCKED`. Do not proceed to re-review with an unresolved gate failure — that would just re-escalate one phase later with a worse paper trail.
|
|
152
|
+
|
|
153
|
+
**Format/verify**: run the project's configured `verification.commands` (same as Phase 3H) before committing.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Phase M4: Commit, Push, and Close Addressed Findings
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
cd {WORKTREE_PATH}
|
|
161
|
+
git add -u
|
|
162
|
+
git commit -s -m "fix(remediate): {description} (#{ISSUE_NUMBER})"
|
|
163
|
+
git push origin {HEAD_BRANCH}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
If push fails, retry with `--force-with-lease` (expected when M3 rebased to resolve a conflict). If it still fails: post a comment, add `needs-human`, EXIT `REMEDIATE_RESULT: status: BLOCKED`.
|
|
167
|
+
|
|
168
|
+
**Close each addressed review-finding issue directly** (this remediation fixes findings in-place on the existing PR, rather than each finding spawning its own downstream `/work-on` pipeline — leaving them open would have a future run rediscover already-fixed code). Track the closed numbers in `ADDRESSED_FINDING_NUMBERS[]` — Phase M8 reports this array in the final paper trail:
|
|
169
|
+
```bash
|
|
170
|
+
ADDRESSED_FINDING_NUMBERS=()
|
|
171
|
+
for FINDING_NUM in {FIXABLE_FINDING_NUMBERS_FROM_M1}; do
|
|
172
|
+
gh issue close "$FINDING_NUM" {GH_FLAG} \
|
|
173
|
+
--comment "Fixed by remediation of PR #{PR_NUMBER} (commit {COMMIT_SHA}). See #{ISSUE_NUMBER}."
|
|
174
|
+
ADDRESSED_FINDING_NUMBERS+=("$FINDING_NUM")
|
|
175
|
+
done
|
|
176
|
+
```
|
|
177
|
+
Only close findings actually addressed in this commit — leave any FIXABLE-but-deferred or unrelated open findings untouched.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Phase M5: Post Interim FORGE:REMEDIATION Progress (before re-review)
|
|
182
|
+
|
|
183
|
+
Post the same body to **both** `{PR_NUMBER}` and `{ISSUE_NUMBER}` (PR copy is the idempotency source of truth; issue copy keeps the standard trajectory/resume logic consistent):
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
gh pr comment {PR_NUMBER} {GH_FLAG} --body "<!-- FORGE:REMEDIATION -->
|
|
187
|
+
## Remediation In Progress for PR #{PR_NUMBER}
|
|
188
|
+
|
|
189
|
+
**Findings addressed**:
|
|
190
|
+
{bulleted list: finding # — title — one-line fix summary}
|
|
191
|
+
|
|
192
|
+
**Commit**: {COMMIT_SHA}
|
|
193
|
+
**Quality gate**: {iterations} iteration(s), PASS
|
|
194
|
+
|
|
195
|
+
Re-invoking \`/review-pr --auto-merge\` now."
|
|
196
|
+
gh issue comment {ISSUE_NUMBER} {GH_FLAG} --body "<!-- FORGE:REMEDIATION -->
|
|
197
|
+
## Remediation In Progress for PR #{PR_NUMBER}
|
|
198
|
+
|
|
199
|
+
**Findings addressed**:
|
|
200
|
+
{bulleted list: finding # — title — one-line fix summary}
|
|
201
|
+
|
|
202
|
+
**Commit**: {COMMIT_SHA}
|
|
203
|
+
**Quality gate**: {iterations} iteration(s), PASS
|
|
204
|
+
|
|
205
|
+
Re-invoking \`/review-pr --auto-merge\` now."
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Note the marker is `<!-- FORGE:REMEDIATION -->` with **no** `:COMPLETE` suffix yet — per the marker-presence convention (forge#1360/#1357), the absence of `:COMPLETE` correctly signals "in progress" to any concurrent reader, and the M0 resume check above treats this exact state as an interrupted attempt if a session dies before M8.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Phase M6: Re-Invoke /review-pr
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
Skill(skill="review-pr", args="{PR_NUMBER} --auto-merge --issue {ISSUE_NUMBER} --base {PR_BASE} --gh-flag {GH_FLAG}")
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
This re-runs the full review (domain agents → verdict → Phase 8 auto-merge gate). Because `{ISSUE_NUMBER}` still carries `needs-human` at this point, one of two things happens inside `review-pr.md`'s existing, **unedited** Phase 8:
|
|
219
|
+
|
|
220
|
+
- **Re-escalated**: the re-review itself trips a fresh block (`CHANGES REQUESTED`, purpose-regression, calibration, trust, or a still-`CONFLICTING` mergeability check) → `needs-human` remains set, no merge attempted.
|
|
221
|
+
- **Clean re-review**: `VERDICT=APPROVED`-equivalent, mergeable, and the "Previously-escalated re-review guard" (forge#1810) fires — clearing `needs-human` and setting `workflow:awaiting-merge`, *without* auto-merging (that guard's own safe default, left untouched by this file).
|
|
222
|
+
|
|
223
|
+
Extract the re-review verdict for the paper trail (Phase M8 reports this verbatim):
|
|
224
|
+
```bash
|
|
225
|
+
RE_REVIEW_VERDICT=$(gh api repos/{GH_REPO}/issues/{PR_NUMBER}/comments \
|
|
226
|
+
--jq '[.[] | select(.body | test("APPROVED:|CHANGES REQUESTED:"; "i"))] | last | .body // "unknown"' 2>/dev/null | head -c 200)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Phase M7: Compute the #1809 Q1 Auto-Land Bar
|
|
232
|
+
|
|
233
|
+
Re-read the issue's current labels after M6:
|
|
234
|
+
```bash
|
|
235
|
+
POST_REVIEW_LABELS=$(gh issue view {ISSUE_NUMBER} {GH_FLAG} --json labels --jq '[.labels[].name] | join(",")')
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**If `needs-human` is present** (re-escalated case): the bar does not apply — nothing to compute. `RE_GATE_OUTCOME="RE-ESCALATED"`. Skip to Phase M8.
|
|
239
|
+
|
|
240
|
+
**If `workflow:awaiting-merge` is present** (clean re-review case — the only branch where `review-pr.md`'s guard has already safely parked this PR): compute the bar.
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
REVIEW_BODIES=$(gh pr view {PR_NUMBER} {GH_FLAG} --json reviews,comments \
|
|
244
|
+
--jq '[.reviews[].body // ""] + [.comments[].body // ""] | .[]')
|
|
245
|
+
APPROVED_COUNT=$(echo "$REVIEW_BODIES" | grep -cE 'APPROVED:' 2>/dev/null || true); APPROVED_COUNT=${APPROVED_COUNT:-0}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Auto-land bar** (per #1809 Q1) — BOTH conditions required:
|
|
249
|
+
1. `APPROVED_COUNT >= 2` — at least two distinct adversarial `APPROVED:` review comments (same counting convention as `work-on.md` Phase 7A).
|
|
250
|
+
2. `GATE_PASSED = true` from this remediation's own Phase M3 quality-gate loop.
|
|
251
|
+
|
|
252
|
+
**If the bar is met**:
|
|
253
|
+
```bash
|
|
254
|
+
gh pr merge {PR_NUMBER} {GH_FLAG} --merge
|
|
255
|
+
MERGE_STATE=$(gh pr view {PR_NUMBER} {GH_FLAG} --json state --jq '.state')
|
|
256
|
+
if [ "$MERGE_STATE" = "MERGED" ]; then
|
|
257
|
+
RESOLUTION=$(resolve_script 'transition-label')
|
|
258
|
+
TIER="${RESOLUTION%%:*}"; SCRIPT_PATH="${RESOLUTION#*:}"
|
|
259
|
+
case "$TIER" in
|
|
260
|
+
adaptive|universal) bash "$SCRIPT_PATH" {ISSUE_NUMBER} {GH_FLAG} merged ;;
|
|
261
|
+
prose)
|
|
262
|
+
gh issue edit {ISSUE_NUMBER} {GH_FLAG} --add-label "workflow:merged" \
|
|
263
|
+
--remove-label "workflow:awaiting-merge,needs-human,workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
264
|
+
;;
|
|
265
|
+
esac
|
|
266
|
+
RE_GATE_OUTCOME="AUTO-LANDED"
|
|
267
|
+
else
|
|
268
|
+
RE_GATE_OUTCOME="HELD-AWAITING-MERGE"
|
|
269
|
+
# gh pr merge reported success but the PR isn't actually MERGED — leave workflow:awaiting-merge
|
|
270
|
+
# in place (unchanged) and let a human merge manually rather than retrying automatically.
|
|
271
|
+
fi
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**If the bar is NOT met**: leave the issue at `workflow:awaiting-merge` exactly as `review-pr.md`'s guard set it — do NOT attempt a merge. `RE_GATE_OUTCOME="HELD-AWAITING-MERGE"`. Fail-safe direction: any doubt about the bar defaults to holding, matching `review-pr.md`'s own existing default for every other caller.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Phase M8: Finalize FORGE:REMEDIATION Paper Trail
|
|
279
|
+
|
|
280
|
+
Post the completion body to **both** `{PR_NUMBER}` and `{ISSUE_NUMBER}` — this is the single idempotency marker checked by Phase M0 (this file, on future resume) and by the orchestrator's item 6.4 dispatch guard:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
case "$RE_GATE_OUTCOME" in
|
|
284
|
+
AUTO-LANDED) AUTO_LAND_BAR_TEXT="MET (${APPROVED_COUNT:-0} APPROVED: reviews + quality gate pass)"; OUTCOME_DETAIL="to {PR_BASE}" ;;
|
|
285
|
+
HELD-AWAITING-MERGE) AUTO_LAND_BAR_TEXT="NOT MET (${APPROVED_COUNT:-0} APPROVED: reviews)"; OUTCOME_DETAIL="at workflow:awaiting-merge" ;;
|
|
286
|
+
RE-ESCALATED) AUTO_LAND_BAR_TEXT="N/A — re-escalated before the bar was evaluated"; OUTCOME_DETAIL="at needs-human" ;;
|
|
287
|
+
UNFIXABLE) AUTO_LAND_BAR_TEXT="N/A — unfixable (see Phase M1 classification)"; OUTCOME_DETAIL="at needs-human" ;;
|
|
288
|
+
*) AUTO_LAND_BAR_TEXT="N/A"; OUTCOME_DETAIL="" ;;
|
|
289
|
+
esac
|
|
290
|
+
|
|
291
|
+
REMEDIATION_BODY="<!-- FORGE:REMEDIATION -->
|
|
292
|
+
## Remediation Complete for PR #{PR_NUMBER}
|
|
293
|
+
|
|
294
|
+
**Findings addressed**: ${#ADDRESSED_FINDING_NUMBERS[@]} (${ADDRESSED_FINDING_NUMBERS[*]:-none})
|
|
295
|
+
**Re-review verdict**: ${RE_REVIEW_VERDICT:-unknown}
|
|
296
|
+
**Auto-land bar**: ${AUTO_LAND_BAR_TEXT}
|
|
297
|
+
**Re-gate outcome**: ${RE_GATE_OUTCOME} ${OUTCOME_DETAIL}
|
|
298
|
+
|
|
299
|
+
<!-- FORGE:REMEDIATION:COMPLETE -->"
|
|
300
|
+
|
|
301
|
+
gh pr comment {PR_NUMBER} {GH_FLAG} --body "$REMEDIATION_BODY"
|
|
302
|
+
gh issue comment {ISSUE_NUMBER} {GH_FLAG} --body "$REMEDIATION_BODY"
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**If the outcome was `AUTO-LANDED`**: this Skill invocation is itself the caller's terminal delegate (Phase 0A.1 of `work-on.md` already told its own routing loop to STOP after dispatching here) — so `remediate.md` must drive the close phase itself rather than assume some other inline logic will. Invoke the close subcommand directly, the same way `work-on/review.md` does when it hands off from a spawned sub-agent context:
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
Skill("work-on:close", args="{ISSUE_NUMBER} --repo {GH_REPO} --gh-flag {GH_FLAG} --pr {PR_NUMBER} --base {PR_BASE}")
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`work-on:close` handles project board update, final issue body, parent tracker, trajectory log, and worktree cleanup (including the remediation worktree at `{WORKTREE_PATH}`) — do not duplicate any of that here.
|
|
312
|
+
|
|
313
|
+
**If the outcome was `HELD-AWAITING-MERGE`, `RE-ESCALATED`, or `UNFIXABLE`**: leave the worktree in place (a human may need it for manual inspection/merge) and return the structured result below without invoking close. Do not close the issue.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Output
|
|
318
|
+
|
|
319
|
+
Return this structured block to the caller:
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
REMEDIATE_RESULT:
|
|
323
|
+
status: COMPLETE | ALREADY_DONE | UNFIXABLE | BLOCKED
|
|
324
|
+
pr_number: {PR_NUMBER}
|
|
325
|
+
issue_number: {ISSUE_NUMBER}
|
|
326
|
+
re_gate_outcome: AUTO-LANDED | HELD-AWAITING-MERGE | RE-ESCALATED | UNFIXABLE | N/A
|
|
327
|
+
findings_addressed: [{finding_number}, ...]
|
|
328
|
+
blocker: {description if status=BLOCKED}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Caller behavior**: this Skill already drives its own close phase when `re_gate_outcome: AUTO-LANDED` (see Phase M8) — the caller does not need to invoke close itself. For every other `re_gate_outcome`, this result is terminal for the current invocation: the issue is left at `needs-human` or `workflow:awaiting-merge`, both already recognized as terminal states in the Universal Phase Dispatcher (see `work-on.md`). Whether invoked standalone (`/work-on <pr> --remediate`) or via the orchestrator's item 6.4 dispatch, no further action is required from the caller.
|
|
@@ -12,7 +12,7 @@ argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--worktree P
|
|
|
12
12
|
**Invoked by**: `work-on.md` Phase 4–5, after `build/validate.md` returns `GATE_PASSED: true`.
|
|
13
13
|
**Output**: Push branch, create PR, invoke /review-pr --auto-merge, return result to caller.
|
|
14
14
|
|
|
15
|
-
**Agent model policy**: `model: "
|
|
15
|
+
**Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154. This file's mechanical bits (label transitions, `FORGE:CHECKPOINT` writes) stay at this tier because they're interleaved with the review/merge-decision steps in the same `Skill()` invocation — see `work-on.md` section "Model and Effort Tiering — What Actually Applies". <!-- Added: forge#1827 -->
|
|
16
16
|
**NEVER use plan mode (EnterPlanMode).**
|
|
17
17
|
|
|
18
18
|
<!-- FORGE:SPEC_LOADED — work-on/review.md loaded and active. Agent is bound by this spec. -->
|
|
@@ -12,7 +12,7 @@ install: internal
|
|
|
12
12
|
|
|
13
13
|
Orchestrator for the full issue lifecycle: investigate → build → review → merge → close. GitHub issues are the persistent context layer — read existing comments before starting, write structured reports back, use `workflow:*` labels to track state.
|
|
14
14
|
|
|
15
|
-
**Agent model policy**: `model: "
|
|
15
|
+
**Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
|
|
16
16
|
**NEVER use plan mode (EnterPlanMode).**
|
|
17
17
|
|
|
18
18
|
> **Relationship to `/work-on` (canonical path)**: This file is a `[BENCHMARK]` reference variant. It documents the same inline-execution model that `/work-on` Phase 3 and `work-on/build.md` use as their **default** for STANDARD/fast-lane issues — all build sub-phases (context → architect → implement → validate) run inline in the current context window with no `Skill()` sub-agent spawns. The difference is that this file omits the worktree lifecycle, `FORGE:CONTRACT` posting, and the Spawn-Decision exception path for large builds. Use `/work-on` for production runs; use this file for baseline benchmarking or when comparing single-prompt token cost against the modular path. See `work-on/build.md` (Canonical Build Path section) for the reconciled build topology. <!-- Added: forge#1276 -->
|