forgedock 1.1.11 → 1.2.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.
- package/README.md +1 -1
- package/bin/cli-spawn-shared.mjs +25 -0
- package/bin/engine-cli.mjs +22 -1
- package/bin/engine.mjs +46 -4
- package/bin/forgedock.mjs +87 -16
- package/bin/hooks/pre-tool-use.mjs +331 -10
- package/bin/init-enrich-api.mjs +12 -4
- package/bin/init-enrich-cli.mjs +328 -0
- package/bin/init-enrich.mjs +137 -0
- package/bin/journey.mjs +13 -4
- package/bin/runner.mjs +653 -10
- package/commands/adopt.md +1 -1
- package/commands/analytics.md +10 -7
- package/commands/audit-agents.md +29 -6
- package/commands/audit.md +9 -20
- package/commands/autopilot.md +52 -78
- package/commands/changelog.md +1 -1
- package/commands/ci-audit.md +19 -10
- package/commands/cleanup.md +1 -1
- package/commands/compat-audit.md +1 -1
- package/commands/deploy-info.md +1 -1
- package/commands/deploy-pr.md +1 -1
- package/commands/design-bench.md +1 -1
- package/commands/design-render-critique-loop.md +1 -1
- package/commands/diagnose.md +1 -1
- package/commands/forge-stats.md +1 -1
- package/commands/forgedock-init.md +1 -1
- package/commands/geo-audit.md +9 -6
- package/commands/incident-response.md +15 -5
- package/commands/issue.md +298 -15
- package/commands/milestone.md +45 -18
- package/commands/optimize.md +1 -1
- package/commands/orchestrate/phase-1-resolve.md +20 -5
- package/commands/orchestrate/phase-4-execution.md +6 -1
- package/commands/orchestrate/phase-5-cleanup.md +43 -2
- package/commands/orchestrate/phase-6-report.md +1 -0
- package/commands/orchestrate.md +1 -1
- package/commands/pipeline-health/phase-4-proposals.md +40 -10
- package/commands/pipeline-health/phase-5-report.md +140 -13
- package/commands/pipeline-health.md +1 -1
- package/commands/pipeline-resume.md +1 -1
- package/commands/pipeline-status.md +1 -1
- package/commands/qa-sweep.md +10 -4
- package/commands/recover-orphans.md +1 -1
- package/commands/review-pr-agents/protocols.md +4 -0
- package/commands/review-pr-agents.md +7 -4
- package/commands/review-pr-staging.md +44 -15
- package/commands/review-pr.md +73 -28
- package/commands/rollback.md +15 -5
- package/commands/security-audit.md +14 -11
- package/commands/signal-planner.md +32 -12
- package/commands/spec-doctor.md +1 -1
- package/commands/sync-ecosystem.md +54 -11
- package/commands/test-gate.md +49 -11
- package/commands/validate.md +16 -5
- package/commands/work-on/build/architect.md +1 -1
- package/commands/work-on/build/context.md +1 -1
- package/commands/work-on/build/implement.md +2 -2
- package/commands/work-on/build/validate.md +3 -1
- package/commands/work-on/build.md +1 -1
- package/commands/work-on/close.md +1 -1
- package/commands/work-on/decompose.md +27 -24
- package/commands/work-on/investigate.md +2 -2
- package/commands/work-on/remediate.md +1 -1
- package/commands/work-on/review.md +5 -3
- package/commands/work-on-monolithic.md +1 -1
- package/commands/work-on.md +28 -7
- package/docs/CI.md +18 -0
- package/docs/CONFIG.md +5 -2
- package/docs/site/annotations-explained.md +2 -2
- package/docs/site/command-reference.md +21 -0
- package/docs/spec/review-protocol.md +6 -0
- package/package.json +1 -1
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,206 @@ 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 /tmp/body.md --label bug --label P2")`. 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.
|
|
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
|
+
```bash
|
|
185
|
+
if [ "$PROGRAMMATIC_MODE" = "true" ]; then
|
|
186
|
+
if [ -n "$PROGRAMMATIC_BODY" ] && [ -n "$PROGRAMMATIC_BODY_FILE" ]; then
|
|
187
|
+
echo "ERROR: programmatic mode requires exactly one of --body or --body-file (both given)"
|
|
188
|
+
exit 1
|
|
189
|
+
elif [ -z "$PROGRAMMATIC_BODY" ] && [ -z "$PROGRAMMATIC_BODY_FILE" ]; then
|
|
190
|
+
echo "ERROR: programmatic mode requires exactly one of --body or --body-file (neither given)"
|
|
191
|
+
exit 1
|
|
192
|
+
elif [ -n "$PROGRAMMATIC_BODY_FILE" ]; then
|
|
193
|
+
# Fail loudly on a missing/unreadable file — do NOT let a failed `cat` fall
|
|
194
|
+
# through as an empty $PROGRAMMATIC_BODY. An empty body would otherwise look
|
|
195
|
+
# like "no body given" to Phase 3F below, which would silently repair it
|
|
196
|
+
# with placeholder-only stub sections instead of surfacing the caller's
|
|
197
|
+
# file-path mistake as an error, same fail-loudly convention as the
|
|
198
|
+
# both/neither check directly above.
|
|
199
|
+
if [ ! -r "$PROGRAMMATIC_BODY_FILE" ]; then
|
|
200
|
+
echo "ERROR: --body-file '$PROGRAMMATIC_BODY_FILE' is missing or unreadable"
|
|
201
|
+
exit 1
|
|
202
|
+
fi
|
|
203
|
+
PROGRAMMATIC_BODY=$(cat "$PROGRAMMATIC_BODY_FILE")
|
|
204
|
+
fi
|
|
205
|
+
fi
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**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.
|
|
209
|
+
|
|
210
|
+
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.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
19
214
|
## Why This Exists
|
|
20
215
|
|
|
21
216
|
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 +219,8 @@ Bad issues cause cascading pipeline failures. When an issue says "fix the docker
|
|
|
24
219
|
|
|
25
220
|
## Phase 1: Parse the Request
|
|
26
221
|
|
|
222
|
+
**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`.
|
|
223
|
+
|
|
27
224
|
The user's input (`$ARGUMENTS`) can be:
|
|
28
225
|
|
|
29
226
|
| Input Form | Example |
|
|
@@ -97,6 +294,8 @@ Pick ONE primary category label:
|
|
|
97
294
|
|
|
98
295
|
**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
296
|
|
|
297
|
+
**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).
|
|
298
|
+
|
|
100
299
|
### 2A: Identify the domain
|
|
101
300
|
|
|
102
301
|
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 +372,8 @@ If a duplicate exists:
|
|
|
173
372
|
gh api repos/{GH_REPO}/milestones --jq '.[] | select(.state == "open") | "#\(.number) \(.title) (\(.open_issues) open)"'
|
|
174
373
|
```
|
|
175
374
|
|
|
375
|
+
**Skip in programmatic mode** — `$PROGRAMMATIC_MILESTONE` (if supplied) is used directly by Phase 4B; there is nothing to look up.
|
|
376
|
+
|
|
176
377
|
---
|
|
177
378
|
|
|
178
379
|
## Phase 3: Draft the Issue
|
|
@@ -349,14 +550,64 @@ Before creating, verify:
|
|
|
349
550
|
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
551
|
6. **Priority matches severity**: P0 = prod down, P1 = significant user impact, P2 = minor, P3 = cosmetic
|
|
351
552
|
|
|
553
|
+
**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.
|
|
554
|
+
|
|
555
|
+
### 3F: Programmatic Pre-Create Validation (programmatic mode only, MANDATORY)
|
|
556
|
+
|
|
557
|
+
**Skip if `PROGRAMMATIC_MODE=false`** — this step only runs for the `--title`-driven path; the free-text/interactive path uses 3E above instead.
|
|
558
|
+
|
|
559
|
+
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.
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
MISSING_SECTIONS=""
|
|
563
|
+
echo "$PROGRAMMATIC_BODY" | grep -q "^## Problem" || MISSING_SECTIONS="$MISSING_SECTIONS PROBLEM"
|
|
564
|
+
echo "$PROGRAMMATIC_BODY" | grep -q "^## Affected Files" || MISSING_SECTIONS="$MISSING_SECTIONS AFFECTED_FILES"
|
|
565
|
+
echo "$PROGRAMMATIC_BODY" | grep -q "^## Acceptance Criteria" || MISSING_SECTIONS="$MISSING_SECTIONS ACCEPTANCE_CRITERIA"
|
|
566
|
+
|
|
567
|
+
if [ -n "$MISSING_SECTIONS" ]; then
|
|
568
|
+
echo "WARNING: supplied --body is missing sections:$MISSING_SECTIONS — adding placeholders before creation"
|
|
569
|
+
|
|
570
|
+
APPEND_TEXT=""
|
|
571
|
+
echo "$MISSING_SECTIONS" | grep -q "PROBLEM" && APPEND_TEXT="$APPEND_TEXT
|
|
572
|
+
## Problem
|
|
573
|
+
|
|
574
|
+
Root cause unknown — investigation needed."
|
|
575
|
+
|
|
576
|
+
echo "$MISSING_SECTIONS" | grep -q "AFFECTED_FILES" && APPEND_TEXT="$APPEND_TEXT
|
|
577
|
+
## Affected Files
|
|
578
|
+
|
|
579
|
+
Files to be identified during investigation."
|
|
580
|
+
|
|
581
|
+
echo "$MISSING_SECTIONS" | grep -q "ACCEPTANCE_CRITERIA" && APPEND_TEXT="$APPEND_TEXT
|
|
582
|
+
## Acceptance Criteria
|
|
583
|
+
|
|
584
|
+
- [ ] Fix confirmed during investigation."
|
|
585
|
+
|
|
586
|
+
# Append missing sections to the supplied body (never replace — only extend),
|
|
587
|
+
# same non-blocking "repair, don't fail" semantics as Phase 4C.5.
|
|
588
|
+
PROGRAMMATIC_BODY="${PROGRAMMATIC_BODY}${APPEND_TEXT}"
|
|
589
|
+
echo "Body repaired in-memory before creation — added:$MISSING_SECTIONS"
|
|
590
|
+
else
|
|
591
|
+
echo "Pre-create validation passed — all mandatory sections present"
|
|
592
|
+
fi
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
**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.
|
|
596
|
+
|
|
352
597
|
---
|
|
353
598
|
|
|
354
599
|
## Phase 4: Create the Issue
|
|
355
600
|
|
|
356
|
-
### 4A:
|
|
601
|
+
### 4A: Create by default, or stop for review with `--dry-run`
|
|
602
|
+
|
|
603
|
+
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.
|
|
604
|
+
|
|
605
|
+
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).
|
|
606
|
+
|
|
607
|
+
**If `--dry-run` was passed** (see Argument Parsing): print the draft below and STOP. Do NOT run Phase 4B.
|
|
357
608
|
|
|
358
609
|
```
|
|
359
|
-
## Issue Draft
|
|
610
|
+
## Issue Draft (--dry-run — not created)
|
|
360
611
|
|
|
361
612
|
**Repo**: {GH_REPO}
|
|
362
613
|
**Title**: {title}
|
|
@@ -369,11 +620,9 @@ Before creating, verify:
|
|
|
369
620
|
|
|
370
621
|
---
|
|
371
622
|
|
|
372
|
-
|
|
623
|
+
Re-run without --dry-run to create this issue.
|
|
373
624
|
```
|
|
374
625
|
|
|
375
|
-
**Wait for user confirmation.** Do NOT create the issue without approval.
|
|
376
|
-
|
|
377
626
|
### 4B: Create the issue
|
|
378
627
|
|
|
379
628
|
```bash
|
|
@@ -398,6 +647,25 @@ ISSUE_EOF
|
|
|
398
647
|
)"
|
|
399
648
|
```
|
|
400
649
|
|
|
650
|
+
**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.
|
|
651
|
+
|
|
652
|
+
`{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):
|
|
653
|
+
|
|
654
|
+
```bash
|
|
655
|
+
LABEL_FLAG=()
|
|
656
|
+
for label in "${PROGRAMMATIC_LABELS[@]}"; do
|
|
657
|
+
LABEL_FLAG+=(--label "$label")
|
|
658
|
+
done
|
|
659
|
+
|
|
660
|
+
if [ -n "$PROGRAMMATIC_MILESTONE" ]; then
|
|
661
|
+
gh issue create {GH_FLAG} --title "$PROGRAMMATIC_TITLE" "${LABEL_FLAG[@]}" \
|
|
662
|
+
--milestone "$PROGRAMMATIC_MILESTONE" --body "$PROGRAMMATIC_BODY"
|
|
663
|
+
else
|
|
664
|
+
gh issue create {GH_FLAG} --title "$PROGRAMMATIC_TITLE" "${LABEL_FLAG[@]}" \
|
|
665
|
+
--body "$PROGRAMMATIC_BODY"
|
|
666
|
+
fi
|
|
667
|
+
```
|
|
668
|
+
|
|
401
669
|
### 4C: Post-creation verification
|
|
402
670
|
|
|
403
671
|
```bash
|
|
@@ -412,6 +680,8 @@ echo "Labels: ${LABELS}"
|
|
|
412
680
|
|
|
413
681
|
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
682
|
|
|
683
|
+
**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).
|
|
684
|
+
|
|
415
685
|
**This step never fails or blocks.** Missing sections cause a repair, not an error. Issues created with full structure pass through immediately.
|
|
416
686
|
|
|
417
687
|
```bash
|
|
@@ -481,13 +751,25 @@ fi
|
|
|
481
751
|
|
|
482
752
|
If the user provides multiple issues to create (e.g., from an audit, analytics session, or decomposition):
|
|
483
753
|
|
|
484
|
-
1. Draft ALL issues first
|
|
485
|
-
2.
|
|
486
|
-
3.
|
|
487
|
-
4. Report all created issue numbers
|
|
754
|
+
1. Draft ALL issues first (each running its own dedup + validation pass, Phase 2D/3E)
|
|
755
|
+
2. Create all of them by default — consistent with the single-issue default in Phase 4A. No pre-create confirmation prompt.
|
|
756
|
+
3. If `--dry-run` was passed, present them as a numbered list instead of creating them (see table below) and STOP
|
|
757
|
+
4. Report all created (or, under `--dry-run`, all drafted) issue numbers
|
|
758
|
+
|
|
759
|
+
**Default (no `--dry-run`)**: create each drafted issue in order, then report:
|
|
760
|
+
```
|
|
761
|
+
## Batch Issues Created
|
|
762
|
+
|
|
763
|
+
| # | Issue | Priority | Category | Deps |
|
|
764
|
+
|---|-------|----------|----------|------|
|
|
765
|
+
| 1 | #{N1} {title} | P2 | bug | — |
|
|
766
|
+
| 2 | #{N2} {title} | P1 | feature | After #{N1} |
|
|
767
|
+
| 3 | #{N3} {title} | P2 | refactor | — |
|
|
768
|
+
```
|
|
488
769
|
|
|
770
|
+
**With `--dry-run`**: print the batch draft table and stop before any `gh issue create` call:
|
|
489
771
|
```
|
|
490
|
-
## Batch Issue Draft
|
|
772
|
+
## Batch Issue Draft (--dry-run — not created)
|
|
491
773
|
|
|
492
774
|
| # | Title | Priority | Category | Deps |
|
|
493
775
|
|---|-------|----------|----------|------|
|
|
@@ -507,7 +789,7 @@ If the user provides multiple issues to create (e.g., from an audit, analytics s
|
|
|
507
789
|
|
|
508
790
|
</details>
|
|
509
791
|
|
|
510
|
-
|
|
792
|
+
Re-run without --dry-run to create these {N} issues.
|
|
511
793
|
```
|
|
512
794
|
|
|
513
795
|
---
|
|
@@ -528,10 +810,11 @@ Create all {N} issues? (yes / adjust #N / cancel)
|
|
|
528
810
|
|
|
529
811
|
## Safety Rules
|
|
530
812
|
|
|
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. **
|
|
813
|
+
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.)
|
|
814
|
+
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.
|
|
815
|
+
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
816
|
4. **Never fabricate file paths.** Every path in the issue body must be verified against the actual repo.
|
|
535
817
|
5. **Never omit override/companion files.** If one compose file is affected, check the other. If one model is affected, check its migration.
|
|
536
818
|
6. **Use conventional commit prefixes in titles.** The pipeline and changelog depend on them.
|
|
537
819
|
7. **Default to P2.** Only use P0/P1 when the user explicitly indicates severity or the evidence clearly shows production impact.
|
|
820
|
+
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.
|
package/commands/milestone.md
CHANGED
|
@@ -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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
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
|
package/commands/optimize.md
CHANGED
|
@@ -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
|
-
|
|
112
|
-
|
|
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
|
-
#
|
|
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
|
|
@@ -39,9 +39,50 @@ Include the audit summary in the final report (Phase 6). Key metrics to surface:
|
|
|
39
39
|
- **Resume cycles** — how many times agents had to be resumed
|
|
40
40
|
- **Stall boundaries** — which phase transitions cause the most stalls
|
|
41
41
|
|
|
42
|
-
### Step 5C:
|
|
42
|
+
### Step 5C: Close the coordination issue (claims board) <!-- Added: forge#2072 -->
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
**Why this step exists**: Step 3D.1 (`phase-3-dependency.md`) creates a per-batch coordination issue (the claims board) and exports `FORGE_COORD_ISSUE`/`COORD_ISSUE_NUMBER`. Step 5A's `/cleanup` sweep only closes orphaned issues whose PR merged — the claims board has no PR of its own, so that heuristic never matches it. Without this step, one `automation`-labelled issue leaks per orchestration batch.
|
|
45
|
+
|
|
46
|
+
**Run after Step 5B, before Step 5D.** No-ops cleanly if the claims board was never created this run (`COORD_ISSUE_NUMBER` unset/empty — e.g. Step 3D.1's `gh issue create` failed, or this batch never reached DAG construction). Tolerates GitHub API failures without aborting the rest of Phase 5 — the same tolerant-failure convention (`2>/dev/null || echo ...`, `|| true`) used throughout `phase-3-dependency.md`/`phase-4-execution.md`.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
if [ -n "${COORD_ISSUE_NUMBER:-}" ]; then
|
|
50
|
+
# Detect any still-active (unreleased) claims — a claim posted with no matching
|
|
51
|
+
# CLAIM_RELEASED comment on the same coordination issue. Adapted from the query
|
|
52
|
+
# phase-4-execution.md's claims-board relaxation sweep uses (Step 4B item 4),
|
|
53
|
+
# simplified to holder-only (a flat string array) since Step 5C only needs
|
|
54
|
+
# holder names for the closure note — unlike phase-4-execution.md's version,
|
|
55
|
+
# which also captures each claim's `files` into an object alongside `holder`.
|
|
56
|
+
ACTIVE_CLAIMS=$(gh api repos/{GH_REPO}/issues/${COORD_ISSUE_NUMBER}/comments \
|
|
57
|
+
--jq '[.[] | select(.body | contains("<!-- FORGE:CLAIM -->")) |
|
|
58
|
+
select(.body | contains("<!-- FORGE:CLAIM_RELEASED -->") | not)] |
|
|
59
|
+
map(.body | capture("\\*\\*Holder\\*\\*: (?P<h>[^\\n]+)").h // "unknown")' 2>/dev/null || echo '[]')
|
|
60
|
+
ACTIVE_CLAIMS_COUNT=$(echo "$ACTIVE_CLAIMS" | jq 'length' 2>/dev/null || echo 0)
|
|
61
|
+
|
|
62
|
+
if [ "$ACTIVE_CLAIMS_COUNT" -gt 0 ] 2>/dev/null; then
|
|
63
|
+
ACTIVE_CLAIMS_NOTE="**Still-active claims at close time** (${ACTIVE_CLAIMS_COUNT}) — not blocking closure, listed for visibility:
|
|
64
|
+
$(echo "$ACTIVE_CLAIMS" | jq -r '.[] | "- " + .' 2>/dev/null)"
|
|
65
|
+
else
|
|
66
|
+
ACTIVE_CLAIMS_NOTE="No active (unreleased) claims at close time."
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
gh issue close "$COORD_ISSUE_NUMBER" -R {GH_REPO} --comment "Batch complete — closing claims board.
|
|
70
|
+
|
|
71
|
+
${ACTIVE_CLAIMS_NOTE}
|
|
72
|
+
|
|
73
|
+
Closed automatically by \`/orchestrate\` Phase 5 Step 5C." 2>/dev/null \
|
|
74
|
+
&& echo "Closed coordination issue #${COORD_ISSUE_NUMBER}" \
|
|
75
|
+
|| echo "WARNING: failed to close coordination issue #${COORD_ISSUE_NUMBER} — non-fatal, continuing Phase 5"
|
|
76
|
+
else
|
|
77
|
+
echo "No coordination issue for this batch (FORGE_COORD_ISSUE unset) — Step 5C is a no-op"
|
|
78
|
+
fi
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Idempotency**: `gh issue close` on an already-closed issue succeeds as a no-op (no error) — safe to re-run this step on a resumed/compacted session without an extra pre-check.
|
|
82
|
+
|
|
83
|
+
### Step 5D: Report cleanup results
|
|
84
|
+
|
|
85
|
+
Include the cleanup summary in the final report (Phase 6), including whether the coordination issue was closed this run (see Step 5C). If cleanup found problems, call them out — they indicate agent pipeline failures that may need investigation.
|
|
45
86
|
|
|
46
87
|
---
|
|
47
88
|
|