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.
Files changed (84) hide show
  1. package/README.md +10 -33
  2. package/bin/cinema.mjs +23 -5
  3. package/bin/engine-cli.mjs +137 -11
  4. package/bin/env-detect.mjs +249 -0
  5. package/bin/forge-utils.mjs +364 -4
  6. package/bin/forgedock.mjs +1064 -135
  7. package/bin/hooks/session-start.mjs +68 -3
  8. package/bin/init-detect.mjs +101 -12
  9. package/bin/journey.mjs +917 -16
  10. package/bin/recall.mjs +24 -7
  11. package/bin/registry.mjs +91 -3
  12. package/bin/runner.mjs +38 -2
  13. package/bin/tui.mjs +49 -3
  14. package/commands/adopt.md +1 -1
  15. package/commands/analytics.md +8 -8
  16. package/commands/audit.md +1 -1
  17. package/commands/autopilot.md +1 -1
  18. package/commands/ci-audit.md +1 -1
  19. package/commands/cleanup.md +87 -52
  20. package/commands/compat-audit.md +1 -1
  21. package/commands/deploy-info.md +1 -1
  22. package/commands/deploy-pr.md +1 -1
  23. package/commands/diagnose.md +1 -1
  24. package/commands/explain.md +1 -1
  25. package/commands/fix-ci.md +1 -1
  26. package/commands/forgedock-init.md +1 -1
  27. package/commands/incident-response.md +3 -3
  28. package/commands/issue.md +1 -1
  29. package/commands/optimize.md +1 -1
  30. package/commands/orchestrate/config.md +13 -3
  31. package/commands/orchestrate/phase-1-resolve.md +1 -1
  32. package/commands/orchestrate/phase-2-triage.md +1 -1
  33. package/commands/orchestrate/phase-2.5-synthesis.md +1 -1
  34. package/commands/orchestrate/phase-3-dependency.md +128 -20
  35. package/commands/orchestrate/phase-4-execution.md +598 -43
  36. package/commands/orchestrate/phase-5-cleanup.md +1 -1
  37. package/commands/orchestrate/phase-6-report.md +51 -3
  38. package/commands/orchestrate/safety.md +2 -2
  39. package/commands/qa-sweep.md +1 -1
  40. package/commands/quality-gate.md +1 -1
  41. package/commands/recover-orphans.md +5 -5
  42. package/commands/replay.md +1 -1
  43. package/commands/review-pr-agents/api.md +2 -2
  44. package/commands/review-pr-agents/auth.md +2 -2
  45. package/commands/review-pr-agents/billing.md +2 -2
  46. package/commands/review-pr-agents/concurrency.md +2 -2
  47. package/commands/review-pr-agents/database.md +2 -2
  48. package/commands/review-pr-agents/frontend.md +2 -2
  49. package/commands/review-pr-agents/infra.md +2 -2
  50. package/commands/review-pr-agents/protocols.md +1 -1
  51. package/commands/review-pr-agents/scraper.md +2 -2
  52. package/commands/review-pr-agents/security.md +2 -2
  53. package/commands/review-pr-staging.md +41 -3
  54. package/commands/review-pr.md +71 -6
  55. package/commands/rollback.md +1 -1
  56. package/commands/scope.md +1 -1
  57. package/commands/security-audit.md +1 -1
  58. package/commands/test-gate.md +1 -1
  59. package/commands/validate.md +1 -1
  60. package/commands/work-on/build/architect.md +1 -1
  61. package/commands/work-on/build/context.md +9 -6
  62. package/commands/work-on/build/implement.md +1 -1
  63. package/commands/work-on/build/validate.md +1 -1
  64. package/commands/work-on/build.md +31 -6
  65. package/commands/work-on/close.md +3 -2
  66. package/commands/work-on/decompose.md +1 -1
  67. package/commands/work-on/investigate.md +14 -5
  68. package/commands/work-on/remediate.md +331 -0
  69. package/commands/work-on/review.md +1 -1
  70. package/commands/work-on-monolithic.md +1 -1
  71. package/commands/work-on.md +194 -56
  72. package/docs/CI.md +2 -2
  73. package/docs/CODEX.md +1 -1
  74. package/docs/CONFIG.md +186 -0
  75. package/docs/site/command-reference.md +31 -1
  76. package/docs/site/getting-started.md +9 -34
  77. package/docs/site/index.md +1 -1
  78. package/docs/spec/label-state-machine.md +1 -1
  79. package/package.json +2 -1
  80. package/packages/protocol/src/types.js +1 -1
  81. package/scripts/check-command-docs-drift.sh +4 -2
  82. package/scripts/danger-zones.mjs +19 -12
  83. package/scripts/transition-label.sh +101 -13
  84. 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
- ACTIVE_PEER_CLAIMS=$(echo "$COORD_COMMENTS" | jq -r '.[] |
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 | grep -v "^Holder: #${NUMBER}" || echo "")
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: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
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: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
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: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
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
- ID=$(echo "$check_line" | sed -n 's/.*id=\([^ ]*\).*/\1/p')
384
- TYPE=$(echo "$check_line" | sed -n 's/.*type=\([^ ]*\).*/\1/p')
385
- TARGET=$(echo "$check_line"| sed -n 's/.*target=\([^ ]*\).*/\1/p')
386
- MATCHER=$(echo "$check_line"| sed -n 's/.*matcher=\([^ ]*\).*/\1/p')
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**: `model: "haiku"`, `effort: low` (mechanical tier — label transitions, annotation posting, board updates). Fallback: `model: "sonnet"` if rate-limited. Feature gate: pass `effort` only on Claude Code >= 2.1.154.
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: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
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: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
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
- **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`
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: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
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: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
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 -->