forgedock 1.8.1 → 1.8.3
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/design-bench.md +1 -0
- package/commands/design-render-critique-loop.md +1 -0
- package/commands/design.md +1 -0
- package/commands/forge-stats.md +1 -0
- 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 +3 -2
- package/commands/orchestrate/phase-1-resolve.md +57 -12
- package/commands/orchestrate/phase-2-triage.md +1 -0
- package/commands/orchestrate/phase-2.5-synthesis.md +1 -0
- package/commands/orchestrate/phase-3-dependency.md +17 -5
- package/commands/orchestrate/phase-4-execution.md +625 -78
- package/commands/orchestrate/phase-5-cleanup.md +5 -2
- package/commands/orchestrate/phase-6-report.md +8 -3
- package/commands/orchestrate/safety.md +1 -0
- package/commands/orchestrate.md +13 -9
- package/commands/pipeline-health/config.md +1 -0
- package/commands/pipeline-health/phase-1-context.md +1 -0
- package/commands/pipeline-health/phase-2-metrics.md +1 -0
- package/commands/pipeline-health/phase-3-analyze.md +1 -0
- package/commands/pipeline-health/phase-4-proposals.md +7 -4
- package/commands/pipeline-health/phase-5-report.md +5 -2
- package/commands/pipeline-health/phase-6-summary.md +1 -0
- 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 +219 -13
- package/commands/review-pr-agents/api.md +1 -0
- package/commands/review-pr-agents/auth.md +1 -0
- package/commands/review-pr-agents/billing.md +1 -0
- package/commands/review-pr-agents/concurrency.md +1 -0
- package/commands/review-pr-agents/database.md +1 -0
- package/commands/review-pr-agents/frontend.md +1 -0
- package/commands/review-pr-agents/infra.md +1 -0
- package/commands/review-pr-agents/protocols.md +1 -0
- package/commands/review-pr-agents/scraper.md +1 -0
- package/commands/review-pr-agents/security.md +1 -0
- package/commands/review-pr-agents/spec-cli.md +1 -0
- package/commands/review-pr-agents.md +10 -4
- package/commands/review-pr-staging.md +14 -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 +82 -19
- package/commands/work-on/build/context.md +62 -18
- package/commands/work-on/build/implement.md +63 -18
- package/commands/work-on/build/validate.md +241 -60
- package/commands/work-on/build.md +345 -146
- package/commands/work-on/close.md +618 -246
- package/commands/work-on/decompose.md +240 -41
- package/commands/work-on/investigate.md +378 -61
- package/commands/work-on/remediate.md +43 -9
- package/commands/work-on/review.md +241 -29
- 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 +21 -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/work-on-monolithic.md +0 -326
|
@@ -1,51 +1,141 @@
|
|
|
1
1
|
---
|
|
2
|
+
user-invocable: false
|
|
2
3
|
description: Build subcommand — create worktree, post contract, sequence context/architect/implement/validate
|
|
3
|
-
|
|
4
|
+
context: fork
|
|
5
|
+
argument-hint: "{NUMBER} --repo {GH_REPO} --gh-flag \"{GH_FLAG}\" --base {PR_BASE}"
|
|
4
6
|
---
|
|
5
7
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
8
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
7
9
|
|
|
8
10
|
# work-on/build — Build Phase Orchestrator
|
|
9
11
|
|
|
12
|
+
> **Skill Name Resolution (forked phase)**: `{FORGE_SKILL_PREFIX}` is the namespace this skill itself was invoked under — invoked as `forgedock:work-on:…` → `forgedock:` (nesting `:`); as `work-on:…` → empty (`install.sh`); as `forge-work-on-…` → `forge-` (Codex, nesting `-`); OpenCode → empty with `-` nesting. Confirm the target name in the available-skills list before calling it. A forked phase receives no resolved value from its caller; never guess, and if the target skill is not listed return BLOCKED "skill not found: <name>".
|
|
13
|
+
|
|
10
14
|
**Input**: $ARGUMENTS
|
|
11
15
|
|
|
12
|
-
**Invoked by**:
|
|
13
|
-
**Output**: Create worktree, post contract, run
|
|
16
|
+
**Invoked by**: the work-on router, when the issue carries label `workflow:ready-to-build` or `workflow:building`. This skill runs in an isolated forked context: it sees only its args and re-reads everything else from GitHub and git.
|
|
17
|
+
**Output**: Create worktree, classify complexity, post contract, run the child phases (context, architect, implement, validate) through `Skill()`, run the acceptance gate, and print exactly one `BUILD_RESULT:` block as the final reply.
|
|
14
18
|
|
|
15
|
-
**Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154. This file's mechanical bits (
|
|
19
|
+
**Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154. This file's mechanical bits (classification, label transitions) stay at this tier because they are interleaved with the reasoning-heavy child phases in the same run. <!-- Added: forge#1827 -->
|
|
16
20
|
**NEVER use plan mode (EnterPlanMode).**
|
|
17
21
|
|
|
18
|
-
**CRITICAL: You MUST execute ALL phases B0–B6 in order.
|
|
22
|
+
**CRITICAL: You MUST execute ALL phases B0–B6.5 in order. Every child (B3 context, B4 architect, B5 implement, B6 validate) is invoked via `Skill(...)` — each is a forked sub-skill with its own isolated context. B3 and B4 are invoked for every complexity band except where the band is TRIVIAL or INVESTIGATION (B2/B2.1/B2.5 are also skipped for INVESTIGATION); the children post their own skip markers, so a skipped child is still visible on the issue. Skipping a child without the band justification degrades build quality and fails the phase-trail check.**
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
<!-- FORGE:SPEC_LOADED — work-on/build.md loaded and active. Agent is bound by this spec. -->
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
---
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
## Inputs
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
Parse from $ARGUMENTS:
|
|
31
|
+
- `{NUMBER}` — issue number (required)
|
|
32
|
+
- `--repo {GH_REPO}` — GitHub repo (e.g. `{owner}/{repo}`) (required)
|
|
33
|
+
- `--gh-flag {GH_FLAG}` — gh CLI repo flag (e.g. `-R {owner}/{repo}`) (required)
|
|
34
|
+
- `--base {PR_BASE}` — PR target branch, computed by the router (e.g. `milestone/modular-pipeline-architecture` or `staging`) (required)
|
|
35
|
+
- `--worktree {WORKTREE_PATH}` / `--branch {BRANCH}` — optional hints from a router re-invocation; when present and consistent with the issue they are reused, otherwise they are re-derived in B1
|
|
27
36
|
|
|
28
|
-
|
|
29
|
-
|------|------|--------|
|
|
30
|
-
| **STANDARD/fast-lane (default)** | All issues not matching exceptions below | B0 → B1 → B2 → B2.5 → [B3] → [B4] → B5 → B6 — all inline |
|
|
31
|
-
| **Spawn exception (Row c)** | ≥20 Skill invocations OR ≥10 files changed before build | Spawn B3/B4 as fresh sub-agents via `Skill()` |
|
|
32
|
-
| **TRIVIAL fast-path** | COMPLEXITY_BAND: TRIVIAL | Skip B3 and B4 entirely |
|
|
37
|
+
**Fail closed**: if `{NUMBER}`, `--repo` or `--gh-flag` is missing, or `--base` is missing or empty, take the Blocked exit below with blocker "missing required arg: <name>" — never guess a base branch (no fallback to `staging`/`main`).
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
---
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
## Script resolution
|
|
42
|
+
|
|
43
|
+
**Shell state does not persist between Bash tool calls** (each call is a fresh shell): paste the block below at the top of every bash command in this skill that calls `resolve_script` or uses `FORGE_ROOT`/`UNIVERSAL_DIR`.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Canonical script resolution (keep byte-identical across specs; guarded by scripts/forge-root.test.sh).
|
|
47
|
+
# Shell state does NOT persist between Bash tool calls: include this whole block at the top of
|
|
48
|
+
# every command that uses resolve_script or FORGE_ROOT.
|
|
49
|
+
REPO_PATH="${REPO_PATH:-$(yq '.paths.root // ""' forge.yaml 2>/dev/null)}"
|
|
50
|
+
[ -n "$REPO_PATH" ] && [ "$REPO_PATH" != "null" ] || REPO_PATH="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
51
|
+
ADAPTIVE_DIR_RAW="${REPO_PATH}/$(yq '.adaptive_scripts.directory // ".forgedock/scripts"' forge.yaml 2>/dev/null || echo '.forgedock/scripts')"
|
|
52
|
+
ADAPTIVE_DIR=$(realpath -m "$ADAPTIVE_DIR_RAW" 2>/dev/null || echo "$ADAPTIVE_DIR_RAW")
|
|
53
|
+
ADAPTIVE_ENABLED=$(yq '.adaptive_scripts.enabled // "true"' forge.yaml 2>/dev/null || echo 'true')
|
|
54
|
+
# Bounds check: reject adaptive_scripts.directory values that escape the repo root.
|
|
55
|
+
# Normalize REPO_PATH the same way ADAPTIVE_DIR is normalized (realpath -m) so a trailing
|
|
56
|
+
# slash in paths.root does not inject a '//' into the glob and trigger a false positive.
|
|
57
|
+
REPO_PATH_NORM=$(realpath -m "$REPO_PATH" 2>/dev/null || echo "$REPO_PATH")
|
|
58
|
+
if [[ "$ADAPTIVE_DIR" != "${REPO_PATH_NORM}/"* ]]; then
|
|
59
|
+
echo "WARNING: adaptive_scripts.directory resolves outside repo root ('$ADAPTIVE_DIR') — adaptive tier disabled" >&2
|
|
60
|
+
ADAPTIVE_ENABLED=false
|
|
61
|
+
fi
|
|
62
|
+
# ForgeDock's own install root (holds scripts/ and commands/), resolved ONCE by the canonical bootstrap
|
|
63
|
+
# below — never the consumer repo: plugin installs set neither FORGE_HOME nor FORGEDOCK_HOME, and a
|
|
64
|
+
# repo-relative fallback would execute (or miss) a same-named script controlled by the consumer repo.
|
|
65
|
+
# Resolution: $FORGEDOCK_HOME (authoritative when set) > $FORGE_HOME > $CLAUDE_PLUGIN_ROOT > the
|
|
66
|
+
# ~/.claude/commands symlink target > the Claude Code plugin cache/marketplace dirs. <!-- forge#3098 -->
|
|
67
|
+
# FORGE_ROOT bootstrap (canonical; keep byte-identical across specs, guarded by scripts/forge-root.test.sh)
|
|
68
|
+
FORGE_ROOT=""
|
|
69
|
+
if [ -n "${FORGEDOCK_HOME:-}" ]; then case "$FORGEDOCK_HOME" in /*) FORGE_ROOT="$FORGEDOCK_HOME" ;; esac; else
|
|
70
|
+
# Portable to bash 3.2 (macOS), BSD/GNU coreutils and zsh: no mapfile, no sort -V, no bare globs (zsh aborts on no match).
|
|
71
|
+
_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")")"
|
|
72
|
+
# 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)
|
|
73
|
+
_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-)"
|
|
74
|
+
_m="$(find -L "$HOME/.claude/plugins/marketplaces" -mindepth 1 -maxdepth 1 -type d -iname '*forgedock*' 2>/dev/null)"
|
|
75
|
+
# '${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.
|
|
76
|
+
_k="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_HOME:-}" "$_l" "$_v" "$_m")"
|
|
77
|
+
while IFS= read -r _c; do
|
|
78
|
+
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
|
|
79
|
+
done <<< "$_k"
|
|
80
|
+
fi
|
|
81
|
+
UNIVERSAL_DIR="${FORGE_ROOT:+$FORGE_ROOT/scripts}" # empty => tier 3 skipped, prose tier
|
|
82
|
+
# NOTE: never resolve this via `which` or `find` — universal scripts are
|
|
83
|
+
# install-relative, not installed on $PATH, so a PATH lookup always misses.
|
|
84
|
+
# FORGE_ROOT (above) is the deterministic resolution; there is NO repo-path fallback.
|
|
85
|
+
# Pipeline agents MUST NOT use `find` (unbounded or filesystem-wide) to
|
|
86
|
+
# locate pipeline scripts under any circumstances: if UNIVERSAL_DIR/${operation}.sh
|
|
87
|
+
# does not exist, resolve_script() falls through to Tier 4 (prose) below,
|
|
88
|
+
# which is always safe and available. A missing script is never a reason
|
|
89
|
+
# to search the filesystem. <!-- Added: forge#1984 -->
|
|
90
|
+
|
|
91
|
+
resolve_script() {
|
|
92
|
+
local operation="$1"
|
|
93
|
+
# Tier 2: per-repo adaptive (skip if disabled)
|
|
94
|
+
if [ "$ADAPTIVE_ENABLED" != "false" ] && [ -f "${ADAPTIVE_DIR}/${operation}.sh" ]; then
|
|
95
|
+
echo "adaptive:${ADAPTIVE_DIR}/${operation}.sh"
|
|
96
|
+
return
|
|
97
|
+
fi
|
|
98
|
+
# Tier 3: universal script
|
|
99
|
+
if [ -n "$UNIVERSAL_DIR" ] && [ -f "${UNIVERSAL_DIR}/${operation}.sh" ]; then
|
|
100
|
+
echo "universal:${UNIVERSAL_DIR}/${operation}.sh"
|
|
101
|
+
return
|
|
102
|
+
fi
|
|
103
|
+
# Tier 4: prose fallback
|
|
104
|
+
echo "prose:"
|
|
105
|
+
}
|
|
106
|
+
```
|
|
37
107
|
|
|
38
108
|
---
|
|
39
109
|
|
|
40
|
-
##
|
|
110
|
+
## Result emission and Blocked exit
|
|
41
111
|
|
|
42
|
-
|
|
43
|
-
- `{NUMBER}` — issue number (required)
|
|
44
|
-
- `--repo {GH_REPO}` — GitHub repo (e.g. `{owner}/{repo}` — resolved from `forge.yaml → project`)
|
|
45
|
-
- `--gh-flag {GH_FLAG}` — gh CLI repo flag (e.g. `-R {owner}/{repo}`)
|
|
46
|
-
- `--base {PR_BASE}` — PR target branch (e.g. `milestone/modular-pipeline-architecture` or `staging`)
|
|
112
|
+
Every exit path of this skill — COMPLETE, ALREADY_DONE, INVESTIGATION_COMPLETE, every guard, every failure — ends by printing exactly one block as the final reply:
|
|
47
113
|
|
|
48
|
-
|
|
114
|
+
```
|
|
115
|
+
BUILD_RESULT:
|
|
116
|
+
status: COMPLETE | ALREADY_DONE | INVESTIGATION_COMPLETE | BLOCKED
|
|
117
|
+
branch: {BRANCH}
|
|
118
|
+
worktree: {WORKTREE_PATH}
|
|
119
|
+
blocker: {description if status=BLOCKED, else empty}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`branch` / `worktree` are empty when the build stopped before B1 created them.
|
|
123
|
+
|
|
124
|
+
Every `BLOCKED` exit first runs this procedure (set `BLOCKER` to the reason text), then prints the block with `status: BLOCKED`:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
BLOCKER="{reason}"
|
|
128
|
+
run() { if [ -n "${DRY_RUN:-}" ]; then echo "DRY_RUN: $*"; else "$@"; fi; }
|
|
129
|
+
run gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human" 2>/dev/null || true
|
|
130
|
+
run gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:BUILD_BLOCKED -->
|
|
131
|
+
## Build Blocked
|
|
132
|
+
|
|
133
|
+
${BLOCKER}
|
|
134
|
+
|
|
135
|
+
Human attention required (needs-human)." 2>/dev/null || true
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
(When the arguments themselves are missing and no `gh` call is possible, skip the procedure and print the block only.)
|
|
49
139
|
|
|
50
140
|
---
|
|
51
141
|
|
|
@@ -57,109 +147,193 @@ Re-read current state before doing anything:
|
|
|
57
147
|
gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state,milestone
|
|
58
148
|
|
|
59
149
|
# Check investigation report
|
|
60
|
-
gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
150
|
+
gh api repos/{GH_REPO}/issues/{NUMBER}/comments --paginate \
|
|
61
151
|
--jq '.[] | select(.body | contains("FORGE:INVESTIGATOR")) | .body'
|
|
62
152
|
|
|
63
|
-
# Check if build already completed
|
|
64
|
-
gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
153
|
+
# Check if build already completed (require FORGE:BUILDER:COMPLETE — not just FORGE:BUILDER)
|
|
154
|
+
gh api repos/{GH_REPO}/issues/{NUMBER}/comments --paginate \
|
|
65
155
|
--jq '.[] | select(.body | contains("FORGE:BUILDER")) | .body'
|
|
156
|
+
|
|
157
|
+
# Existing classification from a prior run (resume path)
|
|
158
|
+
EXISTING_FAST_PATH=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments --paginate \
|
|
159
|
+
--jq '.[] | select(.body | contains("FORGE:FAST_PATH")) | .body' 2>/dev/null | head -1)
|
|
66
160
|
```
|
|
67
161
|
|
|
68
162
|
**Resume check**:
|
|
69
|
-
- If `<!-- FORGE:BUILDER:COMPLETE -->` is present in a BUILDER comment → build already complete.
|
|
70
|
-
- If `<!-- FORGE:BUILDER -->` exists BUT `<!-- FORGE:BUILDER:COMPLETE -->` is ABSENT → build was interrupted after the comment was posted but before the commit (validate
|
|
163
|
+
- If `<!-- FORGE:BUILDER:COMPLETE -->` is present in a BUILDER comment → build already complete. Derive BRANCH/WORKTREE_PATH as in B1A/B1C (without creating anything), then print `BUILD_RESULT: status: ALREADY_DONE`.
|
|
164
|
+
- If `<!-- FORGE:BUILDER -->` exists BUT `<!-- FORGE:BUILDER:COMPLETE -->` is ABSENT → build was interrupted after the comment was posted but before the commit (validate V5). Delete the partial comment and restart from Phase B2 (contract): <!-- Added: forge#1305 -->
|
|
71
165
|
```bash
|
|
72
|
-
PARTIAL_ID=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
166
|
+
PARTIAL_ID=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments --paginate \
|
|
73
167
|
--jq '[.[] | select(.body | contains("FORGE:BUILDER") and (contains("FORGE:BUILDER:COMPLETE") | not))] | last | .id // ""')
|
|
74
168
|
if [ -n "$PARTIAL_ID" ]; then
|
|
75
169
|
gh api repos/{GH_REPO}/issues/comments/$PARTIAL_ID -X DELETE
|
|
76
170
|
echo "Deleted partial FORGE:BUILDER comment (no FORGE:BUILDER:COMPLETE) — restarting build from Phase B2"
|
|
77
171
|
fi
|
|
78
172
|
```
|
|
79
|
-
- If no `<!-- FORGE:INVESTIGATOR -->` comment with `<!-- INVESTIGATION:COMPLETE -->` →
|
|
173
|
+
- If there is no `<!-- FORGE:INVESTIGATOR -->` comment with `<!-- INVESTIGATION:COMPLETE -->` → Blocked exit with blocker "Investigation not complete — run investigate first".
|
|
80
174
|
|
|
81
|
-
Extract from investigation report:
|
|
82
|
-
- Affected files list
|
|
175
|
+
Extract from the investigation report:
|
|
176
|
+
- Affected files list → `{AFFECTED_FILES}` (space-separated repo-relative paths; this is what B3/B4 receive via `--files`)
|
|
83
177
|
- Root cause
|
|
84
178
|
- Recommendation
|
|
85
179
|
- Task type (Bug Fix / Feature / Refactor / Maintenance / UI/UX / Full-Stack)
|
|
86
180
|
|
|
87
|
-
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Phase B0.5: Classify Task Type and Complexity (MANDATORY — build owns this) <!-- Added: forge#1380 -->
|
|
184
|
+
|
|
185
|
+
Build classifies the task and posts the `FORGE:FAST_PATH` comment. `scripts/verify-phase-trail.sh` fails the review preflight if it is missing.
|
|
186
|
+
|
|
187
|
+
**Resume path**: If `EXISTING_FAST_PATH` is non-empty, extract COMPLEXITY_BAND from it and skip re-classification (do not post a second comment):
|
|
188
|
+
|
|
88
189
|
```bash
|
|
89
|
-
COMPLEXITY_BAND=$(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
# Default to STANDARD if not found (conservative — runs full pipeline)
|
|
93
|
-
COMPLEXITY_BAND="${COMPLEXITY_BAND:-STANDARD}"
|
|
94
|
-
echo "COMPLEXITY_BAND: $COMPLEXITY_BAND"
|
|
190
|
+
COMPLEXITY_BAND=$(printf '%s\n' "$EXISTING_FAST_PATH" \
|
|
191
|
+
| sed -n 's/.*\*\*COMPLEXITY_BAND\*\*: *\([A-Za-z_]*\).*/\1/p' | head -1 | tr '[:lower:]' '[:upper:]')
|
|
192
|
+
echo "COMPLEXITY_BAND (existing): ${COMPLEXITY_BAND:-<none>}"
|
|
95
193
|
```
|
|
96
194
|
|
|
195
|
+
If `COMPLEXITY_BAND` is empty after this (no prior classification, or an unparseable one), classify now as below.
|
|
196
|
+
|
|
197
|
+
**Step 1 — Task type classification:**
|
|
198
|
+
|
|
199
|
+
| Signal | Type | Approach |
|
|
200
|
+
|--------|------|----------|
|
|
201
|
+
| Title starts with "Investigate:"/"Audit:"/"Research:" | Investigation | Produce issues as deliverables |
|
|
202
|
+
| UI/UX, feature + web/ files | UI/UX | `frontend-design` skill |
|
|
203
|
+
| Feature + services/ | Backend Feature | Implement directly |
|
|
204
|
+
| Feature + both | Full-Stack | Backend first, then frontend-design |
|
|
205
|
+
| Bug + web/ | Frontend Fix | Direct |
|
|
206
|
+
| Bug + services/ | Backend Fix | Direct |
|
|
207
|
+
| Refactor/docs | Maintenance | Direct |
|
|
208
|
+
|
|
209
|
+
**Investigation tasks — early exit (skip B2, B2.1, B2.5, B3, B4):** If task type = Investigation (title prefix, or task type = Investigation in the investigator report), the Builder Contract, Context Gathering and Architecture Plan are NOT run. Post the `<!-- FORGE:FAST_PATH -->` comment below, still run B1 (worktree and `building` label), then go straight to B5 (implement → issue-creation path). Do NOT run B2/B2.1/B2.5/B3/B4 for investigation tasks.
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
run() { if [ -n "${DRY_RUN:-}" ]; then echo "DRY_RUN: $*"; else "$@"; fi; }
|
|
213
|
+
run gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:FAST_PATH -->
|
|
214
|
+
## Fast-Path Classification
|
|
215
|
+
|
|
216
|
+
**COMPLEXITY_BAND**: INVESTIGATION
|
|
217
|
+
**Task type**: Investigation
|
|
218
|
+
**Rationale**: Title prefix 'Investigate:' (or task type = Investigation from investigator report) — skipping Builder Contract, Context Gathering and Architecture Plan. Jumping directly to implement (issue creation).
|
|
219
|
+
**Phases skipped**: contract, context, architect"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Step 2 — Complexity classification (for non-Investigation tasks):**
|
|
223
|
+
|
|
224
|
+
Classify COMPLEXITY_BAND based on affected file count and task nature:
|
|
225
|
+
|
|
226
|
+
| Condition | COMPLEXITY_BAND |
|
|
227
|
+
|-----------|-----------------|
|
|
228
|
+
| Single file, doc/config/markdown only, no logic changes expected | TRIVIAL |
|
|
229
|
+
| 1–5 files, existing patterns, no cross-service impact | STANDARD |
|
|
230
|
+
| 6+ files, new abstractions, cross-service, migration, schema changes | COMPLEX |
|
|
231
|
+
|
|
232
|
+
Post `<!-- FORGE:FAST_PATH -->` immediately after classification:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
run() { if [ -n "${DRY_RUN:-}" ]; then echo "DRY_RUN: $*"; else "$@"; fi; }
|
|
236
|
+
run gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:FAST_PATH -->
|
|
237
|
+
## Fast-Path Classification
|
|
238
|
+
|
|
239
|
+
**COMPLEXITY_BAND**: {TRIVIAL|STANDARD|COMPLEX}
|
|
240
|
+
**Task type**: {TASK_TYPE}
|
|
241
|
+
**Affected file count**: {N}
|
|
242
|
+
**Rationale**: {one-sentence explanation of classification decision}
|
|
243
|
+
**Phases skipped**: {list phases skipped, or 'none — full pipeline' for STANDARD/COMPLEX}"
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**TRIVIAL tasks**: B3 (context) and B4 (architect) are skipped only. B2 (Builder Contract) is **retained** — it still runs. When filling in **Phases skipped**, write: `context, architect`. (Each skipped child still posts its own skip marker when invoked — see B3/B4.)
|
|
247
|
+
|
|
248
|
+
**STANDARD and COMPLEX tasks**: Run the full pipeline. No phases skipped.
|
|
249
|
+
|
|
250
|
+
Whatever path was taken, set `COMPLEXITY_BAND` (uppercase) for the rest of this run and echo it. A missing or unparseable band at this point is a bug in this phase — re-classify, never default silently.
|
|
251
|
+
|
|
97
252
|
---
|
|
98
253
|
|
|
99
254
|
## Phase B1: Create Worktree & Branch
|
|
100
255
|
|
|
101
256
|
### B1A: Derive branch name
|
|
102
257
|
|
|
103
|
-
|
|
258
|
+
If `--branch` was passed, or a local/remote branch already exists whose name ends in `-{NUMBER}` (`git branch -a --list "*-{NUMBER}"`), reuse it (resume). Otherwise derive from the issue title: lowercase, hyphenated, max 40 chars (truncate if needed).
|
|
104
259
|
- Bug / fix issues → prefix `fix/`
|
|
105
260
|
- Feature issues → prefix `feat/`
|
|
106
261
|
- Refactor / maintenance → prefix `fix/` or `refactor/`
|
|
107
262
|
|
|
108
|
-
Append `-{NUMBER}` to ensure uniqueness: e.g. `fix/work-on-build-landing-file-85`.
|
|
263
|
+
Append `-{NUMBER}` to ensure uniqueness: e.g. `fix/work-on-build-landing-file-85`. Then `BRANCH_SLUG` is `{BRANCH}` with every `/` replaced by `-`.
|
|
109
264
|
|
|
110
265
|
### B1B: Determine source branch
|
|
111
266
|
|
|
112
267
|
- Review-finding issue → parse `**Code branch**: \`{branch}\`` from issue body; branch from `origin/{branch}`
|
|
113
|
-
- **Milestone review-finding hybrid lane** (ONLY when Code branch matches `milestone/*`): This is a high-risk lane. The worktree will carry the full milestone history. The PR target is `
|
|
114
|
-
- **Missing ref fallback**: After parsing, verify the Code branch still exists on remote. If not, fall back to
|
|
268
|
+
- **Milestone review-finding hybrid lane** (ONLY when Code branch matches `milestone/*`): This is a high-risk lane. The worktree will carry the full milestone history. The PR target is `{PR_BASE}`. **DANGER: Agents MUST NOT use `git merge` to resolve any conflicts in this lane.** Merge-based conflict resolution will pull the entire milestone commit tree onto the PR target, contaminating it with unapproved code. Use `git rebase` or `git cherry-pick` only. If conflicts cannot be resolved without a merge, post a comment on the issue, add `needs-human`, and STOP (Blocked exit).
|
|
269
|
+
- **Missing ref fallback**: After parsing, verify the Code branch still exists on remote. If not, fall back to `{PR_BASE}` and note the fallback:
|
|
115
270
|
```bash
|
|
116
271
|
SOURCE_BRANCH="{CODE_BRANCH_FROM_ISSUE_BODY}"
|
|
117
272
|
if ! git ls-remote --exit-code origin "$SOURCE_BRANCH" >/dev/null 2>&1; then
|
|
118
|
-
echo "WARNING: Code branch '$SOURCE_BRANCH' not found on remote — falling back to
|
|
119
|
-
SOURCE_BRANCH="
|
|
273
|
+
echo "WARNING: Code branch '$SOURCE_BRANCH' not found on remote — falling back to PR base '{PR_BASE}'"
|
|
274
|
+
SOURCE_BRANCH="{PR_BASE}"
|
|
120
275
|
fi
|
|
121
276
|
```
|
|
122
|
-
-
|
|
123
|
-
- Fast lane (no milestone) → branch from `origin/staging`
|
|
277
|
+
- Every other issue (feature lane or fast lane) → `SOURCE_BRANCH` = `{PR_BASE}` (the `--base` arg). There is no hardcoded lane default here: the router already chose `{PR_BASE}` for the lane. <!-- Fixed: forge#639 -->
|
|
124
278
|
|
|
125
279
|
### B1C: Create worktree
|
|
126
280
|
|
|
127
281
|
```bash
|
|
128
|
-
|
|
282
|
+
REPO_ROOT=$(cd "$(git rev-parse --git-common-dir)/.." && pwd)
|
|
283
|
+
WORKTREE_ROOT="${REPO_ROOT}/.claude/worktrees"
|
|
129
284
|
if [ "${FORGE_RUNTIME:-}" = "opencode" ] ||
|
|
130
285
|
[ -n "${OPENCODE_SESSION_ID:-}" ] ||
|
|
131
286
|
[ -n "${OPENCODE_PID:-}" ] ||
|
|
132
287
|
[ -n "${OPENCODE:-}" ]; then
|
|
133
|
-
WORKTREE_ROOT="
|
|
288
|
+
WORKTREE_ROOT="${REPO_ROOT}/.opencode/worktrees"
|
|
134
289
|
elif [ "${FORGE_RUNTIME:-}" = "codex" ]; then
|
|
135
|
-
WORKTREE_ROOT="
|
|
290
|
+
WORKTREE_ROOT="${REPO_ROOT}/.codex/worktrees"
|
|
136
291
|
fi
|
|
292
|
+
# An explicit, absolute forge.yaml paths.worktree_base wins over the runtime default, so worktrees
|
|
293
|
+
# land where the project's cleanup and recovery tooling expects them (e.g. a project that keeps worktrees under .forge/worktrees).
|
|
294
|
+
CONFIGURED_BASE=$(yq '.paths.worktree_base // ""' forge.yaml 2>/dev/null || echo "")
|
|
295
|
+
case "$CONFIGURED_BASE" in /*) WORKTREE_ROOT="${CONFIGURED_BASE%/}" ;; esac
|
|
296
|
+
mkdir -p "$WORKTREE_ROOT"
|
|
137
297
|
WORKTREE_PATH="${WORKTREE_ROOT}/{BRANCH_SLUG}"
|
|
138
|
-
git
|
|
298
|
+
git fetch origin "{SOURCE_BRANCH}" 2>/dev/null || true
|
|
299
|
+
if [ -d "$WORKTREE_PATH" ]; then
|
|
300
|
+
# Reuse existing worktree only when it is on the correct branch
|
|
301
|
+
CURRENT=$(git -C "$WORKTREE_PATH" branch --show-current 2>/dev/null)
|
|
302
|
+
if [ "$CURRENT" != "{BRANCH}" ]; then
|
|
303
|
+
git worktree remove "$WORKTREE_PATH" --force
|
|
304
|
+
fi
|
|
305
|
+
fi
|
|
306
|
+
if [ ! -d "$WORKTREE_PATH" ]; then
|
|
307
|
+
if git show-ref --verify --quiet "refs/heads/{BRANCH}"; then
|
|
308
|
+
git worktree add "$WORKTREE_PATH" "{BRANCH}" # resume: branch already exists
|
|
309
|
+
else
|
|
310
|
+
git worktree add "$WORKTREE_PATH" -b "{BRANCH}" "origin/{SOURCE_BRANCH}"
|
|
311
|
+
fi
|
|
312
|
+
fi
|
|
313
|
+
echo "WORKTREE_PATH=$WORKTREE_PATH BRANCH={BRANCH} BASE={PR_BASE}"
|
|
139
314
|
```
|
|
140
315
|
|
|
141
|
-
If worktree
|
|
142
|
-
```bash
|
|
143
|
-
# Reuse existing worktree — verify it's on the correct branch
|
|
144
|
-
git -C {WORKTREE_PATH} branch --show-current
|
|
145
|
-
```
|
|
146
|
-
If wrong branch, remove and recreate:
|
|
147
|
-
```bash
|
|
148
|
-
git worktree remove {WORKTREE_PATH} --force
|
|
149
|
-
git worktree add {WORKTREE_PATH} -b {BRANCH} origin/{SOURCE_BRANCH}
|
|
150
|
-
```
|
|
316
|
+
If worktree creation fails, take the Blocked exit with the git error as the blocker. Use the resulting `$WORKTREE_PATH` as `{WORKTREE_PATH}` for every later phase and in `BUILD_RESULT`.
|
|
151
317
|
|
|
152
318
|
### B1D: Set building label
|
|
153
319
|
|
|
154
320
|
```bash
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
321
|
+
RESOLUTION=$(resolve_script 'transition-label'); TIER="${RESOLUTION%%:*}"; SCRIPT_PATH="${RESOLUTION#*:}"
|
|
322
|
+
case "$TIER" in
|
|
323
|
+
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} building ;;
|
|
324
|
+
prose)
|
|
325
|
+
[ -n "${DRY_RUN:-}" ] && echo "DRY_RUN: set workflow:building" || \
|
|
326
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:building" \
|
|
327
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
328
|
+
;;
|
|
329
|
+
esac
|
|
158
330
|
```
|
|
159
331
|
|
|
160
332
|
---
|
|
161
333
|
|
|
162
|
-
## Phase B2: Post Builder Contract
|
|
334
|
+
## Phase B2: Post Builder Contract (skip for INVESTIGATION)
|
|
335
|
+
|
|
336
|
+
**Skip if COMPLEXITY_BAND: INVESTIGATION** — go to B5. For every other band (TRIVIAL included) the contract is mandatory. If a `FORGE:CONTRACT` comment already exists on the issue (resume), do not post a second one — continue to B2.1.
|
|
163
337
|
|
|
164
338
|
Post `<!-- FORGE:CONTRACT -->` comment documenting what will be built and why:
|
|
165
339
|
|
|
@@ -171,7 +345,8 @@ SHOW_ATTRIBUTION=$(yq '.branding.show_attribution // "true"' forge.yaml 2>/dev/n
|
|
|
171
345
|
```
|
|
172
346
|
|
|
173
347
|
```bash
|
|
174
|
-
|
|
348
|
+
run() { if [ -n "${DRY_RUN:-}" ]; then echo "DRY_RUN: $*"; else "$@"; fi; }
|
|
349
|
+
run gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CONTRACT -->
|
|
175
350
|
## Builder Contract
|
|
176
351
|
|
|
177
352
|
**Task type**: {TASK_TYPE}
|
|
@@ -209,28 +384,29 @@ Contract must be grounded in the investigation report. Every deliverable file mu
|
|
|
209
384
|
**When `FORGE_COORD_ISSUE` is set**: Post a `FORGE:CLAIM` annotation on the coordination issue to advertise this agent's active resource reservation to the orchestrator and peer agents. This enables the claims-board Layer-2/4 relaxation sweep (orchestrate Step 4B) to identify issue-pairs with disjoint file sets and downgrade unnecessary serialization edges.
|
|
210
385
|
|
|
211
386
|
```bash
|
|
387
|
+
run() { if [ -n "${DRY_RUN:-}" ]; then echo "DRY_RUN: $*"; else "$@"; fi; }
|
|
212
388
|
if [ -n "${FORGE_COORD_ISSUE:-}" ]; then
|
|
213
389
|
COORD_NUM=$(echo "$FORGE_COORD_ISSUE" | grep -oE '[0-9]+$')
|
|
214
390
|
if [ -n "$COORD_NUM" ]; then
|
|
215
391
|
# Extract file paths from the just-posted FORGE:CONTRACT deliverables table
|
|
216
|
-
CLAIMED_FILES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
392
|
+
CLAIMED_FILES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments --paginate \
|
|
217
393
|
--jq '[.[] | select(.body | contains("FORGE:CONTRACT"))] | last | .body' 2>/dev/null \
|
|
218
394
|
| awk '/^### Deliverables/{p=1; next} /^### /{p=0} p' \
|
|
219
|
-
| grep -
|
|
220
|
-
| tr -d '`' | sort -u |
|
|
395
|
+
| grep -oE '`[^`]+\.(py|tsx?|jsx?|sql|json|ya?ml|md|mjs|sh)`' \
|
|
396
|
+
| tr -d '`' | sort -u | head -20)
|
|
221
397
|
CLAIMED_FILES="${CLAIMED_FILES:-"(files listed in FORGE:CONTRACT deliverables table)"}"
|
|
222
398
|
|
|
223
399
|
# Extract preserved interfaces from the FORGE:ARCHITECT affected paths table (if present)
|
|
224
|
-
CLAIMED_INTERFACES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
400
|
+
CLAIMED_INTERFACES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments --paginate \
|
|
225
401
|
--jq '[.[] | select(.body | contains("FORGE:ARCHITECT"))] | last | .body' 2>/dev/null \
|
|
226
402
|
| awk '/^### Affected Paths/{p=1; next} /^### /{p=0} p' \
|
|
227
|
-
| grep -
|
|
403
|
+
| grep -oE 'Function/Class.*\|.*\|' | head -10 || true)
|
|
228
404
|
CLAIMED_INTERFACES="${CLAIMED_INTERFACES:-"(see FORGE:ARCHITECT comment for interface details)"}"
|
|
229
405
|
|
|
230
|
-
CLAIM_HOLDER="
|
|
231
|
-
CLAIM_TTL="terminal state of Holder issue
|
|
406
|
+
CLAIM_HOLDER="#{NUMBER} / $(date -u +%Y%m%dT%H%M%S)"
|
|
407
|
+
CLAIM_TTL="terminal state of Holder issue #{NUMBER}"
|
|
232
408
|
|
|
233
|
-
gh issue comment "$COORD_NUM" -R {GH_REPO} --body "<!-- FORGE:CLAIM -->
|
|
409
|
+
run gh issue comment "$COORD_NUM" -R {GH_REPO} --body "<!-- FORGE:CLAIM -->
|
|
234
410
|
## Resource Claim
|
|
235
411
|
|
|
236
412
|
**Holder**: ${CLAIM_HOLDER}
|
|
@@ -239,7 +415,7 @@ if [ -n "${FORGE_COORD_ISSUE:-}" ]; then
|
|
|
239
415
|
**TTL**: ${CLAIM_TTL}
|
|
240
416
|
|
|
241
417
|
<!-- CLAIM:COMPLETE -->" 2>/dev/null || true
|
|
242
|
-
echo "FORGE:CLAIM posted on coordination issue #${COORD_NUM} for
|
|
418
|
+
echo "FORGE:CLAIM posted on coordination issue #${COORD_NUM} for #{NUMBER}"
|
|
243
419
|
fi
|
|
244
420
|
fi
|
|
245
421
|
```
|
|
@@ -248,12 +424,12 @@ fi
|
|
|
248
424
|
|
|
249
425
|
---
|
|
250
426
|
|
|
251
|
-
## Phase B2.5: Extract FUNCTION_NAMES from Contract
|
|
427
|
+
## Phase B2.5: Extract FUNCTION_NAMES from Contract (skip for INVESTIGATION)
|
|
252
428
|
|
|
253
|
-
After posting the Builder Contract, extract the primary function/class names from the contract's deliverables table. These are passed to the context
|
|
429
|
+
After posting the Builder Contract, extract the primary function/class names from the contract's deliverables table. These are passed to the context child for its C3 caller/importer discovery.
|
|
254
430
|
|
|
255
431
|
```bash
|
|
256
|
-
FUNCTION_NAMES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
432
|
+
FUNCTION_NAMES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments --paginate \
|
|
257
433
|
--jq '.[] | select(.body | contains("FORGE:CONTRACT")) | .body' \
|
|
258
434
|
| awk '/^### Deliverables/{p=1; next} /^### /{p=0} p' \
|
|
259
435
|
| grep -oE '`[A-Za-z_][A-Za-z0-9_]*`' \
|
|
@@ -263,56 +439,49 @@ FUNCTION_NAMES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
|
263
439
|
| xargs)
|
|
264
440
|
# Scope is limited to the ### Deliverables section to avoid false matches from FORGE markers,
|
|
265
441
|
# phase labels (B2, C3), and identifiers mentioned in Acceptance Criteria or Quality sections.
|
|
266
|
-
# Fallback: if extraction yields nothing, FUNCTION_NAMES remains empty string
|
|
267
|
-
# context
|
|
442
|
+
# Fallback: if extraction yields nothing, FUNCTION_NAMES remains the empty string and
|
|
443
|
+
# context C3 skips gracefully (its for-loop produces zero iterations).
|
|
268
444
|
```
|
|
269
445
|
|
|
270
|
-
If `FUNCTION_NAMES` is non-empty, it
|
|
446
|
+
If `FUNCTION_NAMES` is non-empty, pass it via `--functions` to the context child. If empty, omit the `--functions` flag.
|
|
271
447
|
|
|
272
448
|
---
|
|
273
449
|
|
|
274
|
-
## Phase B3: Context Gathering (
|
|
275
|
-
|
|
276
|
-
**Skip if COMPLEXITY_BAND: TRIVIAL** (read from FORGE:FAST_PATH in Phase B0) — skip this phase entirely. Proceed directly to Phase B4.
|
|
277
|
-
|
|
278
|
-
**For STANDARD and COMPLEX tasks**: Always run. Do NOT skip without a TRIVIAL COMPLEXITY_BAND.
|
|
450
|
+
## Phase B3: Context Gathering (invoke for every band except TRIVIAL / INVESTIGATION)
|
|
279
451
|
|
|
280
|
-
**
|
|
452
|
+
**Skip only if COMPLEXITY_BAND is TRIVIAL or INVESTIGATION** — proceed directly to Phase B4. (The context child would also detect these bands and return `SKIPPED` itself; for STANDARD and COMPLEX it is NOT optional — it must be invoked.)
|
|
281
453
|
|
|
282
|
-
|
|
454
|
+
Always invoke it as a forked sub-skill — never inline:
|
|
283
455
|
|
|
284
|
-
**Spawn exception** (only when Row (c) applies):
|
|
285
456
|
```
|
|
286
|
-
Skill("work-on:build:context", args="{NUMBER} --repo {GH_REPO} --gh-flag {GH_FLAG} --repo-path {WORKTREE_PATH} {AFFECTED_FILES} --functions {FUNCTION_NAMES}")
|
|
457
|
+
Skill(skill="{FORGE_SKILL_PREFIX}work-on:build:context", args="{NUMBER} --repo {GH_REPO} --gh-flag \"{GH_FLAG}\" --repo-path {WORKTREE_PATH} --files \"{AFFECTED_FILES}\" --functions \"{FUNCTION_NAMES}\"")
|
|
287
458
|
```
|
|
288
|
-
If `FUNCTION_NAMES` is empty, omit `--functions`. The Skill() form above is the exception path — not the default. <!-- Added: forge#1276 -->
|
|
289
459
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
460
|
+
If `FUNCTION_NAMES` is empty, omit `--functions`. The child posts its own `FORGE:CONTEXT` comment (a minimal marker when it skips a non-TRIVIAL issue).
|
|
461
|
+
|
|
462
|
+
**After the child returns** (read its `CONTEXT_RESULT:` block):
|
|
463
|
+
- `status: COMPLETE | PARTIAL | SKIPPED` → continue to B4
|
|
464
|
+
- No `CONTEXT_RESULT:` block, timeout or error → log a warning and continue to B4 (context is advisory and non-blocking)
|
|
465
|
+
- Skill not found → Blocked exit, blocker "skill not found: work-on:build:context"
|
|
293
466
|
# MUST CONTINUE to Phase B4 — context result is intermediate, NOT terminal.
|
|
294
467
|
|
|
295
468
|
---
|
|
296
469
|
|
|
297
|
-
## Phase B4: Architecture Planning (
|
|
298
|
-
|
|
299
|
-
**Skip if COMPLEXITY_BAND: TRIVIAL** (read from FORGE:FAST_PATH in Phase B0) — skip this phase entirely. Proceed directly to Phase B5.
|
|
470
|
+
## Phase B4: Architecture Planning (invoke for every band except TRIVIAL / INVESTIGATION)
|
|
300
471
|
|
|
301
|
-
**For STANDARD and COMPLEX
|
|
472
|
+
**Skip only if COMPLEXITY_BAND is TRIVIAL or INVESTIGATION** — proceed directly to Phase B5. For STANDARD and COMPLEX it is NOT optional; even a 1-file STANDARD fix benefits from cross-path consistency checks.
|
|
302
473
|
|
|
303
|
-
**Execution model**: Run **inline** (see Canonical Build Path above). Read the `commands/work-on/build/architect.md` spec and execute its steps directly in this context window. Only spawn a Skill() sub-agent when the Spawn-Decision Table Row (c) applies. <!-- Added: forge#1276 -->
|
|
304
|
-
|
|
305
|
-
Trace all affected code paths and produce an ordered implementation plan. The full step-by-step logic is defined in `commands/work-on/build/architect.md`. Key steps: map all callers and importers of changed functions; check consistency rules across paths; post a `FORGE:ARCHITECT` annotation with the ordered plan and a risk table.
|
|
306
|
-
|
|
307
|
-
**Spawn exception** (only when Row (c) applies):
|
|
308
474
|
```
|
|
309
|
-
Skill("work-on:build:architect", args="{NUMBER} --repo {GH_REPO} --gh-flag {GH_FLAG} --repo-path {WORKTREE_PATH} --files {AFFECTED_FILES}")
|
|
475
|
+
Skill(skill="{FORGE_SKILL_PREFIX}work-on:build:architect", args="{NUMBER} --repo {GH_REPO} --gh-flag \"{GH_FLAG}\" --repo-path {WORKTREE_PATH} --files \"{AFFECTED_FILES}\"")
|
|
310
476
|
```
|
|
311
|
-
The Skill() form above is the exception path — not the default. <!-- Added: forge#1276 -->
|
|
312
477
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
478
|
+
The child posts its own `FORGE:ARCHITECT` comment (a "Skipped" marker + `:COMPLETE` on every skip, forge#2689).
|
|
479
|
+
|
|
480
|
+
**After the child returns** (read its `ARCHITECT_RESULT:` block):
|
|
481
|
+
- `status: COMPLETE | PARTIAL | SKIPPED` → continue to B5
|
|
482
|
+
- `status: BLOCKED` (conflicting constraints that cannot be resolved) → Blocked exit with the child's `blocker`
|
|
483
|
+
- No `ARCHITECT_RESULT:` block → re-read the issue: if a `FORGE:ARCHITECT:COMPLETE` marker exists continue to B5, otherwise Blocked exit "architect produced no result"
|
|
484
|
+
- Skill not found → Blocked exit, blocker "skill not found: work-on:build:architect"
|
|
316
485
|
# MUST CONTINUE to Phase B5 — architect result is intermediate, NOT terminal.
|
|
317
486
|
|
|
318
487
|
---
|
|
@@ -322,14 +491,15 @@ The Skill() form above is the exception path — not the default. <!-- Added: fo
|
|
|
322
491
|
Invoke the implement subcommand to write code, stage, and post the builder comment:
|
|
323
492
|
|
|
324
493
|
```
|
|
325
|
-
Skill("work-on:build:implement", args="{NUMBER} --repo {GH_REPO} --gh-flag {GH_FLAG} --worktree {WORKTREE_PATH} --branch {BRANCH}")
|
|
494
|
+
Skill(skill="{FORGE_SKILL_PREFIX}work-on:build:implement", args="{NUMBER} --repo {GH_REPO} --gh-flag \"{GH_FLAG}\" --worktree {WORKTREE_PATH} --branch {BRANCH} --base {PR_BASE}")
|
|
326
495
|
```
|
|
327
496
|
|
|
328
|
-
**After subcommand returns
|
|
329
|
-
- `
|
|
330
|
-
- `
|
|
331
|
-
- `
|
|
332
|
-
- `
|
|
497
|
+
**After subcommand returns** (read its `IMPLEMENT_RESULT:` block):
|
|
498
|
+
- `status: COMPLETE` → continue to B6
|
|
499
|
+
- `status: ALREADY_DONE` → continue to B6 (validate what's already there)
|
|
500
|
+
- `status: INVESTIGATION_COMPLETE` → issues were created as deliverables and the original closed; print `BUILD_RESULT: status: INVESTIGATION_COMPLETE` (skip B6/B6.5)
|
|
501
|
+
- `status: BLOCKED` → Blocked exit with the child's `blocker`
|
|
502
|
+
- Skill not found → Blocked exit, blocker "skill not found: work-on:build:implement"
|
|
333
503
|
# MUST CONTINUE to Phase B6 — implement result is intermediate, NOT terminal (validation still required).
|
|
334
504
|
|
|
335
505
|
---
|
|
@@ -339,25 +509,30 @@ Skill("work-on:build:implement", args="{NUMBER} --repo {GH_REPO} --gh-flag {GH_F
|
|
|
339
509
|
Invoke the validate subcommand to run the quality gate loop, formatting, and deploy checks:
|
|
340
510
|
|
|
341
511
|
```
|
|
342
|
-
Skill("work-on:build:validate", args="{NUMBER} --repo {GH_REPO} --gh-flag {GH_FLAG} --worktree {WORKTREE_PATH} --files {CHANGED_FILES}")
|
|
512
|
+
Skill(skill="{FORGE_SKILL_PREFIX}work-on:build:validate", args="{NUMBER} --repo {GH_REPO} --gh-flag \"{GH_FLAG}\" --worktree {WORKTREE_PATH} --branch {BRANCH} --base {PR_BASE} --files \"{CHANGED_FILES}\"")
|
|
343
513
|
```
|
|
344
514
|
|
|
345
|
-
Where `{CHANGED_FILES}` is the space-separated list of files changed by the implement subcommand (
|
|
515
|
+
Where `{CHANGED_FILES}` is the space-separated list of files changed by the implement subcommand (`files_changed` in `IMPLEMENT_RESULT`, or derive it):
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
CHANGED_FILES=$(git -C "{WORKTREE_PATH}" diff --name-only "origin/{PR_BASE}...HEAD" 2>/dev/null | tr '\n' ' ' | xargs)
|
|
519
|
+
```
|
|
346
520
|
|
|
347
|
-
**After subcommand returns
|
|
348
|
-
- `
|
|
349
|
-
- `
|
|
521
|
+
**After subcommand returns** (read its `VALIDATE_RESULT:` block):
|
|
522
|
+
- `gate_passed: true` → verify the `FORGE:QUALITY_GATE` marker exists on the issue (posted by validate V5; docs-only changes exempt). If absent, re-invoke validate once; a missing marker is not a pass. Then continue to Phase B6.5 (acceptance gate)
|
|
523
|
+
- `gate_passed: false` → the subcommand has already posted its comment and added `needs-human`; print `BUILD_RESULT: status: BLOCKED` with the child's `blocker` (run the Blocked exit if no comment was posted)
|
|
524
|
+
- Skill not found → Blocked exit, blocker "skill not found: work-on:build:validate"
|
|
350
525
|
|
|
351
526
|
---
|
|
352
527
|
|
|
353
528
|
## Phase B6.5: Acceptance Gate (MANDATORY — cannot be silently skipped) <!-- Added: forge#1315 -->
|
|
354
529
|
|
|
355
|
-
**Goal**: Execute the machine-checkable acceptance spec emitted by investigate Phase 1C and block merge if any check fails. This is a hard gate — not advisory.
|
|
530
|
+
**Goal**: Execute the machine-checkable acceptance spec emitted by investigate Phase 1C and block merge if any check fails. This is a hard gate — not advisory. Run the checks from the worktree (`cd "{WORKTREE_PATH}"`) so relative targets resolve against the built code.
|
|
356
531
|
|
|
357
532
|
**Read acceptance spec from FORGE:INVESTIGATOR comment**:
|
|
358
533
|
|
|
359
534
|
```bash
|
|
360
|
-
ACCEPTANCE_CHECKS=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
535
|
+
ACCEPTANCE_CHECKS=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments --paginate \
|
|
361
536
|
--jq '.[] | select(.body | contains("FORGE:INVESTIGATOR")) | .body' \
|
|
362
537
|
| grep "^ACCEPTANCE_CHECK:" )
|
|
363
538
|
```
|
|
@@ -365,7 +540,8 @@ ACCEPTANCE_CHECKS=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
|
365
540
|
**If `ACCEPTANCE_CHECKS` is empty** (investigation predates this feature or comment was deleted): post a warning comment and **block** — do not silently pass:
|
|
366
541
|
|
|
367
542
|
```bash
|
|
368
|
-
|
|
543
|
+
run() { if [ -n "${DRY_RUN:-}" ]; then echo "DRY_RUN: $*"; else "$@"; fi; }
|
|
544
|
+
run gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:ACCEPTANCE_GATE -->
|
|
369
545
|
## Acceptance Gate — No Spec Found
|
|
370
546
|
|
|
371
547
|
No \`ACCEPTANCE_CHECK:\` lines found in the FORGE:INVESTIGATOR comment. This may mean:
|
|
@@ -375,14 +551,15 @@ No \`ACCEPTANCE_CHECK:\` lines found in the FORGE:INVESTIGATOR comment. This may
|
|
|
375
551
|
**Gate result: BLOCKED** — re-run \`/work-on:investigate {NUMBER}\` to regenerate the acceptance spec, then retry the build.
|
|
376
552
|
|
|
377
553
|
<!-- FORGE:ACCEPTANCE_GATE:BLOCKED -->"
|
|
378
|
-
gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human"
|
|
554
|
+
run gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human"
|
|
379
555
|
```
|
|
380
|
-
|
|
556
|
+
Print `BUILD_RESULT: status: BLOCKED`, blocker: "No acceptance spec — re-run investigate to emit ACCEPTANCE_CHECK lines".
|
|
381
557
|
|
|
382
558
|
**If all checks are `type=skipped`**: post a pass comment noting human review is required, then continue to the checkpoint (non-blocking — skip was deliberate):
|
|
383
559
|
|
|
384
560
|
```bash
|
|
385
|
-
|
|
561
|
+
run() { if [ -n "${DRY_RUN:-}" ]; then echo "DRY_RUN: $*"; else "$@"; fi; }
|
|
562
|
+
run gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:ACCEPTANCE_GATE -->
|
|
386
563
|
## Acceptance Gate — Skipped (No Machine-Checkable Criteria)
|
|
387
564
|
|
|
388
565
|
The acceptance spec contains only a skip sentinel (\`type=skipped\`). No automated checks were run. Human review is required before merge.
|
|
@@ -394,6 +571,7 @@ The acceptance spec contains only a skip sentinel (\`type=skipped\`). No automat
|
|
|
394
571
|
|
|
395
572
|
```bash
|
|
396
573
|
GATE_PASS=true
|
|
574
|
+
CORRECTED=""
|
|
397
575
|
FAILED_CHECKS=""
|
|
398
576
|
|
|
399
577
|
while IFS= read -r check_line; do
|
|
@@ -422,9 +600,11 @@ while IFS= read -r check_line; do
|
|
|
422
600
|
# only for older ACCEPTANCE_CHECK comments emitted before this fix (still correct for
|
|
423
601
|
# single-token exists/contains targets; multi-word legacy targets remain truncated until the
|
|
424
602
|
# issue's investigation is re-run to emit the quoted format).
|
|
425
|
-
|
|
603
|
+
# Quoted fields may contain escaped quotes (matcher="return \"$HELD\""): match (\\.|[^"\\])* and unescape.
|
|
604
|
+
# A plain [^"]* stops at the first \" and silently checks a truncated matcher (field test #3167).
|
|
605
|
+
TARGET=$(printf '%s\n' "$check_line" | sed -nE 's/^ACCEPTANCE_CHECK: id=[^ ]* type=[^ ]* target="(([^"\\]|\\.)*)".*/\1/p' | sed 's/\\"/"/g')
|
|
426
606
|
[ -z "$TARGET" ] && TARGET=$(echo "$check_line" | sed -n 's/^ACCEPTANCE_CHECK: id=[^ ]* type=[^ ]* target=\([^ ]*\).*/\1/p')
|
|
427
|
-
MATCHER=$(
|
|
607
|
+
MATCHER=$(printf '%s\n' "$check_line" | sed -nE 's/^ACCEPTANCE_CHECK: id=[^ ]* type=[^ ]* target=("([^"\\]|\\.)*"|[^ ]*) matcher="(([^"\\]|\\.)*)".*/\3/p' | sed 's/\\"/"/g')
|
|
428
608
|
[ -z "$MATCHER" ] && MATCHER=$(echo "$check_line" | sed -n 's/^ACCEPTANCE_CHECK: id=[^ ]* type=[^ ]* target=[^ ]* matcher=\([^ ]*\).*/\1/p')
|
|
429
609
|
DESC=$(echo "$check_line" | sed -n 's/.*description=\(.*\)/\1/p')
|
|
430
610
|
|
|
@@ -438,14 +618,22 @@ while IFS= read -r check_line; do
|
|
|
438
618
|
[ -e "$TARGET" ] || { RESULT="FAIL"; DETAIL="path not found: $TARGET"; }
|
|
439
619
|
;;
|
|
440
620
|
contains)
|
|
441
|
-
|
|
621
|
+
# A matcher is usually literal code; an unescaped $ ( [ . turns it into a different regex. Accept a
|
|
622
|
+
# literal match and record the correction instead of failing the build on a malformed check.
|
|
623
|
+
if ! grep -qE "$MATCHER" "$TARGET" 2>/dev/null; then
|
|
624
|
+
if grep -qF -- "$MATCHER" "$TARGET" 2>/dev/null; then CORRECTED="${CORRECTED}\n- **$ID**: matched as a literal string (the regex form could not match)"
|
|
625
|
+
else RESULT="FAIL"; DETAIL="'$MATCHER' not found in $TARGET"; fi
|
|
626
|
+
fi
|
|
442
627
|
;;
|
|
443
628
|
command|behavior)
|
|
444
629
|
if [ "$MATCHER" = "exit_0" ]; then
|
|
445
630
|
eval "$TARGET" >/dev/null 2>&1 || { RESULT="FAIL"; DETAIL="command exited non-zero: $TARGET"; }
|
|
446
631
|
else
|
|
447
632
|
OUTPUT=$(eval "$TARGET" 2>&1)
|
|
448
|
-
|
|
633
|
+
if ! printf '%s\n' "$OUTPUT" | grep -qE "$MATCHER"; then
|
|
634
|
+
if printf '%s\n' "$OUTPUT" | grep -qF -- "$MATCHER"; then CORRECTED="${CORRECTED}\n- **$ID**: output matched as a literal string"
|
|
635
|
+
else RESULT="FAIL"; DETAIL="output did not match '$MATCHER'. Got: $(printf '%s\n' "$OUTPUT" | head -3)"; fi
|
|
636
|
+
fi
|
|
449
637
|
fi
|
|
450
638
|
;;
|
|
451
639
|
*)
|
|
@@ -463,15 +651,17 @@ done <<< "$ACCEPTANCE_CHECKS"
|
|
|
463
651
|
**Post gate result comment**:
|
|
464
652
|
|
|
465
653
|
```bash
|
|
654
|
+
run() { if [ -n "${DRY_RUN:-}" ]; then echo "DRY_RUN: $*"; else "$@"; fi; }
|
|
466
655
|
if [ "$GATE_PASS" = "true" ]; then
|
|
467
|
-
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:ACCEPTANCE_GATE -->
|
|
656
|
+
run gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:ACCEPTANCE_GATE -->
|
|
468
657
|
## Acceptance Gate — PASSED
|
|
469
658
|
|
|
470
659
|
All machine-checkable acceptance criteria verified against real behavior.
|
|
660
|
+
$( [ -n "$CORRECTED" ] && printf '\n**Checks corrected (malformed matcher, literal text present)**:%b\n' "$CORRECTED" )
|
|
471
661
|
|
|
472
662
|
<!-- FORGE:ACCEPTANCE_GATE:PASSED -->"
|
|
473
663
|
else
|
|
474
|
-
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:ACCEPTANCE_GATE -->
|
|
664
|
+
run gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:ACCEPTANCE_GATE -->
|
|
475
665
|
## Acceptance Gate — FAILED
|
|
476
666
|
|
|
477
667
|
The following acceptance checks did not pass:
|
|
@@ -481,19 +671,26 @@ $(echo -e "$FAILED_CHECKS")
|
|
|
481
671
|
Merge is blocked. Fix the failing criteria and re-run the validate phase.
|
|
482
672
|
|
|
483
673
|
<!-- FORGE:ACCEPTANCE_GATE:FAILED -->"
|
|
484
|
-
gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human"
|
|
485
|
-
# Return BLOCKED — merge gate failed
|
|
674
|
+
run gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human"
|
|
486
675
|
fi
|
|
487
676
|
```
|
|
488
677
|
|
|
489
|
-
If `GATE_PASS = false
|
|
678
|
+
If `GATE_PASS = false`, the build repairs itself **once** before anything escalates (a failing acceptance check is pipeline work, not a human decision):
|
|
679
|
+
|
|
680
|
+
1. Count `<!-- FORGE:ACCEPTANCE_REPAIR: issue={NUMBER} -->` comments on the issue. If one already exists, skip to step 4.
|
|
681
|
+
2. Post that marker (with the failed check ids), remove `needs-human` if this gate added it, then invoke
|
|
682
|
+
`Skill(skill="{FORGE_SKILL_PREFIX}work-on:build:implement", args="{NUMBER} --repo {GH_REPO} --gh-flag \"{GH_FLAG}\" --worktree {WORKTREE_PATH} --branch {BRANCH} --base {PR_BASE} --fix-acceptance \"<failed check ids and details>\"")`
|
|
683
|
+
followed by `work-on:build:validate` with the same args as B6 (the new code gets a fresh quality gate and commit).
|
|
684
|
+
3. Re-run this whole B6.5 gate. PASS → continue to the checkpoint.
|
|
685
|
+
4. Still failing (or already repaired once) → leave `needs-human` and print `BUILD_RESULT: status: BLOCKED`, blocker: "Acceptance gate failed after one repair — see FORGE:ACCEPTANCE_GATE comment".
|
|
490
686
|
|
|
491
687
|
If `GATE_PASS = true`: continue to write the phase checkpoint below.
|
|
492
688
|
|
|
493
|
-
**When
|
|
689
|
+
**When the gate passed — write machine-readable phase checkpoint before returning (MANDATORY)**:
|
|
494
690
|
```bash
|
|
691
|
+
run() { if [ -n "${DRY_RUN:-}" ]; then echo "DRY_RUN: $*"; else "$@"; fi; }
|
|
495
692
|
CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
496
|
-
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
693
|
+
run gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
497
694
|
\`\`\`json
|
|
498
695
|
{\"phase\": \"BUILD\", \"status\": \"COMPLETE\", \"next_phase\": \"REVIEW\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
|
|
499
696
|
\`\`\`"
|
|
@@ -503,7 +700,7 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
|
503
700
|
|
|
504
701
|
## Output
|
|
505
702
|
|
|
506
|
-
|
|
703
|
+
Print this structured block as your final reply (and nothing after it) — the router reads only this block, re-evaluates state, and continues to the next phase. Heartbeats are posted by the router, not here.
|
|
507
704
|
|
|
508
705
|
```
|
|
509
706
|
BUILD_RESULT:
|
|
@@ -513,18 +710,20 @@ BUILD_RESULT:
|
|
|
513
710
|
blocker: {description if status=BLOCKED}
|
|
514
711
|
```
|
|
515
712
|
|
|
713
|
+
- `COMPLETE` — B0–B6.5 all passed; `FORGE:BUILDER:COMPLETE` is on the issue.
|
|
714
|
+
- `ALREADY_DONE` — B0 found `FORGE:BUILDER:COMPLETE`.
|
|
715
|
+
- `INVESTIGATION_COMPLETE` — the implement child created the deliverable issues (B5); no review follows.
|
|
716
|
+
- `BLOCKED` — any guard or failure; `needs-human` is set and the blocker is posted.
|
|
717
|
+
|
|
516
718
|
---
|
|
517
719
|
|
|
518
|
-
## Integration Point
|
|
720
|
+
## Integration Point
|
|
519
721
|
|
|
520
|
-
|
|
722
|
+
Position in the work-on pipeline (label `workflow:ready-to-build` or `workflow:building`):
|
|
521
723
|
|
|
522
724
|
```
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
Phase 6 (Close) → work-on:close — trajectory + parent tracker + summary + worktree cleanup
|
|
725
|
+
investigate → [THIS MODULE] worktree + classify + contract + context + architect + implement + validate + acceptance-gate
|
|
726
|
+
→ posts FORGE:FAST_PATH, FORGE:CONTRACT, FORGE:BUILDER(:COMPLETE), FORGE:ACCEPTANCE_GATE; writes FORGE:CHECKPOINT next_phase=REVIEW
|
|
727
|
+
→ work-on:review (push branch, create PR, review, merge)
|
|
728
|
+
→ work-on:close
|
|
528
729
|
```
|
|
529
|
-
|
|
530
|
-
After this module posts `FORGE:BUILDER` and returns, work-on.md's Universal continuation rule re-reads the issue labels. Since the label is not yet terminal (`workflow:merged` / `workflow:invalid` / `needs-human`), it proceeds immediately to Phase 4 (PR Creation) and then Phase 5 (Auto-Review).
|