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.
- package/README.md +9 -3
- package/bin/cli-spawn-shared.mjs +25 -0
- package/bin/engine/phases.mjs +178 -28
- package/bin/engine/state.mjs +39 -7
- package/bin/engine-cli.mjs +85 -3
- package/bin/engine.mjs +71 -8
- package/bin/forgedock.mjs +322 -20
- package/bin/hooks/pre-tool-use.mjs +345 -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 +116 -15
- 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 +6 -5
- 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 +302 -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 +8 -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 +27 -4
- package/commands/recover-orphans.md +1 -1
- package/commands/replay.md +16 -2
- package/commands/review-pr-agents/protocols.md +4 -0
- package/commands/review-pr-agents/spec-cli.md +179 -0
- package/commands/review-pr-agents.md +7 -4
- package/commands/review-pr-staging.md +44 -15
- package/commands/review-pr.md +122 -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/upgrade-deps.md +518 -0
- 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 +64 -2
- package/commands/work-on/build.md +10 -2
- package/commands/work-on/close.md +1 -1
- package/commands/work-on/decompose.md +35 -25
- 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/AIDER.md +264 -0
- package/docs/AUTOPILOT-CRON.md +130 -0
- package/docs/CI.md +18 -0
- package/docs/CONFIG.md +41 -2
- package/docs/CURSOR.md +135 -0
- package/docs/OPENCODE.md +312 -0
- package/docs/demo.gif +0 -0
- package/docs/eval/README.md +4 -4
- package/docs/launch/show-hn.md +3 -3
- package/docs/site/annotations-explained.md +2 -2
- package/docs/site/architecture.md +180 -0
- package/docs/site/command-reference.md +38 -0
- package/docs/site/configuration.md +243 -0
- package/docs/site/for-companies.md +4 -4
- package/docs/site/forge-annotation-protocol.md +1 -1
- package/docs/site/github-is-the-memory.md +2 -2
- package/docs/site/how-it-works.md +1 -1
- package/docs/site/public/llms-full.txt +380 -0
- package/docs/site/public/llms.txt +35 -0
- package/docs/spec/review-protocol.md +6 -0
- package/docs/spec-graph-schema.md +3 -3
- package/package.json +2 -2
- package/packages/protocol/README.md +75 -0
- package/packages/protocol/src/card.js +114 -0
- package/packages/protocol/src/cli.js +4 -77
- package/packages/protocol/src/emit.js +14 -3
- package/packages/protocol/src/index.js +2 -1
- package/packages/protocol/src/parse.js +17 -8
- package/packages/protocol/src/types.js +1 -1
- package/packages/protocol/src/validate.js +11 -0
- package/scripts/check-command-side-effects.sh +261 -0
- package/scripts/check-spec-markers.sh +273 -0
- package/scripts/classify-lane.sh +29 -7
- package/scripts/forge-run.sh +322 -0
- package/templates/cross-repo-trigger.yml +282 -0
- package/templates/cursor/forge.mdc +148 -0
- 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:
|
|
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
|
-
|
|
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.
|
|
486
|
-
3.
|
|
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
|
-
|
|
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. **
|
|
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.
|
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
|
|
@@ -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
|