forgedock 1.1.11 → 1.3.0

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 (109) hide show
  1. package/README.md +9 -3
  2. package/bin/cli-spawn-shared.mjs +25 -0
  3. package/bin/engine/phases.mjs +178 -28
  4. package/bin/engine/state.mjs +39 -7
  5. package/bin/engine-cli.mjs +85 -3
  6. package/bin/engine.mjs +71 -8
  7. package/bin/forgedock.mjs +322 -20
  8. package/bin/hooks/pre-tool-use.mjs +345 -10
  9. package/bin/init-enrich-api.mjs +12 -4
  10. package/bin/init-enrich-cli.mjs +328 -0
  11. package/bin/init-enrich.mjs +137 -0
  12. package/bin/journey.mjs +116 -15
  13. package/bin/runner.mjs +653 -10
  14. package/commands/adopt.md +1 -1
  15. package/commands/analytics.md +10 -7
  16. package/commands/audit-agents.md +29 -6
  17. package/commands/audit.md +9 -20
  18. package/commands/autopilot.md +52 -78
  19. package/commands/changelog.md +1 -1
  20. package/commands/ci-audit.md +19 -10
  21. package/commands/cleanup.md +1 -1
  22. package/commands/compat-audit.md +1 -1
  23. package/commands/deploy-info.md +1 -1
  24. package/commands/deploy-pr.md +1 -1
  25. package/commands/design-bench.md +1 -1
  26. package/commands/design-render-critique-loop.md +1 -1
  27. package/commands/diagnose.md +1 -1
  28. package/commands/forge-stats.md +6 -5
  29. package/commands/forgedock-init.md +1 -1
  30. package/commands/geo-audit.md +9 -6
  31. package/commands/incident-response.md +15 -5
  32. package/commands/issue.md +302 -15
  33. package/commands/milestone.md +45 -18
  34. package/commands/optimize.md +1 -1
  35. package/commands/orchestrate/phase-1-resolve.md +20 -5
  36. package/commands/orchestrate/phase-4-execution.md +8 -1
  37. package/commands/orchestrate/phase-5-cleanup.md +43 -2
  38. package/commands/orchestrate/phase-6-report.md +1 -0
  39. package/commands/orchestrate.md +1 -1
  40. package/commands/pipeline-health/phase-4-proposals.md +40 -10
  41. package/commands/pipeline-health/phase-5-report.md +140 -13
  42. package/commands/pipeline-health.md +1 -1
  43. package/commands/pipeline-resume.md +1 -1
  44. package/commands/pipeline-status.md +1 -1
  45. package/commands/qa-sweep.md +27 -4
  46. package/commands/recover-orphans.md +1 -1
  47. package/commands/replay.md +16 -2
  48. package/commands/review-pr-agents/protocols.md +4 -0
  49. package/commands/review-pr-agents/spec-cli.md +179 -0
  50. package/commands/review-pr-agents.md +7 -4
  51. package/commands/review-pr-staging.md +44 -15
  52. package/commands/review-pr.md +122 -28
  53. package/commands/rollback.md +15 -5
  54. package/commands/security-audit.md +14 -11
  55. package/commands/signal-planner.md +32 -12
  56. package/commands/spec-doctor.md +1 -1
  57. package/commands/sync-ecosystem.md +54 -11
  58. package/commands/test-gate.md +49 -11
  59. package/commands/upgrade-deps.md +518 -0
  60. package/commands/validate.md +16 -5
  61. package/commands/work-on/build/architect.md +1 -1
  62. package/commands/work-on/build/context.md +1 -1
  63. package/commands/work-on/build/implement.md +2 -2
  64. package/commands/work-on/build/validate.md +64 -2
  65. package/commands/work-on/build.md +10 -2
  66. package/commands/work-on/close.md +1 -1
  67. package/commands/work-on/decompose.md +35 -25
  68. package/commands/work-on/investigate.md +2 -2
  69. package/commands/work-on/remediate.md +1 -1
  70. package/commands/work-on/review.md +5 -3
  71. package/commands/work-on-monolithic.md +1 -1
  72. package/commands/work-on.md +28 -7
  73. package/docs/AIDER.md +264 -0
  74. package/docs/AUTOPILOT-CRON.md +130 -0
  75. package/docs/CI.md +18 -0
  76. package/docs/CONFIG.md +41 -2
  77. package/docs/CURSOR.md +135 -0
  78. package/docs/OPENCODE.md +312 -0
  79. package/docs/demo.gif +0 -0
  80. package/docs/eval/README.md +4 -4
  81. package/docs/launch/show-hn.md +3 -3
  82. package/docs/site/annotations-explained.md +2 -2
  83. package/docs/site/architecture.md +180 -0
  84. package/docs/site/command-reference.md +38 -0
  85. package/docs/site/configuration.md +243 -0
  86. package/docs/site/for-companies.md +4 -4
  87. package/docs/site/forge-annotation-protocol.md +1 -1
  88. package/docs/site/github-is-the-memory.md +2 -2
  89. package/docs/site/how-it-works.md +1 -1
  90. package/docs/site/public/llms-full.txt +380 -0
  91. package/docs/site/public/llms.txt +35 -0
  92. package/docs/spec/review-protocol.md +6 -0
  93. package/docs/spec-graph-schema.md +3 -3
  94. package/package.json +2 -2
  95. package/packages/protocol/README.md +75 -0
  96. package/packages/protocol/src/card.js +114 -0
  97. package/packages/protocol/src/cli.js +4 -77
  98. package/packages/protocol/src/emit.js +14 -3
  99. package/packages/protocol/src/index.js +2 -1
  100. package/packages/protocol/src/parse.js +17 -8
  101. package/packages/protocol/src/types.js +1 -1
  102. package/packages/protocol/src/validate.js +11 -0
  103. package/scripts/check-command-side-effects.sh +261 -0
  104. package/scripts/check-spec-markers.sh +273 -0
  105. package/scripts/classify-lane.sh +29 -7
  106. package/scripts/forge-run.sh +322 -0
  107. package/templates/cross-repo-trigger.yml +282 -0
  108. package/templates/cursor/forge.mdc +148 -0
  109. package/templates/github-actions/forgedock-autopilot.yml +113 -0
package/commands/issue.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Create a well-structured GitHub issue that the pipeline can consume reliably
3
- argument-hint: [description of the problem or feature]
3
+ argument-hint: "[description of the problem or feature] [--dry-run] | --title \"...\" --body-file <path> --label \"...\" [--milestone \"...\"] [--dry-run]"
4
4
  ---
5
5
  <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
6
  <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
@@ -11,11 +11,210 @@ argument-hint: [description of the problem or feature]
11
11
 
12
12
  You create GitHub issues with the exact structure the `/work-on` pipeline expects. Every issue you create must give the investigation agent enough context to find the right files on the first pass — no vague descriptions, no missing domains, no ambiguous scope.
13
13
 
14
+ `/issue` is the structured create-hook for issue creation across the pipeline — it enforces the canonical template, reads code before drafting, runs dedup, and validates mandatory sections. Because those checks are deterministic, `/issue` **creates the issue by default once they pass** — it does not wait for a human to approve the draft. Pass `--dry-run` to review the draft without creating anything (see Argument Parsing below).
15
+
16
+ `/issue` also accepts a **programmatic invocation form** for callers that have already composed a title, body, labels, and (optionally) a milestone — e.g. `Skill(skill="issue", args="--title \"fix: ...\" --body-file \"$(mktemp)\" --label bug --label P2")` (the caller writes the body to that path before invoking). This form skips the free-text parsing (Phase 1) and LLM drafting (Phase 3) entirely, but still runs the same dedup and body-validation correctness gates as the interactive path. See **Programmatic Invocation Contract** below — in particular, `--body-file` callers MUST use a `mktemp`-created path, never a fixed literal like `/tmp/body.md`; concurrent orchestrated agents share this host's `/tmp`, and a fixed path can be clobbered mid-flight by another agent (forge#2198).
17
+
14
18
  **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.
15
19
  **NEVER use plan mode (EnterPlanMode).**
16
20
 
17
21
  ---
18
22
 
23
+ ## Argument Parsing
24
+
25
+ | Flag | Effect |
26
+ |------|--------|
27
+ | (none) | Default — read code, dedup, draft, validate, then create the issue immediately. No pre-create confirmation prompt. |
28
+ | `--dry-run` | Draft and validate as normal, but STOP before `gh issue create` — print the draft (or batch draft table) for human review instead of creating it |
29
+ | `--title "..."` | **Activates programmatic mode.** Supplies the final issue title directly — skips Phase 1 (parse) and Phase 3 (draft) entirely. See Programmatic Invocation Contract below. |
30
+ | `--body "..."` | Supplies the final issue body directly (programmatic mode only). Mutually exclusive with `--body-file`. |
31
+ | `--body-file <path>` | Supplies the final issue body by reading it from a file (programmatic mode only). Mutually exclusive with `--body`. |
32
+ | `--label "..."` | Adds one label. Repeatable (`--label bug --label P2`) — labels accumulate. Programmatic mode only. |
33
+ | `--milestone "..."` | Supplies the milestone title directly, skipping the Phase 2E milestone lookup. Programmatic mode only, optional. |
34
+
35
+ ```bash
36
+ DRY_RUN=false
37
+ PROGRAMMATIC_TITLE=""
38
+ PROGRAMMATIC_BODY=""
39
+ PROGRAMMATIC_BODY_FILE=""
40
+ PROGRAMMATIC_LABELS=()
41
+ PROGRAMMATIC_MILESTONE=""
42
+
43
+ # Positional/flag parsing. $ARGUMENTS is a single opaque string (the invoking
44
+ # agent's raw args, e.g. from `Skill(skill="issue", args="--title \"fix: ...\" ...")`)
45
+ # — it is NOT pre-tokenized by any harness, and a naive `ARGS=($ARGUMENTS)` would
46
+ # word-split on whitespace without honoring embedded quote characters (a literal
47
+ # `"` inside the string is not shell syntax at this point), fragmenting any
48
+ # multi-word `--title`/`--body` value (forge#2094).
49
+ #
50
+ # Do NOT tokenize via `eval "set -- $ARGUMENTS"`. An earlier fix for #2094 used
51
+ # exactly that — it is quote-aware, but `eval` re-parses and EXECUTES the full
52
+ # substituted string as shell code: any unescaped `$(...)`, backtick, `;`, `|`,
53
+ # or `&&` embedded in a caller-supplied title/body value runs as a command
54
+ # during parsing, not just as text. `/issue` is invoked programmatically by
55
+ # other automation (audit tools, /work-on decomposition, review-finding
56
+ # creation — see commands/work-on.md, commands/review-pr.md,
57
+ # commands/review-pr-staging.md, commands/test-gate.md) with less-trusted
58
+ # strings, so this is a real call-site injection surface, not a theoretical one.
59
+ #
60
+ # Use `xargs` instead: it applies the same quote/backslash-aware word-splitting
61
+ # a shell command line would (so `--title "fix: billing crash"` still yields
62
+ # ONE token), but — unlike `eval` — it never expands `$(...)`, never expands
63
+ # backticks, and never executes anything. NUL-delimit the output (`printf
64
+ # '%s\0'` / `xargs -n1 ... -0`) so tokens containing embedded newlines still
65
+ # round-trip correctly through `mapfile -d ''`.
66
+ #
67
+ # `mapfile -d ''` requires bash >= 4.4 — consistent with the minimum this
68
+ # pipeline already assumes elsewhere (e.g. `resolve_script()`'s tier-dispatch
69
+ # in work-on.md), so this is not a new constraint.
70
+ #
71
+ # Do NOT silence xargs's exit status with `2>/dev/null`: xargs has a
72
+ # platform-dependent max single-argument length (POSIX guarantees at least
73
+ # 4096 bytes; Linux/macOS observed limits are far higher but not unbounded).
74
+ # A token exceeding that ceiling makes xargs fail loudly — suppressing stderr
75
+ # would turn that into a silent truncation instead, which is worse than the
76
+ # word-splitting bug this whole block exists to fix. `--title`/`--body` are
77
+ # short by convention; `--body-file` (the mechanism every current caller
78
+ # actually uses for large content — see commands/work-on.md,
79
+ # commands/review-pr.md, commands/review-pr-staging.md, commands/test-gate.md,
80
+ # commands/validate.md) is unaffected, since only the file *path* passes
81
+ # through this tokenizer, not the file's contents.
82
+ #
83
+ # `mapfile -d '' -t ARGS < <(cmd)` cannot observe `cmd`'s exit status —
84
+ # process substitution runs in a detached subshell, and mapfile's own exit
85
+ # status only reflects whether the read succeeded, not the upstream command
86
+ # (verified: `mapfile -d '' -t A < <(false; printf 'x\0')` exits 0). Route
87
+ # through a temp file instead so the real xargs exit status is captured.
88
+ ARGS_TMP=$(mktemp)
89
+ if ! printf '%s' "$ARGUMENTS" | xargs -n1 printf '%s\0' > "$ARGS_TMP"; then
90
+ echo "ERROR: failed to tokenize \$ARGUMENTS — a single token may exceed xargs's max argument length. Use --body-file for large content instead of inline --body." >&2
91
+ rm -f "$ARGS_TMP"
92
+ exit 1
93
+ fi
94
+ mapfile -d '' -t ARGS < "$ARGS_TMP"
95
+ rm -f "$ARGS_TMP"
96
+
97
+ # Flag-mode gate: only run the flag-parsing loop (and its hard-error on
98
+ # unrecognized --flags below) when at least one token EXACTLY matches a
99
+ # recognized flag. Free-text invocations (the "(none)" row above) are plain
100
+ # English descriptions and may legitimately contain a "--something" substring
101
+ # as part of the prose (e.g. "fix: crash when using --verbose flag") — those
102
+ # must fall through untouched to Phase 1's free-text parser, not be treated
103
+ # as a caller typo. Without this gate, the --*) error arm below would abort
104
+ # on any free-text description that happens to mention a flag name. See
105
+ # forge#2096 (loop content) for why unrecognized flags must fail loudly once
106
+ # we ARE in flag mode.
107
+ LOOKS_LIKE_FLAG_MODE=false
108
+ for arg in "${ARGS[@]}"; do
109
+ case "$arg" in
110
+ --dry-run|--title|--body|--body-file|--label|--milestone)
111
+ LOOKS_LIKE_FLAG_MODE=true
112
+ break
113
+ ;;
114
+ esac
115
+ done
116
+
117
+ i=0
118
+ while [ "$LOOKS_LIKE_FLAG_MODE" = "true" ] && [ $i -lt ${#ARGS[@]} ]; do
119
+ arg="${ARGS[$i]}"
120
+ case "$arg" in
121
+ --dry-run) DRY_RUN=true ;;
122
+ --title|--body|--body-file|--label|--milestone)
123
+ # Each of these flags requires a value token immediately after it. A flag
124
+ # with no following token (e.g. --title as the last argument) is a usage
125
+ # error, not an empty value — fail loudly instead of silently proceeding
126
+ # with PROGRAMMATIC_TITLE="" (which would otherwise mis-detect as
127
+ # non-programmatic mode and mask the caller's mistake).
128
+ if [ $((i+1)) -ge ${#ARGS[@]} ]; then
129
+ echo "ERROR: $arg requires a value"
130
+ exit 1
131
+ fi
132
+ i=$((i+1))
133
+ case "$arg" in
134
+ --title) PROGRAMMATIC_TITLE="${ARGS[$i]}" ;;
135
+ --body) PROGRAMMATIC_BODY="${ARGS[$i]}" ;;
136
+ --body-file) PROGRAMMATIC_BODY_FILE="${ARGS[$i]}" ;;
137
+ --label) PROGRAMMATIC_LABELS+=("${ARGS[$i]}") ;;
138
+ --milestone) PROGRAMMATIC_MILESTONE="${ARGS[$i]}" ;;
139
+ esac
140
+ ;;
141
+ --*)
142
+ # Unrecognized flag (e.g. a mistyped --titel instead of --title). Fail
143
+ # loudly instead of silently discarding it — a silently-ignored flag
144
+ # can fail open into the interactive/free-text path (PROGRAMMATIC_MODE
145
+ # stays false because PROGRAMMATIC_TITLE never gets set) with no
146
+ # diagnostic pointing at the caller's typo. See forge#2096.
147
+ #
148
+ # Deliberately NOT a bare `*)` catch-all: free-text invocations (the
149
+ # "(none)" row in the Argument Parsing table above, e.g. "the billing
150
+ # page crashes when credits hit zero") tokenize into plain words with
151
+ # no leading `--`, and those words are intentionally NOT flags — they
152
+ # are consumed later via $ARGUMENTS as a whole by Phase 1's free-text
153
+ # parser. Only tokens that look like a flag (start with `--`) but
154
+ # don't match a known one are a caller mistake worth failing loudly on.
155
+ echo "ERROR: unrecognized flag: $arg" >&2
156
+ exit 1
157
+ ;;
158
+ esac
159
+ i=$((i+1))
160
+ done
161
+
162
+ # PROGRAMMATIC_MODE is true iff --title was supplied. Every other structured flag
163
+ # (--body/--body-file/--label/--milestone) is meaningless without a title and does
164
+ # NOT, on its own, activate programmatic mode — this avoids a caller accidentally
165
+ # triggering the skip-Phase-1/3 path with a partial/malformed flag set.
166
+ if [ -n "$PROGRAMMATIC_TITLE" ]; then
167
+ PROGRAMMATIC_MODE=true
168
+ else
169
+ PROGRAMMATIC_MODE=false
170
+ fi
171
+ ```
172
+
173
+ `--dry-run` never bypasses a correctness gate — the Phase 2D dedup STOP and the `--force` human-override rule (see Phase 2D) apply identically whether or not `--dry-run` is set, and identically in both interactive and programmatic mode.
174
+
175
+ ### Programmatic Invocation Contract
176
+
177
+ For callers that have already composed a title, body, labels, and (optionally) a milestone — audit tools, `/work-on` decomposition, pipeline-health proposals, review-finding creation, and similar automation — pass `--title` to activate programmatic mode. This bypasses Phase 1 (free-text parsing) and Phase 3 (LLM drafting) entirely; Phase 2D (dedup) and body validation still run.
178
+
179
+ **Required**: `--title "TEXT"` and exactly one of `--body "TEXT"` / `--body-file <path>`.
180
+ **Optional**: `--label "NAME"` (repeatable, zero or more), `--milestone "TITLE"`, `--dry-run`.
181
+
182
+ If `--title` is present but neither `--body` nor `--body-file` is supplied, or both are supplied, this is a usage error: print `ERROR: programmatic mode requires exactly one of --body or --body-file` and STOP — do not fall through to Phase 2D or Phase 4B.
183
+
184
+ **`--body-file` path requirement (MANDATORY for the caller)**: The path passed to `--body-file` MUST come from `mktemp` (e.g. `BODY_FILE="$(mktemp)"`), never a fixed literal such as `/tmp/body.md` or `/tmp/issue.json`. Concurrent orchestrated agents (`/orchestrate` dispatches up to `orchestration.max_concurrent` agents, default 12) share this host's `/tmp` — a fixed filename collides across agents, and a second agent's write to the same path can silently corrupt the first agent's staged content before it is read. `mktemp` gives every caller a collision-free path at effectively zero cost. This applies to every `--body-file` caller across the pipeline (`commands/work-on.md`, `commands/work-on/decompose.md`, `commands/review-pr*.md`, `commands/orchestrate/phase-1-resolve.md`, and all others) — all current callers already comply; this is the stated contract that keeps it that way (forge#2198).
185
+
186
+ **Fail-loud read ordering (the defensive mechanism against a clobbered body-file)**: `/issue` reads `$PROGRAMMATIC_BODY_FILE` exactly once, into memory, immediately below — it never re-reads the file later in the pipeline. This is deliberate: combined with a `mktemp`-unique path, it minimizes the window in which another process could overwrite the file between the caller's write and this read to effectively nil (no other agent has any reason to target a `mktemp`-generated path). If the file is missing or unreadable at read time, the check below fails loudly (`exit 1`) rather than silently proceeding with an empty or partial body — a clobbered/missing body-file is surfaced as an error, not pushed to GitHub.
187
+
188
+ ```bash
189
+ if [ "$PROGRAMMATIC_MODE" = "true" ]; then
190
+ if [ -n "$PROGRAMMATIC_BODY" ] && [ -n "$PROGRAMMATIC_BODY_FILE" ]; then
191
+ echo "ERROR: programmatic mode requires exactly one of --body or --body-file (both given)"
192
+ exit 1
193
+ elif [ -z "$PROGRAMMATIC_BODY" ] && [ -z "$PROGRAMMATIC_BODY_FILE" ]; then
194
+ echo "ERROR: programmatic mode requires exactly one of --body or --body-file (neither given)"
195
+ exit 1
196
+ elif [ -n "$PROGRAMMATIC_BODY_FILE" ]; then
197
+ # Fail loudly on a missing/unreadable file — do NOT let a failed `cat` fall
198
+ # through as an empty $PROGRAMMATIC_BODY. An empty body would otherwise look
199
+ # like "no body given" to Phase 3F below, which would silently repair it
200
+ # with placeholder-only stub sections instead of surfacing the caller's
201
+ # file-path mistake as an error, same fail-loudly convention as the
202
+ # both/neither check directly above.
203
+ if [ ! -r "$PROGRAMMATIC_BODY_FILE" ]; then
204
+ echo "ERROR: --body-file '$PROGRAMMATIC_BODY_FILE' is missing or unreadable"
205
+ exit 1
206
+ fi
207
+ PROGRAMMATIC_BODY=$(cat "$PROGRAMMATIC_BODY_FILE")
208
+ fi
209
+ fi
210
+ ```
211
+
212
+ **Routing** (see also Phase 1's routing guard below): when `PROGRAMMATIC_MODE=true`, skip directly from here to **Phase 2D** using `{PROPOSED_TITLE}` = `$PROGRAMMATIC_TITLE` — Phase 1 (parse) and Phase 3 (draft) do not run. After Phase 2D passes, run **Phase 3F: Programmatic Pre-Create Validation** (below), then proceed to the unmodified Phase 4 (create). `{PRIORITY_LABEL},{CATEGORY_LABEL}` in Phase 4B is the comma-join of `PROGRAMMATIC_LABELS[@]`; the milestone flag is included iff `PROGRAMMATIC_MILESTONE` is non-empty.
213
+
214
+ The free-text/interactive path (`PROGRAMMATIC_MODE=false`) is completely unaffected — it always proceeds through Phase 1 → Phase 2 → Phase 3 → Phase 4 exactly as before this contract was added.
215
+
216
+ ---
217
+
19
218
  ## Why This Exists
20
219
 
21
220
  Bad issues cause cascading pipeline failures. When an issue says "fix the docker mount" without specifying WHICH compose file, the investigator checks one file, misses the override, the builder writes an incomplete fix, the deploy fails, and a new audit issue gets filed about the original issue. One vague sentence costs 3+ agent runs. This command prevents that by enforcing structure at creation time.
@@ -24,6 +223,8 @@ Bad issues cause cascading pipeline failures. When an issue says "fix the docker
24
223
 
25
224
  ## Phase 1: Parse the Request
26
225
 
226
+ **If `PROGRAMMATIC_MODE=true`** (see Programmatic Invocation Contract above): run **1A only** (repository resolution — `GH_REPO`/`GH_FLAG`/`REPO_PATH`/`STAGING_BRANCH` still come from `forge.yaml` and the multi-repo prefix table, never from a caller flag), then skip 1B/1C/1D (type/priority/category classification — the caller already supplied final `--label` values) and skip Phase 3 (drafting) entirely. Jump directly to Phase 2D using `{PROPOSED_TITLE}` = `$PROGRAMMATIC_TITLE`, then continue to Phase 3F (Programmatic Pre-Create Validation) and Phase 4 (create). Do not apply the free-text classification/drafting rules below — they apply only when `PROGRAMMATIC_MODE=false`.
227
+
27
228
  The user's input (`$ARGUMENTS`) can be:
28
229
 
29
230
  | Input Form | Example |
@@ -97,6 +298,8 @@ Pick ONE primary category label:
97
298
 
98
299
  **You MUST read code before writing the issue.** This is the critical difference between a good issue and a bad one. The investigation agent inherits YOUR file references — if you point to the wrong file, the entire pipeline goes sideways.
99
300
 
301
+ **In programmatic mode, skip 2A/2B/2C (domain identification, code reading, affected-file enumeration)** — the caller already did its own domain-specific reading and drafting before invoking `/issue` with `--title`/`--body`/`--body-file`; re-deriving that here would duplicate work the caller has already done and risks second-guessing a caller with more direct context (e.g. an audit agent that already read the offending file). Continue to **2D** (dedup — still mandatory) and **2E** (skip — see below).
302
+
100
303
  ### 2A: Identify the domain
101
304
 
102
305
  Map the issue to its domain and key files. Domain structure comes from the project — check `forge.yaml → review.context` and the project's `CLAUDE.md` for project-specific domain maps.
@@ -173,6 +376,8 @@ If a duplicate exists:
173
376
  gh api repos/{GH_REPO}/milestones --jq '.[] | select(.state == "open") | "#\(.number) \(.title) (\(.open_issues) open)"'
174
377
  ```
175
378
 
379
+ **Skip in programmatic mode** — `$PROGRAMMATIC_MILESTONE` (if supplied) is used directly by Phase 4B; there is nothing to look up.
380
+
176
381
  ---
177
382
 
178
383
  ## Phase 3: Draft the Issue
@@ -349,14 +554,64 @@ Before creating, verify:
349
554
  5. **Override files included**: If `docker-compose.yml` is listed, `docker-compose.prod.yml` is too (if it exists). If a model is listed, its migration is too.
350
555
  6. **Priority matches severity**: P0 = prod down, P1 = significant user impact, P2 = minor, P3 = cosmetic
351
556
 
557
+ **Skip this step entirely in programmatic mode** — the caller already composed and is responsible for the draft. Programmatic mode has its own validation gate instead: Phase 3F below.
558
+
559
+ ### 3F: Programmatic Pre-Create Validation (programmatic mode only, MANDATORY)
560
+
561
+ **Skip if `PROGRAMMATIC_MODE=false`** — this step only runs for the `--title`-driven path; the free-text/interactive path uses 3E above instead.
562
+
563
+ Runs after Phase 2D (dedup) passes and before Phase 4B (create). This reuses the exact same three mandatory-section check Phase 4C.5 performs post-creation — but applied here, pre-create, against the caller-supplied body, so a programmatic caller's issue is validated *before* it exists rather than repaired after the fact. Phase 4C.5 still runs afterward too (unchanged, for both modes) as an idempotent safety net — after this step it will find nothing to repair.
564
+
565
+ ```bash
566
+ MISSING_SECTIONS=""
567
+ echo "$PROGRAMMATIC_BODY" | grep -q "^## Problem" || MISSING_SECTIONS="$MISSING_SECTIONS PROBLEM"
568
+ echo "$PROGRAMMATIC_BODY" | grep -q "^## Affected Files" || MISSING_SECTIONS="$MISSING_SECTIONS AFFECTED_FILES"
569
+ echo "$PROGRAMMATIC_BODY" | grep -q "^## Acceptance Criteria" || MISSING_SECTIONS="$MISSING_SECTIONS ACCEPTANCE_CRITERIA"
570
+
571
+ if [ -n "$MISSING_SECTIONS" ]; then
572
+ echo "WARNING: supplied --body is missing sections:$MISSING_SECTIONS — adding placeholders before creation"
573
+
574
+ APPEND_TEXT=""
575
+ echo "$MISSING_SECTIONS" | grep -q "PROBLEM" && APPEND_TEXT="$APPEND_TEXT
576
+ ## Problem
577
+
578
+ Root cause unknown — investigation needed."
579
+
580
+ echo "$MISSING_SECTIONS" | grep -q "AFFECTED_FILES" && APPEND_TEXT="$APPEND_TEXT
581
+ ## Affected Files
582
+
583
+ Files to be identified during investigation."
584
+
585
+ echo "$MISSING_SECTIONS" | grep -q "ACCEPTANCE_CRITERIA" && APPEND_TEXT="$APPEND_TEXT
586
+ ## Acceptance Criteria
587
+
588
+ - [ ] Fix confirmed during investigation."
589
+
590
+ # Append missing sections to the supplied body (never replace — only extend),
591
+ # same non-blocking "repair, don't fail" semantics as Phase 4C.5.
592
+ PROGRAMMATIC_BODY="${PROGRAMMATIC_BODY}${APPEND_TEXT}"
593
+ echo "Body repaired in-memory before creation — added:$MISSING_SECTIONS"
594
+ else
595
+ echo "Pre-create validation passed — all mandatory sections present"
596
+ fi
597
+ ```
598
+
599
+ **This step never fails or blocks** — identical to Phase 4C.5's contract. Missing sections cause an in-memory repair, not an error; `$PROGRAMMATIC_BODY` (now guaranteed to contain all three mandatory sections) is what Phase 4B creates the issue with, and what a `--dry-run` preview (Phase 4A) prints.
600
+
352
601
  ---
353
602
 
354
603
  ## Phase 4: Create the Issue
355
604
 
356
- ### 4A: Present draft to user
605
+ ### 4A: Create by default, or stop for review with `--dry-run`
606
+
607
+ Once Phase 2D (dedup) has passed with no blocking duplicate, and either Phase 3E (interactive draft validation) or Phase 3F (programmatic pre-create validation) has passed, proceed directly to Phase 4B and create the issue. **No pre-create confirmation prompt.** This is the default for both interactive and programmatic invocations — dedup and validation are the correctness gates, not a human approval step.
608
+
609
+ In programmatic mode, `{title}` = `$PROGRAMMATIC_TITLE`, `{full issue body}` = `$PROGRAMMATIC_BODY` (post-3F, i.e. already section-repaired), `{priority}, {category}` = the joined `PROGRAMMATIC_LABELS[@]`, and `{milestone}` = `$PROGRAMMATIC_MILESTONE` (or "none" if empty) — no interactive drafting occurred, so the dry-run preview below simply echoes back what the caller supplied (plus any 3F repairs).
610
+
611
+ **If `--dry-run` was passed** (see Argument Parsing): print the draft below and STOP. Do NOT run Phase 4B.
357
612
 
358
613
  ```
359
- ## Issue Draft
614
+ ## Issue Draft (--dry-run — not created)
360
615
 
361
616
  **Repo**: {GH_REPO}
362
617
  **Title**: {title}
@@ -369,11 +624,9 @@ Before creating, verify:
369
624
 
370
625
  ---
371
626
 
372
- Create this issue? (yes / adjust / cancel)
627
+ Re-run without --dry-run to create this issue.
373
628
  ```
374
629
 
375
- **Wait for user confirmation.** Do NOT create the issue without approval.
376
-
377
630
  ### 4B: Create the issue
378
631
 
379
632
  ```bash
@@ -398,6 +651,25 @@ ISSUE_EOF
398
651
  )"
399
652
  ```
400
653
 
654
+ **Programmatic mode variable mapping**: `{TITLE}` = `$PROGRAMMATIC_TITLE`; `{FULL_BODY}` = `$PROGRAMMATIC_BODY` (post-Phase-3F); the milestone branch is used iff `$PROGRAMMATIC_MILESTONE` is non-empty, with `{MILESTONE_TITLE}` = `$PROGRAMMATIC_MILESTONE`. This is the same `gh issue create` call the interactive path uses — no new command surface, only a new source for the variables.
655
+
656
+ `{PRIORITY_LABEL},{CATEGORY_LABEL}` in the interactive-mode template above stands for `PROGRAMMATIC_LABELS[@]`, but the actual `--label` flag(s) passed to `gh issue create` are built as one repeatable `--label` per array element — NOT a single comma-joined string. `gh issue create --label` itself comma-splits its argument, so joining `PROGRAMMATIC_LABELS[@]` with `,` and passing it as one value would incorrectly fragment any individual label that contains a literal comma (see forge#2097). `--label` is a repeatable flag — passing it multiple times accumulates labels without any join/split ambiguity. Zero labels is valid and must omit `--label` entirely (an empty `--label ""` would fail):
657
+
658
+ ```bash
659
+ LABEL_FLAG=()
660
+ for label in "${PROGRAMMATIC_LABELS[@]}"; do
661
+ LABEL_FLAG+=(--label "$label")
662
+ done
663
+
664
+ if [ -n "$PROGRAMMATIC_MILESTONE" ]; then
665
+ gh issue create {GH_FLAG} --title "$PROGRAMMATIC_TITLE" "${LABEL_FLAG[@]}" \
666
+ --milestone "$PROGRAMMATIC_MILESTONE" --body "$PROGRAMMATIC_BODY"
667
+ else
668
+ gh issue create {GH_FLAG} --title "$PROGRAMMATIC_TITLE" "${LABEL_FLAG[@]}" \
669
+ --body "$PROGRAMMATIC_BODY"
670
+ fi
671
+ ```
672
+
401
673
  ### 4C: Post-creation verification
402
674
 
403
675
  ```bash
@@ -412,6 +684,8 @@ echo "Labels: ${LABELS}"
412
684
 
413
685
  After creation, verify the issue body contains the three mandatory pipeline sections. If any are missing, add placeholder content so downstream pipeline agents have the correct scaffolding.
414
686
 
687
+ **Runs for both interactive and programmatic invocations, unconditionally — do not skip this step for programmatic mode.** For programmatic callers this is expected to be a no-op: Phase 3F already validated and repaired `$PROGRAMMATIC_BODY` before creation, so `$CREATED_BODY` here should already contain all three sections. This step still runs as an idempotent safety net (e.g. if GitHub or an intermediate transform altered the body between 3F and creation).
688
+
415
689
  **This step never fails or blocks.** Missing sections cause a repair, not an error. Issues created with full structure pass through immediately.
416
690
 
417
691
  ```bash
@@ -481,13 +755,25 @@ fi
481
755
 
482
756
  If the user provides multiple issues to create (e.g., from an audit, analytics session, or decomposition):
483
757
 
484
- 1. Draft ALL issues first
485
- 2. Present them as a numbered list for review
486
- 3. Create all after confirmation
487
- 4. Report all created issue numbers
758
+ 1. Draft ALL issues first (each running its own dedup + validation pass, Phase 2D/3E)
759
+ 2. Create all of them by default — consistent with the single-issue default in Phase 4A. No pre-create confirmation prompt.
760
+ 3. If `--dry-run` was passed, present them as a numbered list instead of creating them (see table below) and STOP
761
+ 4. Report all created (or, under `--dry-run`, all drafted) issue numbers
762
+
763
+ **Default (no `--dry-run`)**: create each drafted issue in order, then report:
764
+ ```
765
+ ## Batch Issues Created
766
+
767
+ | # | Issue | Priority | Category | Deps |
768
+ |---|-------|----------|----------|------|
769
+ | 1 | #{N1} {title} | P2 | bug | — |
770
+ | 2 | #{N2} {title} | P1 | feature | After #{N1} |
771
+ | 3 | #{N3} {title} | P2 | refactor | — |
772
+ ```
488
773
 
774
+ **With `--dry-run`**: print the batch draft table and stop before any `gh issue create` call:
489
775
  ```
490
- ## Batch Issue Draft
776
+ ## Batch Issue Draft (--dry-run — not created)
491
777
 
492
778
  | # | Title | Priority | Category | Deps |
493
779
  |---|-------|----------|----------|------|
@@ -507,7 +793,7 @@ If the user provides multiple issues to create (e.g., from an audit, analytics s
507
793
 
508
794
  </details>
509
795
 
510
- Create all {N} issues? (yes / adjust #N / cancel)
796
+ Re-run without --dry-run to create these {N} issues.
511
797
  ```
512
798
 
513
799
  ---
@@ -528,10 +814,11 @@ Create all {N} issues? (yes / adjust #N / cancel)
528
814
 
529
815
  ## Safety Rules
530
816
 
531
- 1. **Always read code before creating an issue.** Never create an issue based solely on user description without verifying the files exist and the problem is plausible.
532
- 2. **Always check for duplicates.** Creating duplicate issues wastes agent runs.
533
- 3. **Always wait for user confirmation.** Never create issues without showing the draft first.
817
+ 1. **Always read code before creating an issue.** Never create an issue based solely on user description without verifying the files exist and the problem is plausible. (Programmatic mode: this responsibility shifts to the caller — the caller already read code before composing `--title`/`--body`; `/issue` does not re-verify file paths for programmatic invocations.)
818
+ 2. **Always check for duplicates.** Creating duplicate issues wastes agent runs — dedup (Phase 2D) runs identically for interactive and programmatic invocations; no flag bypasses it.
819
+ 3. **Default to non-interactive creation.** Once dedup (Phase 2D) and draft validation (Phase 3E, or Phase 3F for programmatic mode) pass, create the issue — do not wait for a human to approve the draft. Only pause for human review when `--dry-run` is explicitly passed.
534
820
  4. **Never fabricate file paths.** Every path in the issue body must be verified against the actual repo.
535
821
  5. **Never omit override/companion files.** If one compose file is affected, check the other. If one model is affected, check its migration.
536
822
  6. **Use conventional commit prefixes in titles.** The pipeline and changelog depend on them.
537
823
  7. **Default to P2.** Only use P0/P1 when the user explicitly indicates severity or the evidence clearly shows production impact.
824
+ 8. **`--title` is the sole programmatic-mode trigger.** `--body`/`--body-file`/`--label`/`--milestone` are meaningless without it and never activate programmatic mode on their own — this prevents an accidental partial flag set from silently skipping Phase 1/3.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Create, manage, and ship milestones — the top-level planning layer for feature development
3
- argument-hint: [create <name> | status | ship <slug> | sync <slug>]
3
+ argument-hint: "[create <name> | status | ship <slug> | sync <slug>]"
4
4
  install: extras
5
5
  ---
6
6
  <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
@@ -168,17 +168,16 @@ Present the proposed issues to the user. Ask:
168
168
 
169
169
  **Issue body standard**: Use the **Pipeline Issue Template** from `issue.md` Phase 3D as the body structure for every issue created here. Do NOT use a bespoke inline template — the Pipeline Issue Template is the single canonical standard for all automated issue creation across the pipeline. The body content (Problem, Root Cause, Affected Files, Acceptance Criteria, Context, Dependencies) comes from the per-issue investigation in Step 4B. <!-- Added: forge#293 -->
170
170
 
171
+ **Route creation through `/issue`'s programmatic invocation contract** (`commands/issue.md`, added #2085) instead of calling `gh issue create` directly — this gets dedup (Phase 2D) and mandatory-section body validation (Phase 3F) for free. <!-- Changed: forge#2086 — route through /issue create-hook -->
172
+
171
173
  For each approved issue, create it in the **correct repo** based on the scope analysis:
172
174
 
173
175
  ```bash
174
176
  # For default repo issues:
175
177
  # Body content derives from the Step 4B per-issue investigation.
176
178
  # Structure MUST match the Pipeline Issue Template in issue.md Phase 3D.
177
- gh issue create {GH_FLAG} \
178
- --title "{fix|feat|refactor}: {issue_title}" \
179
- --label "{type},{priority}" \
180
- --milestone "{TITLE}" \
181
- --body "$(cat <<'BODY_EOF'
179
+ MILESTONE_ISSUE_BODY_FILE="$(mktemp)"
180
+ cat > "$MILESTONE_ISSUE_BODY_FILE" <<'BODY_EOF'
182
181
  ## Problem
183
182
 
184
183
  {1-3 sentences: what needs to be built or fixed for this milestone. Specific — derived from Step 4B code read, not milestone description alone.}
@@ -209,15 +208,21 @@ Part of milestone: **{MILESTONE_TITLE}**
209
208
 
210
209
  {Either "None" or "Depends on #{ISSUE_NUMBER} — {reason}"}
211
210
  BODY_EOF
212
- )"
211
+ ```
212
+
213
+ ```
214
+ ISSUE_SKILL_OUTPUT=$(Skill(skill="issue", args="--title \"{fix|feat|refactor}: {issue_title}\" --body-file \"${MILESTONE_ISSUE_BODY_FILE}\" --label \"{type}\" --label \"{priority}\" --milestone \"{TITLE}\""))
215
+ ```
213
216
 
217
+ ```bash
214
218
  # For satellite repo issues (MCP, n8n) — create in the satellite repo:
215
219
  # Note: GitHub milestones are per-repo, so satellite issues reference the milestone by name only
216
220
  # Body structure MUST match the Pipeline Issue Template in issue.md Phase 3D.
217
- gh issue create -R {SATELLITE_REPO} \
218
- --title "{fix|feat|refactor}: {issue_title}" \
219
- --label "{type},{priority}" \
220
- --body "$(cat <<'BODY_EOF'
221
+ # issue.md's Phase 1A resolves target repo from a "{satellite_prefix}: " prefix embedded in
222
+ # the title text itself (not a --repo flag — the programmatic contract has none), so the
223
+ # satellite prefix is prepended here exactly as issue.md's routing table documents it.
224
+ SATELLITE_ISSUE_BODY_FILE="$(mktemp)"
225
+ cat > "$SATELLITE_ISSUE_BODY_FILE" <<'BODY_EOF'
221
226
  ## Problem
222
227
 
223
228
  {1-3 sentences: what needs to be built or fixed in this satellite repo for the milestone.}
@@ -247,7 +252,24 @@ Part of cross-repo milestone: **{MILESTONE_TITLE}** (primary: {GH_REPO})
247
252
 
248
253
  {Either "None" or "Depends on #{ISSUE_NUMBER} — {reason}"}
249
254
  BODY_EOF
250
- )"
255
+ ```
256
+
257
+ ```
258
+ ISSUE_SKILL_OUTPUT=$(Skill(skill="issue", args="--title \"{SATELLITE_PREFIX}: {fix|feat|refactor}: {issue_title}\" --body-file \"${SATELLITE_ISSUE_BODY_FILE}\" --label \"{type}\" --label \"{priority}\""))
259
+ ```
260
+
261
+ **Extract each created issue number from the Skill output** (see `commands/issue.md` Phase 4C/4E — it echoes `Created: {url}` and reports `**#{NUMBER}**: {title}`) — repeat for both the default-repo and satellite-repo calls above:
262
+
263
+ ```bash
264
+ NEW_ISSUE_NUMBER=$(echo "$ISSUE_SKILL_OUTPUT" | grep -oE 'issues/[0-9]+' | head -1 | grep -oE '[0-9]+')
265
+ [ -z "$NEW_ISSUE_NUMBER" ] && NEW_ISSUE_NUMBER=$(echo "$ISSUE_SKILL_OUTPUT" | grep -oE '\*\*#[0-9]+\*\*' | head -1 | grep -oE '[0-9]+')
266
+
267
+ if [ -z "$NEW_ISSUE_NUMBER" ]; then
268
+ echo "WARNING: /issue did not report a created issue number for '{issue_title}' — likely a Phase 2D dedup STOP (near-duplicate found) or a usage error. Do not add this issue to {created_issue_numbers} (Step 6B) or to any dependency reference in a later issue body — review the Skill output above."
269
+ else
270
+ # Accumulate into {created_issue_numbers} for Step 6B project board sync
271
+ created_issue_numbers+=("$NEW_ISSUE_NUMBER")
272
+ fi
251
273
  ```
252
274
 
253
275
  ### Step 6B: Add issues to Project board
@@ -445,9 +467,10 @@ else
445
467
  # Truncate AT_RISK to safe size for issue body (avoid shell quoting issues with large diffs)
446
468
  AT_RISK_SNIPPET=$(echo "$AT_RISK" | head -60 | sed "s/'/'\\\\''/g")
447
469
 
448
- gh issue create {GH_FLAG} \
449
- --title "fix(milestone): rebase conflict during hunk-loss audit for milestone/{slug}" \
450
- --body "$(cat <<ISSUE_EOF
470
+ # Route through /issue's programmatic invocation contract (commands/issue.md, #2085)
471
+ # instead of calling gh issue create directly — gets dedup + body validation for free.
472
+ REBASE_ISSUE_BODY_FILE="$(mktemp)"
473
+ cat > "$REBASE_ISSUE_BODY_FILE" <<ISSUE_EOF
451
474
  ## Problem
452
475
 
453
476
  The pre-merge hunk-loss audit for milestone \`{slug}\` detected $AT_RISK_COUNT at-risk staging hunks, then attempted to rebase \`milestone/{slug}\` onto \`origin/{STAGING_BRANCH}\` to absorb them. The rebase conflicted and was aborted. Manual resolution is required before the milestone can ship.
@@ -488,10 +511,14 @@ Then re-run \`/milestone ship {slug}\`.
488
511
 
489
512
  <!-- AUTO-CREATED: hunk-loss audit rebase conflict -->
490
513
  ISSUE_EOF
491
- )" \
492
- --label "bug" --label "priority:P0" --label "needs-human"
514
+ ```
493
515
 
494
- echo "STOP: Rebase conflict. Created issue for manual resolution. Do NOT proceed to PR creation."
516
+ ```
517
+ ISSUE_SKILL_OUTPUT=$(Skill(skill="issue", args="--title \"fix(milestone): rebase conflict during hunk-loss audit for milestone/{slug}\" --body-file \"${REBASE_ISSUE_BODY_FILE}\" --label \"bug\" --label \"priority:P0\" --label \"needs-human\""))
518
+ ```
519
+
520
+ ```bash
521
+ echo "STOP: Rebase conflict. Created issue for manual resolution (see /issue output above). Do NOT proceed to PR creation."
495
522
  echo "Run '/milestone ship {slug}' again after resolving the conflict."
496
523
  exit 1
497
524
  fi
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Analyze this repo and generate per-repo adaptive scripts from learned patterns, git history, and existing configuration
3
- argument-hint: [--dry-run | --force | --no-registry]
3
+ argument-hint: "[--dry-run | --force | --no-registry]"
4
4
  install: extras
5
5
  ---
6
6
 
@@ -106,12 +106,13 @@ BATCHABLE_P3=$(gh issue list {GH_FLAG} \
106
106
  - Form same-file clusters first; evaluate any remaining ungrouped findings for leaf-directory clustering. A finding is claimed by at most one batch.
107
107
 
108
108
  **Sanitize the surface-area path before interpolation (MANDATORY):** `{SURFACE_AREA}` is an affected-file path derived from an issue body, and git filenames can legally carry shell metacharacters (`` ` ``, `$()`, quotes). Restrict it to a validated `[A-Za-z0-9._/-]` charset before templating it into `--title` / `--body`, so an untrusted issue body cannot break the `gh` argument boundary. The same guard is applied at the mirror site in `phase-4-execution.md`. <!-- forge#1833, forge#1835 -->
109
+
110
+ **Route batch issue creation through `/issue`'s programmatic invocation contract** (`commands/issue.md`, added #2085) instead of calling `gh issue create` directly — this gets dedup (Phase 2D) and mandatory-section body validation (Phase 3F) for free. <!-- Changed: forge#2086 — route through /issue create-hook -->
111
+
109
112
  ```bash
110
113
  SAFE_SURFACE_AREA=$(printf '%s' "{SURFACE_AREA}" | tr -cd 'A-Za-z0-9._/-')
111
- BATCH_ISSUE_NUM=$(gh issue create {GH_FLAG} \
112
- --title "fix(batch): P3 review findings — ${SAFE_SURFACE_AREA} (batch #{BATCH_N})" \
113
- --label "review-finding,priority:P3,batch" \
114
- --body "$(cat <<'BATCH_EOF'
114
+ BATCH_BODY_FILE="$(mktemp)"
115
+ cat > "$BATCH_BODY_FILE" <<'BATCH_EOF'
115
116
  ## Problem
116
117
 
117
118
  Batch of P3 review findings in **{SURFACE_AREA}** (same file or leaf directory), grouped to reduce per-finding pipeline overhead.
@@ -135,7 +136,21 @@ Batch of P3 review findings in **{SURFACE_AREA}** (same file or leaf directory),
135
136
 
136
137
  <!-- FORGE:BATCHABLE -->
137
138
  BATCH_EOF
138
- )")
139
+ ```
140
+
141
+ ```
142
+ ISSUE_SKILL_OUTPUT=$(Skill(skill="issue", args="--title \"fix(batch): P3 review findings — ${SAFE_SURFACE_AREA} (batch #{BATCH_N})\" --body-file \"${BATCH_BODY_FILE}\" --label \"review-finding\" --label \"priority:P3\" --label \"batch\""))
143
+ ```
144
+
145
+ **Extract the created batch issue number from the Skill output** (see `commands/issue.md` Phase 4C/4E — it echoes `Created: {url}` and reports `**#{NUMBER}**: {title}`):
146
+
147
+ ```bash
148
+ BATCH_ISSUE_NUM=$(echo "$ISSUE_SKILL_OUTPUT" | grep -oE 'issues/[0-9]+' | head -1 | grep -oE '[0-9]+')
149
+ [ -z "$BATCH_ISSUE_NUM" ] && BATCH_ISSUE_NUM=$(echo "$ISSUE_SKILL_OUTPUT" | grep -oE '\*\*#[0-9]+\*\*' | head -1 | grep -oE '[0-9]+')
150
+
151
+ if [ -z "$BATCH_ISSUE_NUM" ]; then
152
+ echo "WARNING: /issue did not report a created batch issue number — likely a Phase 2D dedup STOP (near-duplicate found) or a usage error. Do not replace member issues with a batch issue for this cluster; leave the members on the standard individual pipeline instead."
153
+ fi
139
154
  ```
140
155
 
141
156
  **Replace member issues with the batch issue** in the resolved issue set. Member issues are NOT individually dispatched to `/work-on` — the batch issue is the single pipeline unit.
@@ -266,7 +266,12 @@ Before building agent prompts, run `classify-lane.sh` for every issue in the cur
266
266
  ```bash
267
267
  # Requires classify-lane.sh to be available at ~/.claude/scripts/classify-lane.sh
268
268
  # (installed by `npx forgedock` — see bin/journey.mjs: forge()'s linkPipelineScripts() step)
269
- # Fallback: bash "$FORGE_HOME/scripts/classify-lane.sh" if ~/.claude/scripts/ is unavailable
269
+ # NOTE: the actual invocations below (and in the review-finding sweep) hardcode this
270
+ # ~/.claude path directly — there is no $FORGE_HOME-based fallback implemented, so this
271
+ # call site is not exposed to the bare-unset-$FORGE_HOME → root-anchored-path footgun
272
+ # that affected commands/review-pr.md (see forge#1984, forge#2035 audit). If
273
+ # ~/.claude/scripts/ is genuinely unavailable, classify-lane.sh hard-fails per the
274
+ # phantom-slug gate above — it does NOT fall through to a filesystem search.
270
275
 
271
276
  declare -A ISSUE_LANE
272
277
  declare -A ISSUE_PR_BASE
@@ -420,6 +425,8 @@ Agent(
420
425
  - For satellite repo issues: `Skill(skill='work-on', args='{SATELLITE_PREFIX}:{NUMBER} --under-orchestration')` (prefix from forge.yaml → repos.satellites)
421
426
  - The `--under-orchestration` flag tells `/work-on` to post its phase-entry `FORGE:HEARTBEAT` comments (Phases 0/1/3/5) — this orchestrator's Step 4B.5 stall detector depends on those timestamps. A solo `/work-on` run omits the flag and skips those writes entirely (see `commands/work-on.md` → Orchestration Flag).
422
427
  - NEVER bypass /work-on with manual git/gh commands — the label updates and structured comments are critical for tracking
428
+ - **Temp files — ALWAYS use `mktemp`, NEVER a fixed path**: You are one of several agents running concurrently on this host and you share its `/tmp` with all of them. Any time you stage content in a temp file before passing it to `gh` (e.g. `--body-file`), create that path with `mktemp` (e.g. `BODY_FILE="$(mktemp)"`) — never a fixed literal like `/tmp/body.md` or `/tmp/issue.json`. A fixed path collides with another concurrently-running agent and can silently overwrite the content you staged (or you can silently overwrite theirs) before either of you reads it back — this has caused real cross-agent GitHub issue-body corruption. `mktemp` costs nothing and eliminates the collision. (forge#2198)
429
+ - **`docker cp` — NEVER write into a bind-mounted shared container**: If the project you're working on runs its dev/test containers with a bind mount to the main (non-worktree) checkout, `docker cp` into that container writes through the mount into the main checkout — not your isolated per-issue worktree. Before running `docker cp` into any container, confirm its mount source is your own worktree, not a shared/main one; if you can't confirm that, don't assume an in-container test run reflects your feature branch. (forge#2198)
423
430
  - NEVER target `main` for PRs targeting the default repo. Use `{STAGING_BRANCH}` for fast-lane issues, or `milestone/{slug}` for milestone issues.
424
431
  - Satellite repos (MCP, n8n) have no staging branch — fast-lane PRs go to `main` for those.
425
432
  - If the issue is INVALID after investigation, close it with a comment explaining why