forgedock 1.8.1 → 1.8.2
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/bin/engine/admission.mjs +57 -1
- package/bin/engine/admission.test.mjs +60 -0
- package/commands/analytics.md +3 -1
- package/commands/audit-agents.md +3 -1
- package/commands/audit.md +10 -8
- package/commands/autopilot.md +16 -14
- package/commands/ci-audit.md +4 -2
- package/commands/cleanup.md +1 -1
- package/commands/deploy-pr.md +31 -11
- package/commands/geo-audit.md +3 -1
- package/commands/incident-response.md +3 -1
- package/commands/issue.md +4 -2
- package/commands/milestone.md +8 -6
- package/commands/orchestrate/config.md +2 -2
- package/commands/orchestrate/phase-1-resolve.md +56 -12
- package/commands/orchestrate/phase-3-dependency.md +15 -5
- package/commands/orchestrate/phase-4-execution.md +593 -78
- package/commands/orchestrate/phase-5-cleanup.md +4 -2
- package/commands/orchestrate/phase-6-report.md +7 -3
- package/commands/orchestrate.md +13 -9
- package/commands/pipeline-health/phase-4-proposals.md +6 -4
- package/commands/pipeline-health/phase-5-report.md +4 -2
- package/commands/pipeline-health.md +9 -7
- package/commands/pipeline-resume.md +4 -2
- package/commands/qa-sweep.md +3 -1
- package/commands/quality-gate.md +4 -3
- package/commands/recover-orphans.md +172 -13
- package/commands/review-pr-agents.md +6 -4
- package/commands/review-pr-staging.md +13 -5
- package/commands/review-pr.md +187 -19
- package/commands/rollback.md +3 -1
- package/commands/security-audit.md +3 -1
- package/commands/signal-planner.md +6 -4
- package/commands/sync-ecosystem.md +4 -2
- package/commands/test-gate.md +4 -2
- package/commands/upgrade-deps.md +3 -1
- package/commands/validate.md +3 -1
- package/commands/work-on/build/architect.md +81 -19
- package/commands/work-on/build/context.md +61 -18
- package/commands/work-on/build/implement.md +62 -18
- package/commands/work-on/build/validate.md +240 -60
- package/commands/work-on/build.md +344 -146
- package/commands/work-on/close.md +617 -246
- package/commands/work-on/decompose.md +239 -41
- package/commands/work-on/investigate.md +377 -61
- package/commands/work-on/remediate.md +42 -9
- package/commands/work-on/review.md +240 -29
- package/commands/work-on-monolithic.md +48 -7
- package/commands/work-on.md +180 -2014
- package/docs/CODEX.md +2 -0
- package/docs/CONFIG.md +11 -0
- package/package.json +1 -1
- package/scripts/build-spec-graph.mjs +11 -4
- package/scripts/check-spec-markers.sh +11 -1
- package/scripts/checkbox-sections.test.sh +8 -23
- package/scripts/extract-affected-files.sh +5 -2
- package/scripts/extract-affected-files.test.sh +27 -0
- package/scripts/forge-root.test.sh +176 -0
- package/scripts/is-docs-only.sh +31 -0
- package/scripts/is-docs-only.test.sh +64 -0
- package/scripts/lint-dispatch-prompt.sh +217 -0
- package/scripts/lint-dispatch-prompt.test.sh +307 -0
- package/scripts/validate-spec-graph.sh +3 -2
- package/scripts/verify-phase-trail.sh +282 -0
- package/scripts/verify-phase-trail.test.sh +298 -0
- package/scripts/wait-ci-green.sh +98 -0
- package/scripts/wait-ci-green.test.sh +99 -0
package/commands/milestone.md
CHANGED
|
@@ -8,6 +8,8 @@ install: extras
|
|
|
8
8
|
|
|
9
9
|
# /milestone — Milestone Lifecycle Manager
|
|
10
10
|
|
|
11
|
+
> **Skill names**: `{FORGE_SKILL_PREFIX}` is `forgedock:` (plugin install) or empty (`install.sh`), resolved once per run by `commands/work-on.md` § Skill Name Resolution. If the skill is not found under either name, STOP and report "skill not found" — never run the phase inline.
|
|
12
|
+
|
|
11
13
|
**Input**: $ARGUMENTS
|
|
12
14
|
|
|
13
15
|
Milestones are the top-level planning unit. They group related issues into a shippable feature set. Each milestone gets its own long-lived Git branch (`milestone/{slug}`) where feature PRs accumulate. When the milestone is ready, it ships to `main` as a single reviewed merge.
|
|
@@ -15,7 +17,7 @@ Milestones are the top-level planning unit. They group related issues into a shi
|
|
|
15
17
|
**Hierarchy**: Milestone → Issues → Sub-issues (optional decomposition)
|
|
16
18
|
|
|
17
19
|
**NEVER use plan mode (EnterPlanMode).**
|
|
18
|
-
**NEVER use the Agent tool for issue work** — milestone dispatches issue work via `Skill(skill="work-on", ...)` only. The Agent tool bypasses the Skill pipeline's label state machine, FORGE annotations, and structured review. The Agent tool may be used only for coordination tasks within milestone planning itself (e.g., parallel issue list queries) — never for /work-on sub-dispatch.
|
|
20
|
+
**NEVER use the Agent tool for issue work** — milestone dispatches issue work via `Skill(skill="{FORGE_SKILL_PREFIX}work-on", ...)` only. The Agent tool bypasses the Skill pipeline's label state machine, FORGE annotations, and structured review. The Agent tool may be used only for coordination tasks within milestone planning itself (e.g., parallel issue list queries) — never for /work-on sub-dispatch.
|
|
19
21
|
|
|
20
22
|
<!-- FORGE:SPEC_LOADED — milestone.md loaded and active. Agent is bound by this spec. -->
|
|
21
23
|
|
|
@@ -211,7 +213,7 @@ BODY_EOF
|
|
|
211
213
|
```
|
|
212
214
|
|
|
213
215
|
```
|
|
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}\""))
|
|
216
|
+
ISSUE_SKILL_OUTPUT=$(Skill(skill="{FORGE_SKILL_PREFIX}issue", args="--title \"{fix|feat|refactor}: {issue_title}\" --body-file \"${MILESTONE_ISSUE_BODY_FILE}\" --label \"{type}\" --label \"{priority}\" --milestone \"{TITLE}\""))
|
|
215
217
|
```
|
|
216
218
|
|
|
217
219
|
```bash
|
|
@@ -255,7 +257,7 @@ BODY_EOF
|
|
|
255
257
|
```
|
|
256
258
|
|
|
257
259
|
```
|
|
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}\""))
|
|
260
|
+
ISSUE_SKILL_OUTPUT=$(Skill(skill="{FORGE_SKILL_PREFIX}issue", args="--title \"{SATELLITE_PREFIX}: {fix|feat|refactor}: {issue_title}\" --body-file \"${SATELLITE_ISSUE_BODY_FILE}\" --label \"{type}\" --label \"{priority}\""))
|
|
259
261
|
```
|
|
260
262
|
|
|
261
263
|
**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:
|
|
@@ -514,7 +516,7 @@ ISSUE_EOF
|
|
|
514
516
|
```
|
|
515
517
|
|
|
516
518
|
```
|
|
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\""))
|
|
519
|
+
ISSUE_SKILL_OUTPUT=$(Skill(skill="{FORGE_SKILL_PREFIX}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
520
|
```
|
|
519
521
|
|
|
520
522
|
```bash
|
|
@@ -617,7 +619,7 @@ fi
|
|
|
617
619
|
Use the `/review-pr` skill to review the shipping PR. This is a comprehensive review of the entire feature set:
|
|
618
620
|
|
|
619
621
|
```
|
|
620
|
-
Skill(skill="review-pr", args="{PR_NUMBER}")
|
|
622
|
+
Skill(skill="{FORGE_SKILL_PREFIX}review-pr", args="{PR_NUMBER}")
|
|
621
623
|
```
|
|
622
624
|
|
|
623
625
|
**If the review returns blocking findings (FINDINGS, not PASSED)**:
|
|
@@ -625,7 +627,7 @@ Skill(skill="review-pr", args="{PR_NUMBER}")
|
|
|
625
627
|
- Do NOT restart from Step 1.
|
|
626
628
|
- Fix the blockers on `milestone/{slug}`, push the commits, then re-invoke review on the same PR:
|
|
627
629
|
```
|
|
628
|
-
Skill(skill="review-pr", args="{PR_NUMBER}")
|
|
630
|
+
Skill(skill="{FORGE_SKILL_PREFIX}review-pr", args="{PR_NUMBER}")
|
|
629
631
|
```
|
|
630
632
|
- Step 3 will detect the open PR on the next `/milestone ship {slug}` call and update it in place — the same PR number is preserved, review history is retained, and the reviewer is re-notified.
|
|
631
633
|
- This eliminates the close-and-recreate churn that inflates the failed-PR count and lowers the staging→main pass rate. <!-- Added: forge#1328 -->
|
|
@@ -10,11 +10,11 @@ Read this file at the start of every `/orchestrate` invocation.
|
|
|
10
10
|
|
|
11
11
|
## HARD RULES — READ BEFORE ANYTHING ELSE
|
|
12
12
|
|
|
13
|
-
1. **Every agent MUST invoke `/work-on` via the Skill tool.** You do NOT write implementation prompts. You copy the Phase 4A template verbatim and fill in the `{VARIABLES}`. Nothing else. No custom prompts. No "just read and edit" shortcuts. The Skill tool invocation is what triggers labels, investigation comments, structured review, and trajectory tracking. Without it, the agent's work has no paper trail and is worthless.
|
|
13
|
+
1. **Every agent MUST invoke `/work-on` via the Skill tool.** You do NOT write implementation prompts. You copy the Phase 4A template verbatim and fill in the `{VARIABLES}`. Nothing else. No custom prompts. No "just read and edit" shortcuts. No pre-written diagnoses, verdicts ("likely already resolved"), or fix designs, and no free-text resume prompts — relaunches use the same unmodified template. This is guarded by a best-effort, self-run lint, not a tamper-proof gate: every rendered prompt MUST pass `scripts/lint-dispatch-prompt.sh` before dispatch (Phase 4, Step 4A), and a failing prompt is refused. No hook gates `Agent(...)`, so the orchestrator itself polices this; template slots are exact-matched, but the free-text slots (issue title, `DISPATCH_CONTEXT` block) are only phrase-scanned and can be bypassed by rephrasing, so the verbatim-template rule above still binds even when the lint passes. The Skill tool invocation is what triggers labels, investigation comments, structured review, and trajectory tracking. Without it, the agent's work has no paper trail and is worthless.
|
|
14
14
|
|
|
15
15
|
2. **You are a dispatcher, not a builder.** You resolve issues, build the dependency DAG, spawn agents, and report results. You NEVER read code, edit files, or implement fixes yourself.
|
|
16
16
|
|
|
17
|
-
3. **After each agent completes, verify it used `/work-on`.** Check that completed issues have `workflow:*` labels and structured comments.
|
|
17
|
+
3. **After each agent completes, verify it used `/work-on`.** Check that completed issues have `workflow:*` labels and structured comments. Labels alone are not verification: run `scripts/verify-phase-trail.sh` on every completion (Phase 4, Step 4B). A failing trail (`PHASE_TRAIL: FAIL`, exit 1) is a bypass. Never classify from a worker's own narrative (what it says it skipped or how it dispatched) — only from the trail verdict and labels. An unreadable or unresolvable trail (`PHASE_TRAIL: ERROR`, exit ≥2 / 127) is an infrastructure BLOCKED: add `needs-human` and do not re-dispatch. For a bypass, classify it FAILED (never DONE), report it to the operator, and re-dispatch with the unmodified template so the missing phases run via `Skill(...)`.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
@@ -7,6 +7,8 @@ install: core
|
|
|
7
7
|
|
|
8
8
|
# /orchestrate — Phase 1: Resolve the Issue Set
|
|
9
9
|
|
|
10
|
+
> **Skill names**: `{FORGE_SKILL_PREFIX}` is `forgedock:` (plugin install) or empty (`install.sh`), resolved once per run by `commands/work-on.md` § Skill Name Resolution. If the skill is not found under either name, STOP and report "skill not found" — never run the phase inline.
|
|
11
|
+
|
|
10
12
|
## Phase 1: Resolve the Issue Set
|
|
11
13
|
|
|
12
14
|
### Batch-Start Timestamp (T0) — capture FIRST, before any resolution
|
|
@@ -201,11 +203,32 @@ Phase 1 resolves `$ARGUMENTS` to a concrete issue-number list exactly once, at T
|
|
|
201
203
|
Classify `$ARGUMENTS` and persist that classification alongside the resolved issue-number list, so Phase 4 (`phase-4-execution.md` Step 4B) can decide whether to re-run the query later in the batch:
|
|
202
204
|
|
|
203
205
|
```bash
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
# FORGE_ROOT bootstrap (canonical; keep byte-identical across specs, guarded by scripts/forge-root.test.sh)
|
|
207
|
+
FORGE_ROOT=""
|
|
208
|
+
if [ -n "${FORGEDOCK_HOME:-}" ]; then case "$FORGEDOCK_HOME" in /*) FORGE_ROOT="$FORGEDOCK_HOME" ;; esac; else
|
|
209
|
+
# Portable to bash 3.2 (macOS), BSD/GNU coreutils and zsh: no mapfile, no sort -V, no bare globs (zsh aborts on no match).
|
|
210
|
+
_l="$HOME/.claude/commands/work-on.md"; _l="$(readlink -f "$_l" 2>/dev/null || readlink "$_l" 2>/dev/null)"; [ -n "$_l" ] && _l="$(dirname "$(dirname "$_l")")"
|
|
211
|
+
# newest cached version first: numeric major.minor.patch of the version dir name only; a release outranks its pre-release (1.10.0 > 1.9.0 > 1.9.0-rc1)
|
|
212
|
+
_v="$(find -L "$HOME/.claude/plugins/cache" -mindepth 3 -maxdepth 3 -type d 2>/dev/null | awk -F/ '$(NF-1)=="forgedock"{v=$NF;p=index(v,"-");r=1;if(p){v=substr(v,1,p-1);r=0};split(v,a,".");printf "%d %d %d %d %s\n",a[1],a[2],a[3],r,$0}' | sort -k1,1nr -k2,2nr -k3,3nr -k4,4nr | cut -d' ' -f5-)"
|
|
213
|
+
_m="$(find -L "$HOME/.claude/plugins/marketplaces" -mindepth 1 -maxdepth 1 -type d -iname '*forgedock*' 2>/dev/null)"
|
|
214
|
+
# '${CLAUDE_PLUGIN_ROOT}' is substituted by Claude Code when it loads a plugin spec (the exact spelling only, never as an env var), so a running plugin resolves to its own root first; unsubstituted (other runtimes) it stays a literal that the /* check rejects.
|
|
215
|
+
_k="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_HOME:-}" "$_l" "$_v" "$_m")"
|
|
216
|
+
while IFS= read -r _c; do
|
|
217
|
+
case "$_c" in /*) [ -z "$FORGE_ROOT" ] && [ -f "$_c/scripts/verify-phase-trail.sh" ] && [ -f "$_c/scripts/lint-dispatch-prompt.sh" ] && [ -f "$_c/scripts/is-docs-only.sh" ] && [ -f "$_c/bin/engine/resolve.mjs" ] && [ -f "$_c/bin/engine/orchestrate-canary.mjs" ] && [ -f "$_c/bin/engine/admission.mjs" ] && FORGE_ROOT="$_c" ;; esac
|
|
218
|
+
done <<< "$_k"
|
|
219
|
+
fi
|
|
220
|
+
# Import ForgeDock's OWN resolve.mjs from FORGE_ROOT, never from the consumer {REPO_PATH}
|
|
221
|
+
# (inert on non-ForgeDock repos, and would execute repo-controlled JS). Unresolved/missing => no output,
|
|
222
|
+
# which Phase 4 treats as "literal" (never re-resolve) — fail closed, loudly.
|
|
223
|
+
if [ -n "$FORGE_ROOT" ] && [ -r "$FORGE_ROOT/bin/engine/resolve.mjs" ]; then
|
|
224
|
+
node -e '
|
|
225
|
+
import(require("node:url").pathToFileURL(process.argv[1]).href).then(({ classifyInputPattern }) => {
|
|
226
|
+
console.log(JSON.stringify(classifyInputPattern(process.argv[2])));
|
|
227
|
+
}, () => process.exit(0));
|
|
228
|
+
' "$FORGE_ROOT/bin/engine/resolve.mjs" "$ARGUMENTS"
|
|
229
|
+
else
|
|
230
|
+
echo "WARNING: ForgeDock install root unresolved or resolve.mjs missing (set FORGEDOCK_HOME) — input pattern left unclassified (treated as literal; no re-resolution)" >&2
|
|
231
|
+
fi
|
|
209
232
|
```
|
|
210
233
|
|
|
211
234
|
This mirrors `bin/engine/resolve.mjs`'s `classifyInputPattern` — a typed, unit-tested reference implementation of the same literal-vs-query rule table shown here (see that file's docstring). As with `admission.mjs` for cascade policy, the two must stay in sync by hand: any change to which patterns count as `literal` vs `query` in this table must be mirrored in `resolve.mjs`, and vice versa.
|
|
@@ -363,12 +386,33 @@ After executing the plan, retain every singleton in the registry for the next ad
|
|
|
363
386
|
# createdAt, and isBatch. OPEN_BATCHES_JSON contains existing batch issue metadata.
|
|
364
387
|
# Run this after initial resolution and every later admission event; execute returned
|
|
365
388
|
# actions using memberIds, never bare issue numbers, so satellite issues cannot collide.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
389
|
+
# FORGE_ROOT bootstrap (canonical; keep byte-identical across specs, guarded by scripts/forge-root.test.sh)
|
|
390
|
+
FORGE_ROOT=""
|
|
391
|
+
if [ -n "${FORGEDOCK_HOME:-}" ]; then case "$FORGEDOCK_HOME" in /*) FORGE_ROOT="$FORGEDOCK_HOME" ;; esac; else
|
|
392
|
+
# Portable to bash 3.2 (macOS), BSD/GNU coreutils and zsh: no mapfile, no sort -V, no bare globs (zsh aborts on no match).
|
|
393
|
+
_l="$HOME/.claude/commands/work-on.md"; _l="$(readlink -f "$_l" 2>/dev/null || readlink "$_l" 2>/dev/null)"; [ -n "$_l" ] && _l="$(dirname "$(dirname "$_l")")"
|
|
394
|
+
# newest cached version first: numeric major.minor.patch of the version dir name only; a release outranks its pre-release (1.10.0 > 1.9.0 > 1.9.0-rc1)
|
|
395
|
+
_v="$(find -L "$HOME/.claude/plugins/cache" -mindepth 3 -maxdepth 3 -type d 2>/dev/null | awk -F/ '$(NF-1)=="forgedock"{v=$NF;p=index(v,"-");r=1;if(p){v=substr(v,1,p-1);r=0};split(v,a,".");printf "%d %d %d %d %s\n",a[1],a[2],a[3],r,$0}' | sort -k1,1nr -k2,2nr -k3,3nr -k4,4nr | cut -d' ' -f5-)"
|
|
396
|
+
_m="$(find -L "$HOME/.claude/plugins/marketplaces" -mindepth 1 -maxdepth 1 -type d -iname '*forgedock*' 2>/dev/null)"
|
|
397
|
+
# '${CLAUDE_PLUGIN_ROOT}' is substituted by Claude Code when it loads a plugin spec (the exact spelling only, never as an env var), so a running plugin resolves to its own root first; unsubstituted (other runtimes) it stays a literal that the /* check rejects.
|
|
398
|
+
_k="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_HOME:-}" "$_l" "$_v" "$_m")"
|
|
399
|
+
while IFS= read -r _c; do
|
|
400
|
+
case "$_c" in /*) [ -z "$FORGE_ROOT" ] && [ -f "$_c/scripts/verify-phase-trail.sh" ] && [ -f "$_c/scripts/lint-dispatch-prompt.sh" ] && [ -f "$_c/scripts/is-docs-only.sh" ] && [ -f "$_c/bin/engine/resolve.mjs" ] && [ -f "$_c/bin/engine/orchestrate-canary.mjs" ] && [ -f "$_c/bin/engine/admission.mjs" ] && FORGE_ROOT="$_c" ;; esac
|
|
401
|
+
done <<< "$_k"
|
|
402
|
+
fi
|
|
403
|
+
# Import ForgeDock's OWN admission.mjs from FORGE_ROOT, never from the consumer {REPO_PATH}.
|
|
404
|
+
# Unresolved/missing => no plan: every candidate stays an ungrouped singleton (fail closed, loudly).
|
|
405
|
+
BATCH_PLAN=""
|
|
406
|
+
if [ -n "$FORGE_ROOT" ] && [ -r "$FORGE_ROOT/bin/engine/admission.mjs" ]; then
|
|
407
|
+
BATCH_PLAN=$(node -e '
|
|
408
|
+
import(require("node:url").pathToFileURL(process.argv[1]).href).then(({ planP3Batches, summarizeP3BatchPlan }) => {
|
|
409
|
+
const plan = planP3Batches({ candidates: JSON.parse(process.argv[2]), openBatches: JSON.parse(process.argv[3]) });
|
|
410
|
+
console.log(JSON.stringify({ plan, summary: summarizeP3BatchPlan(plan) }));
|
|
411
|
+
});
|
|
412
|
+
' "$FORGE_ROOT/bin/engine/admission.mjs" "$BATCH_CANDIDATE_REGISTRY_JSON" "$OPEN_BATCHES_JSON")
|
|
413
|
+
else
|
|
414
|
+
echo "WARNING: ForgeDock install root unresolved or admission.mjs missing (set FORGEDOCK_HOME) — no P3 batch plan; candidates retained as ungrouped singletons" >&2
|
|
415
|
+
fi
|
|
372
416
|
```
|
|
373
417
|
|
|
374
418
|
**Priority label schema**: ForgeDock's own issue creator (`review-pr.md`) writes only the canonical `priority:P<n>` label form. Some repos this pipeline operates against (issues opened externally, imported, or predating ForgeDock adoption) instead carry a bare `P<n>` label with no `priority:` prefix. Every priority-read check in this section — and its mirrors in `phase-4-execution.md` and `cleanup.md` — MUST accept both forms. `priority:P<n>` wins when (unusually) both are present on the same issue. Issue-*creation* call sites (the batch-issue creation below) are unaffected and keep writing canonical `priority:P<n>` only — there is no case where this pipeline needs to *write* the bare form. <!-- Added: forge#2232 -->
|
|
@@ -509,7 +553,7 @@ BATCH_EOF
|
|
|
509
553
|
```
|
|
510
554
|
|
|
511
555
|
```
|
|
512
|
-
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\" --exclude \"${MEMBER_LIST}\""))
|
|
556
|
+
ISSUE_SKILL_OUTPUT=$(Skill(skill="{FORGE_SKILL_PREFIX}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\" --exclude \"${MEMBER_LIST}\""))
|
|
513
557
|
```
|
|
514
558
|
|
|
515
559
|
**Consume `/issue`'s explicit result contract** (see `commands/issue.md` Phases 2D and 4C). A dedup STOP is an expected, named outcome; any output without either result marker is a hard create failure:
|
|
@@ -6,6 +6,8 @@ install: core
|
|
|
6
6
|
|
|
7
7
|
# /orchestrate — Phase 3: Dependency Analysis & Execution Plan
|
|
8
8
|
|
|
9
|
+
> **Skill names**: `{FORGE_SKILL_PREFIX}` is `forgedock:` (plugin install) or empty (`install.sh`), resolved once per run by `commands/work-on.md` § Skill Name Resolution. If the skill is not found under either name, STOP and report "skill not found" — never run the phase inline.
|
|
10
|
+
|
|
9
11
|
## Phase 3: Dependency Analysis & Execution Plan
|
|
10
12
|
|
|
11
13
|
### Step 3A: Analyze explicit dependencies
|
|
@@ -88,6 +90,7 @@ declare -A ISSUE_FILES # {NUM} → newline-separated declared file set (forge#2
|
|
|
88
90
|
# phase-4-execution.md's classify-lane resolver.
|
|
89
91
|
resolve_extract_affected_files() {
|
|
90
92
|
local candidates=()
|
|
93
|
+
case '${CLAUDE_PLUGIN_ROOT}' in /*) candidates+=('${CLAUDE_PLUGIN_ROOT}/scripts/extract-affected-files.sh') ;; esac # running plugin first
|
|
91
94
|
[ -n "${FORGE_HOME:-}" ] && candidates+=("$FORGE_HOME/scripts/extract-affected-files.sh")
|
|
92
95
|
[ -n "${REPO_PATH:-}" ] && candidates+=("$REPO_PATH/scripts/extract-affected-files.sh")
|
|
93
96
|
candidates+=("$PWD/scripts/extract-affected-files.sh")
|
|
@@ -617,7 +620,7 @@ if [ -n "${FORGE_COORD_ISSUE:-}" ] && [ -n "${COORD_ISSUE_NUMBER:-}" ]; then
|
|
|
617
620
|
|
|
618
621
|
case "$LEASE_STATE" in
|
|
619
622
|
free|self)
|
|
620
|
-
HOSTNAME_ID=$(hostname 2>/dev/null ||
|
|
623
|
+
HOSTNAME_ID=$(hostname 2>/dev/null || uname -n 2>/dev/null || true); HOSTNAME_ID="${HOSTNAME_ID:-unknown-host}"
|
|
621
624
|
# GOVERNOR-exempt: intentional coordination side-effect (best-effort lease/board/finding post), DRY_RUN-safe — reviewed & accepted for the check-command-side-effects gate. Flagged only by the staging->main full-diff; passes on every feature PR. forge#2627
|
|
622
625
|
gh issue comment "$COORD_ISSUE_NUMBER" -R {GH_REPO} --body "<!-- FORGE:LEASE -->
|
|
623
626
|
**Holder Batch ID**: ${BATCH_ID}
|
|
@@ -1140,7 +1143,7 @@ The engine drives every phase transition deterministically, mirrors state to the
|
|
|
1140
1143
|
|
|
1141
1144
|
**Fallback — best-effort, not all-or-nothing (fixed forge#2743)**: Engine-first is guaranteed to have an Agent-spawn fallback under two distinct trigger conditions, not just CLI absence:
|
|
1142
1145
|
|
|
1143
|
-
1. **CLI absent at dispatch time**: If `forgedock` is not in PATH, `phase-4-execution.md` Step 4A falls back to spawning Agent sub-agents that run `Skill("work-on", ...)` per issue for the whole batch, before any dispatch happens.
|
|
1146
|
+
1. **CLI absent at dispatch time**: If `forgedock` is not in PATH, `phase-4-execution.md` Step 4A falls back to spawning Agent sub-agents that run `Skill("{FORGE_SKILL_PREFIX}work-on", ...)` per issue for the whole batch, before any dispatch happens.
|
|
1144
1147
|
2. **Backend unavailable despite CLI presence, or a runtime engine-error with empty committed state**: `command -v forgedock` only proves the orchestrator CLI is installed — it says nothing about whether the engine's execution backend (the `claude` CLI spawn) can actually run a phase (forge#2741 is a concrete case: `spawnSync claude` ENOENTs even though a shell `command -v` probe reports the binary present). Step 4A now runs a cheap backend preflight canary before committing the whole ready set to engine-first, downgrading the entire run to Agent-spawn on failure. And per-issue, Step 4B's completion handler auto-falls-back any individual engine-dispatched issue that completes at `workflow:engine-error` with an empty committed state (`committed=[] branch=null pr=null` — see `bin/engine-cli.mjs`'s `formatTerminalDiagnostics()`) to the same Agent-spawn template, rather than leaving it stuck waiting on a resume mechanism that only exists for Agent-spawn-dispatched issues. An engine-error with partial committed state (branch/PR non-null) is NOT auto-fallen-back, to avoid double-work — it surfaces via the existing stall-detection alert instead.
|
|
1145
1148
|
|
|
1146
1149
|
In both cases the SubagentStop hook still bridges the fallback Agent-spawn runs to the engine run-log for state persistence. Engine-first is therefore best-effort: it is always backed by a working Agent-spawn path, whether the gap is discovered before dispatch (canary) or after (per-issue fallback) — a whole ready set is never permanently lost to an environmental engine failure.
|
|
@@ -1365,7 +1368,7 @@ if [ -n "${FORGE_COORD_ISSUE:-}" ] && [ -n "${COORD_ISSUE_NUMBER:-}" ] && [ -n "
|
|
|
1365
1368
|
free|self)
|
|
1366
1369
|
# Free (no live holder) or self (this exact batch already holds it) — safe to
|
|
1367
1370
|
# refresh and continue reconstruction below.
|
|
1368
|
-
HOSTNAME_ID=$(hostname 2>/dev/null ||
|
|
1371
|
+
HOSTNAME_ID=$(hostname 2>/dev/null || uname -n 2>/dev/null || true); HOSTNAME_ID="${HOSTNAME_ID:-unknown-host}"
|
|
1369
1372
|
# GOVERNOR-exempt: intentional coordination side-effect (best-effort lease/board/finding post), DRY_RUN-safe — reviewed & accepted for the check-command-side-effects gate. Flagged only by the staging->main full-diff; passes on every feature PR. forge#2627
|
|
1370
1373
|
gh issue comment "$COORD_ISSUE_NUMBER" -R {GH_REPO} --body "<!-- FORGE:LEASE -->
|
|
1371
1374
|
**Holder Batch ID**: ${BATCH_ID}
|
|
@@ -1386,6 +1389,10 @@ fi
|
|
|
1386
1389
|
# 1. Re-fetch all issue labels and classify each into DONE / GATED / FAILED / IN_PROGRESS
|
|
1387
1390
|
# (same classify_predecessor_state() function defined in phase-4-execution.md Step 4B —
|
|
1388
1391
|
# re-declare it here if this block runs in a fresh context that hasn't sourced Step 4B yet).
|
|
1392
|
+
# forge#3168: classify_predecessor_state() also needs these Step 4B helpers declared in the SAME shell:
|
|
1393
|
+
# hold_merged_trail, resolve_orch_login, trail_escalation_state, release_merged_trail (reverify_merged_trail is
|
|
1394
|
+
# nested inside the classifier). It reports a missing helper on stderr and classifies GATED. The same trusted-author
|
|
1395
|
+
# filter (OWNER/MEMBER/COLLABORATOR or the orchestrator login) applies to the release/fail markers on wake.
|
|
1389
1396
|
declare -A ISSUE_CLASS
|
|
1390
1397
|
declare -A ISSUE_FILES
|
|
1391
1398
|
DONE_ISSUES=()
|
|
@@ -1486,7 +1493,7 @@ for ENTRY in "${NEWLY_BLOCKED[@]:-}"; do
|
|
|
1486
1493
|
--json number --jq '.[0].number // empty' 2>/dev/null || echo "")
|
|
1487
1494
|
gh issue comment "$DEP" -R {GH_REPO} --body "<!-- FORGE:BLOCKED_ON_HUMAN_MERGE -->
|
|
1488
1495
|
**Gating predecessor**: #${PRED} (state: \`${ISSUE_CLASS[$PRED]}\`${GATING_PR:+, open PR #${GATING_PR}})
|
|
1489
|
-
**Status**: Detected on orchestrator wake/compaction reconstruction. Ready to dispatch
|
|
1496
|
+
**Status**: Detected on orchestrator wake/compaction reconstruction. Ready to dispatch once #${PRED} is resolved: when its gating PR merges, or — if #${PRED} is already merged and held on its phase trail — when a human clears \`needs-human\` on it."
|
|
1490
1497
|
gh issue edit "$DEP" -R {GH_REPO} --add-label "blocked-on-human-merge" 2>/dev/null || true
|
|
1491
1498
|
fi
|
|
1492
1499
|
done
|
|
@@ -1507,9 +1514,12 @@ for DEP in $BLOCKED_NOW; do
|
|
|
1507
1514
|
[ "$GPRED_CLASS" != "DONE" ] && STILL_GATED=true
|
|
1508
1515
|
done
|
|
1509
1516
|
if [ "$STILL_GATED" = "false" ]; then
|
|
1517
|
+
# forge#3157: record a human release (FORGE:PHASE_TRAIL_RELEASED) of a trail-held merged predecessor (decay of the escalation marker;
|
|
1518
|
+
# release_merged_trail is defined in phase-4-execution.md Step 4B and is a no-op unless the escalation is ACTIVE).
|
|
1519
|
+
for GPRED in $(echo "$GATING_PREDS_RAW" | jq -r '.[]' 2>/dev/null); do release_merged_trail "$GPRED"; done
|
|
1510
1520
|
gh issue edit "$DEP" -R {GH_REPO} --remove-label "blocked-on-human-merge" 2>/dev/null || true
|
|
1511
1521
|
gh issue comment "$DEP" -R {GH_REPO} --body "<!-- FORGE:UNBLOCKED -->
|
|
1512
|
-
All gating predecessor(s)
|
|
1522
|
+
All gating predecessor(s) are now resolved — merged, or released by a human after a phase-trail hold (detected on orchestrator wake) — dispatching now."
|
|
1513
1523
|
READY_ISSUES+=("$DEP")
|
|
1514
1524
|
fi
|
|
1515
1525
|
done
|