forgedock 1.0.16 → 1.0.18
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 +54 -3
- package/bin/cross-publish.mjs +344 -0
- package/bin/forgedock.mjs +2205 -541
- package/bin/github-app-manifest.json.license +2 -0
- package/bin/hooks/session-start.mjs +4 -5
- package/bin/registry.mjs +20 -0
- package/bin/tui.mjs +172 -96
- package/commands/adopt.md +433 -0
- package/commands/analytics.md +2 -0
- package/commands/audit-agents.md +2 -0
- package/commands/audit.md +2 -0
- package/commands/autopilot.md +77 -0
- package/commands/changelog.md +354 -0
- package/commands/cleanup.md +2 -0
- package/commands/compat-audit.md +330 -0
- package/commands/deploy-info.md +6 -5
- package/commands/design-bench.md +176 -0
- package/commands/design-render-critique-loop.md +103 -0
- package/commands/design.md +259 -0
- package/commands/diagnose.md +483 -0
- package/commands/explain.md +349 -0
- package/commands/forge-stats.md +2 -0
- package/commands/forgedock-init.md +2 -0
- package/commands/geo-audit.md +2 -0
- package/commands/incident-response.md +2 -0
- package/commands/issue.md +9 -3
- package/commands/milestone.md +2 -0
- package/commands/optimize.md +411 -0
- package/commands/orchestrate.md +239 -0
- package/commands/pipeline-health.md +60 -1
- package/commands/qa-sweep.md +39 -2
- package/commands/quality-gate.md +67 -0
- package/commands/replay.md +263 -0
- package/commands/resume.md +264 -0
- package/commands/review-pr-agents.md +5 -1
- package/commands/review-pr-staging.md +160 -2
- package/commands/review-pr.md +85 -3
- package/commands/rollback.md +2 -0
- package/commands/scope.md +280 -0
- package/commands/security-audit.md +2 -0
- package/commands/status.md +262 -0
- package/commands/sync-ecosystem.md +2 -0
- package/commands/test-gate.md +929 -0
- package/commands/validate.md +2 -0
- package/commands/work-on/build/architect.md +128 -18
- package/commands/work-on/build/context.md +130 -17
- package/commands/work-on/build/implement.md +7 -3
- package/commands/work-on/build/validate.md +2 -0
- package/commands/work-on/build.md +32 -7
- package/commands/work-on/close.md +85 -0
- package/commands/work-on/decompose.md +2 -0
- package/commands/work-on/investigate.md +30 -3
- package/commands/work-on/review.md +13 -1
- package/commands/work-on-monolithic.md +2 -0
- package/commands/work-on.md +676 -38
- package/docs/BRAND.md +51 -0
- package/docs/CI.md +177 -0
- package/docs/CONFIG.md +94 -0
- package/docs/FORGE-PROTOCOL.md +324 -0
- package/docs/articles/b-vs-c-baseline.md +210 -0
- package/docs/articles/full-abc-benchmark.md +205 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +174 -0
- package/docs/articles/selective-spec-loading-token-savings.md +140 -0
- package/docs/claude-breakpoints.json +47 -0
- package/docs/design/abc-benchmark.md +195 -0
- package/docs/design/design-architect-rationale.md +123 -0
- package/docs/design/design-memory.md +61 -0
- package/docs/design/design-spec-schema.md +248 -0
- package/docs/design/design-system-lint.md +150 -0
- package/docs/design/divergent-generation.md +106 -0
- package/docs/design/effects-appropriateness.md +133 -0
- package/docs/design/fixtures/briefs/README.md +47 -0
- package/docs/design/fixtures/briefs/cadence.md +39 -0
- package/docs/design/fixtures/briefs/plume.md +39 -0
- package/docs/design/fixtures/briefs/slipstream.md +39 -0
- package/docs/design/fixtures/briefs/tender.md +39 -0
- package/docs/design/fixtures/briefs/voltage.md +39 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-1.html +364 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-2.html +420 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-3.html +506 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-1.html +611 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-2.html +594 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-3.html +817 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/runs.json +175 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/scorecard.json +138 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-1.html +373 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-2.html +398 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-3.html +595 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-1.html +531 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-2.html +565 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-3.html +674 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-1.html +388 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-2.html +463 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-3.html +568 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-1.html +562 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-2.html +618 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-3.html +618 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-1.html +364 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-2.html +420 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-3.html +506 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-1.html +527 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-2.html +445 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-3.html +483 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-1.html +611 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-2.html +594 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-3.html +817 -0
- package/docs/design/fixtures/runs/full-abc/runs.json +190 -0
- package/docs/design/fixtures/runs/full-abc/scorecard.json +144 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-1.html +496 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-2.html +447 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-3.html +564 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-1.html +373 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-2.html +398 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-3.html +595 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-1.html +622 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-2.html +475 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-3.html +660 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-1.html +531 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-2.html +565 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-3.html +674 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-1.html +525 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-2.html +446 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-3.html +463 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-1.html +388 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-2.html +463 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-3.html +568 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-1.html +1068 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-2.html +1423 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-3.html +1245 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-1.html +305 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-2.html +327 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-3.html +428 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-1.html +1270 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-2.html +1172 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-3.html +1532 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-1.html +315 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-2.html +342 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-3.html +353 -0
- package/docs/design/fixtures/runs/full-abc-opus/runs.json +190 -0
- package/docs/design/fixtures/runs/full-abc-opus/scorecard.json +144 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-1.html +345 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-2.html +396 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-3.html +502 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-1.html +408 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-2.html +446 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-3.html +496 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-1.html +813 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-2.html +711 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-3.html +721 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-1.html +280 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-2.html +291 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-3.html +366 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-1.html +1010 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-2.html +836 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-3.html +930 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-1.html +407 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-2.html +396 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-3.html +529 -0
- package/docs/design/foundations/brutalist.css +294 -0
- package/docs/design/foundations/editorial.css +300 -0
- package/docs/design/foundations/minimal-luxury.css +295 -0
- package/docs/design/foundations/technical.css +293 -0
- package/docs/design/foundations/warm-photo.css +299 -0
- package/docs/design/reference-corpus.md +1726 -0
- package/docs/design/render-critique-loop.md +57 -0
- package/docs/site/getting-started.md +21 -0
- package/docs/spec-graph-schema.md +397 -0
- package/package.json +3 -3
- package/scripts/bench-scorecard.mjs +239 -0
- package/scripts/build-spec-graph.mjs +509 -0
- package/scripts/classify-lane.sh +138 -0
- package/scripts/design-system-lint.mjs +993 -0
- package/scripts/graph-query.sh +486 -0
- package/scripts/transition-label.sh +169 -0
- package/scripts/validate-pr-target.sh +30 -0
- package/scripts/validate-spec-graph.sh +423 -0
- package/templates/devdocs/README.md +72 -0
- package/templates/devdocs/agent/using-forgedock.md +3 -0
- package/templates/devdocs/agent/using-github.md +3 -0
- package/templates/devdocs/index.yaml +162 -0
- package/templates/devdocs/project/architecture.md +3 -0
- package/templates/devdocs/project/conventions.md +3 -0
- package/templates/devdocs/project/custom-instructions.md +3 -0
- package/templates/devdocs/project/glossary.md +3 -0
- package/templates/devdocs/project/stack.md +3 -0
- package/templates/devdocs/templates/commit-convention.md +3 -0
- package/templates/devdocs/templates/issue.md +3 -0
- package/templates/devdocs/templates/pull-request.md +3 -0
- package/templates/devdocs/templates/review-finding.md +3 -0
- package/templates/workflows/forgedock-review.yml +96 -0
package/commands/work-on.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
description: Pick up a GitHub issue and run the full investigate-build-review-merge pipeline
|
|
3
3
|
argument-hint: [issue number or "next" to pick highest priority]
|
|
4
4
|
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
5
7
|
|
|
6
8
|
# /work-on — Full Issue Pipeline
|
|
7
9
|
|
|
@@ -37,9 +39,11 @@ Orchestrator for the full issue lifecycle: investigate → decompose (if needed)
|
|
|
37
39
|
| 8 | Phase 6: Close & Cleanup | PR merged | No |
|
|
38
40
|
| 9 | Phase 7: Trajectory | Issue closed | Yes |
|
|
39
41
|
|
|
40
|
-
**Phase 3 sub-phase sequence** (
|
|
42
|
+
**Phase 3 sub-phase sequence** (execute in order; 3C.5 and 3C.6 are conditional — see Phase 3B):
|
|
41
43
|
|
|
42
|
-
3A → 3B → 3C → 3C.5 → 3C.6 → 3D → 3E → 3F → 3F.5 → 3G → 3H → 3I → 3I.5 → 3J → 3K → 3L → 3M
|
|
44
|
+
3A → 3B → [3C → 3C.5* → 3C.6*] → 3D → 3E → 3F → 3F.5 → 3G → 3H → 3I → 3I.5 → 3J → 3K → 3L → 3M
|
|
45
|
+
|
|
46
|
+
*3C.5 and 3C.6 are skipped for TRIVIAL tasks; 3C (Builder Contract) is still required. Investigation tasks exit at 3B before 3C.
|
|
43
47
|
|
|
44
48
|
**Universal continuation rule**: After ANY phase or sub-phase completes, check whether a terminal state has been reached. Terminal states are:
|
|
45
49
|
- `workflow:merged` label is set
|
|
@@ -95,23 +99,209 @@ Satellite repos (those without a `staging` branch) receive fast-lane PRs directl
|
|
|
95
99
|
### 0A: Parse input
|
|
96
100
|
Extract project prefix and issue number. If `next`/`pick`: list open issues sorted by priority, skip `needs-human` and `workflow:decomposed`, pick highest priority.
|
|
97
101
|
|
|
102
|
+
**Optional pre-flight**: Before committing to the full pipeline, run `/scope {NUMBER}` to get a complexity estimate (affected files, blast radius, risk flags, and decomposition recommendation). Especially useful for large or ambiguous issues.
|
|
103
|
+
|
|
104
|
+
### 0A.5: Post Heartbeat Annotation
|
|
105
|
+
|
|
106
|
+
Post a lightweight activity signal immediately after resolving the issue number. This gives the stall detector (orchestrate Step 4B.5) a fresh timestamp to compare against `STALL_TIMEOUT`. Without this, the stall detector can only see the last structured comment (INVESTIGATOR, BUILDER, etc.) which may be hours old during a valid long-running phase.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
110
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
111
|
+
**Phase**: Phase 0 — starting pipeline
|
|
112
|
+
**Timestamp**: ${PHASE_START_TIMESTAMP}
|
|
113
|
+
**Issue**: #{NUMBER}"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Also post at major phase entry points** (Phases 1, 3, and 5) — replace `Phase 0` with the correct phase name in each case. These mid-pipeline heartbeats ensure the stall detector sees recent activity during long phases (e.g., a build phase running for 20 minutes is not falsely classified as stalled). Inline snippets are embedded at Phase 1A, Phase 3A, and Phase 5A — agents resuming mid-pipeline encounter them without reading this section. <!-- Added: forge#740 -->
|
|
117
|
+
|
|
118
|
+
**Skip if**: Issue already has a terminal label (`workflow:merged`, `workflow:invalid`, `needs-human`) — no heartbeat needed on a completed issue.
|
|
119
|
+
|
|
98
120
|
### 0B: Load issue + existing context
|
|
99
121
|
```bash
|
|
100
122
|
gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state,comments,milestone
|
|
101
123
|
gh api repos/{GH_REPO}/issues/{NUMBER}/comments --jq '.[] | {id: .id, author: .user.login, body: .body}'
|
|
102
124
|
```
|
|
103
125
|
|
|
104
|
-
**Check**: state (closed → STOP), terminal labels (`workflow:merged`/`workflow:invalid` → STOP), existing agent comments (`FORGE:INVESTIGATOR`, `FORGE:DECOMPOSED`, `FORGE:CONTRACT`, `FORGE:BUILDER`, `FORGE:TRAJECTORY`), parent tracker status, sub-issue status.
|
|
126
|
+
**Check**: state (closed → STOP), terminal labels (`workflow:merged`/`workflow:invalid` → STOP), existing agent comments (`FORGE:INVESTIGATOR`, `FORGE:DECOMPOSED`, `FORGE:CONTRACT`, `FORGE:BUILDER`, `FORGE:TRAJECTORY`, `FORGE:DECISION_RECORD`), parent tracker status, sub-issue status.
|
|
105
127
|
|
|
106
128
|
**Determine resume point**: No comments → Phase 1. Investigation exists + ready-to-build → Phase 3. Builder + no PR → Phase 4. Builder + PR open → Phase 5. PR merged + issue open → Phase 6.
|
|
107
129
|
|
|
130
|
+
### 0B.5: Read Phase Checkpoint (MANDATORY — executes before any phase-skip decision)
|
|
131
|
+
|
|
132
|
+
Query for the latest `<!-- FORGE:CHECKPOINT -->` comment. This is the machine-readable source of truth for the pipeline's current phase position — it takes priority over all prose-based resume heuristics above.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
CHECKPOINT=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
136
|
+
--jq '[.[] | select(.body | contains("FORGE:CHECKPOINT"))] | last | .body // ""')
|
|
137
|
+
|
|
138
|
+
if [ -n "$CHECKPOINT" ]; then
|
|
139
|
+
# Extract next_phase from the JSON block inside the comment
|
|
140
|
+
NEXT_PHASE=$(echo "$CHECKPOINT" | grep -A5 '```json' | grep '"next_phase"' \
|
|
141
|
+
| sed -n 's/.*"next_phase": "\([^"]*\)".*/\1/p')
|
|
142
|
+
echo "Checkpoint found: next_phase=${NEXT_PHASE}"
|
|
143
|
+
fi
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Routing from checkpoint** (overrides prose heuristics above when a checkpoint exists):
|
|
147
|
+
|
|
148
|
+
| `next_phase` value | Resume at |
|
|
149
|
+
|--------------------|-----------|
|
|
150
|
+
| `BUILD` | Phase 3 (skip Phase 1 investigation) |
|
|
151
|
+
| `DECOMPOSE` | Phase 2 (skip Phase 1 investigation) |
|
|
152
|
+
| `REVIEW` | Phase 4 (skip Phase 1–3) |
|
|
153
|
+
| `CLOSE` | Phase 6 (skip Phase 1–5) |
|
|
154
|
+
| *(absent or unrecognized)* | Fall back to prose heuristics above |
|
|
155
|
+
|
|
156
|
+
**If no checkpoint exists**: fall back to prose resume heuristics in Phase 0B above — treat as fresh start at Phase 1.
|
|
157
|
+
|
|
108
158
|
**Classify lane**: Milestone → feature lane (`milestone/{slug}`). No milestone → fast lane (`staging`).
|
|
109
159
|
|
|
110
160
|
**Source branch for review-findings**: Parse `**Code branch**: \`{branch}\`` from body. Branch from there, not main.
|
|
111
161
|
|
|
162
|
+
**Script resolution** — Use the following `resolve_script()` function whenever calling a pipeline script. It enforces the 4-level precedence hierarchy (see `devdocs/project/architecture.md → Script Precedence`):
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
ADAPTIVE_DIR_RAW="${REPO_PATH}/$(yq '.adaptive_scripts.directory // ".forgedock/scripts"' forge.yaml 2>/dev/null || echo '.forgedock/scripts')"
|
|
166
|
+
ADAPTIVE_DIR=$(realpath -m "$ADAPTIVE_DIR_RAW" 2>/dev/null || echo "$ADAPTIVE_DIR_RAW")
|
|
167
|
+
ADAPTIVE_ENABLED=$(yq '.adaptive_scripts.enabled // "true"' forge.yaml 2>/dev/null || echo 'true')
|
|
168
|
+
# Bounds check: reject adaptive_scripts.directory values that escape the repo root.
|
|
169
|
+
# Normalize REPO_PATH the same way ADAPTIVE_DIR is normalized (realpath -m) so a trailing
|
|
170
|
+
# slash in paths.root does not inject a '//' into the glob and trigger a false positive.
|
|
171
|
+
REPO_PATH_NORM=$(realpath -m "$REPO_PATH" 2>/dev/null || echo "$REPO_PATH")
|
|
172
|
+
if [[ "$ADAPTIVE_DIR" != "${REPO_PATH_NORM}/"* ]]; then
|
|
173
|
+
echo "WARNING: adaptive_scripts.directory resolves outside repo root ('$ADAPTIVE_DIR') — adaptive tier disabled" >&2
|
|
174
|
+
ADAPTIVE_ENABLED=false
|
|
175
|
+
fi
|
|
176
|
+
UNIVERSAL_DIR="${FORGEDOCK_HOME:-$(dirname "$(which classify-lane.sh 2>/dev/null || echo 'scripts')")}/scripts"
|
|
177
|
+
|
|
178
|
+
resolve_script() {
|
|
179
|
+
local operation="$1"
|
|
180
|
+
# Tier 2: per-repo adaptive (skip if disabled)
|
|
181
|
+
if [ "$ADAPTIVE_ENABLED" != "false" ] && [ -f "${ADAPTIVE_DIR}/${operation}.sh" ]; then
|
|
182
|
+
echo "adaptive:${ADAPTIVE_DIR}/${operation}.sh"
|
|
183
|
+
return
|
|
184
|
+
fi
|
|
185
|
+
# Tier 3: universal script
|
|
186
|
+
if [ -f "${UNIVERSAL_DIR}/${operation}.sh" ]; then
|
|
187
|
+
echo "universal:${UNIVERSAL_DIR}/${operation}.sh"
|
|
188
|
+
return
|
|
189
|
+
fi
|
|
190
|
+
# Tier 4: prose fallback
|
|
191
|
+
echo "prose:"
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# Canonical tier-dispatch usage pattern — inline at every resolve_script() call site:
|
|
195
|
+
#
|
|
196
|
+
# There is no centralised run_script() function. The pattern below is inlined
|
|
197
|
+
# directly at each call site because each operation has a different prose
|
|
198
|
+
# fallback. Copy and adapt this block wherever resolve_script() is called.
|
|
199
|
+
#
|
|
200
|
+
# Usage pattern at each call site:
|
|
201
|
+
# RESOLUTION=$(resolve_script 'op')
|
|
202
|
+
# TIER="${RESOLUTION%%:*}"
|
|
203
|
+
# SCRIPT_PATH="${RESOLUTION#*:}"
|
|
204
|
+
# case "$TIER" in
|
|
205
|
+
# adaptive|universal) bash "$SCRIPT_PATH" ARGS ;;
|
|
206
|
+
# prose) # inline fallback here ;;
|
|
207
|
+
# esac
|
|
208
|
+
#
|
|
209
|
+
# The case pattern is inlined at every call site (rather than centralised here)
|
|
210
|
+
# because each operation has a different prose fallback — transition-label falls
|
|
211
|
+
# back to inline gh issue edit; classify-lane has no valid prose fallback and
|
|
212
|
+
# must exit 1; validate-pr-target emits a WARNING and continues (the PR review
|
|
213
|
+
# step catches any mismatch before merge). <!-- Added: forge#822 -->
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
When invoking a resolved script, log the tier in the FORGE annotation: `Script tier: {adaptive|universal|prose} ({path})`. This provides full pipeline observability. <!-- Added: forge#670 -->
|
|
217
|
+
|
|
218
|
+
### 0B.1: Apply learned overrides (MANDATORY — run after 0B, before any routing)
|
|
219
|
+
|
|
220
|
+
Read `forge.yaml → learned:` and override runtime variables. If the `learned:` key is absent or empty, all steps below are no-ops — continue to 0C.
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# Read learned section — all reads use // "" fallback so absent keys are silent no-ops
|
|
224
|
+
LEARNED_STAGING=$(yq '.learned.branch_targets.staging // ""' forge.yaml 2>/dev/null || echo '')
|
|
225
|
+
LEARNED_TEST_COMMANDS=$(yq '.learned.test_commands // []' forge.yaml 2>/dev/null || echo '[]')
|
|
226
|
+
LEARNED_LABEL_MAP=$(yq '.learned.label_map // {}' forge.yaml 2>/dev/null || echo '{}')
|
|
227
|
+
LEARNED_COMMIT_STYLE=$(yq '.learned.commit_style // ""' forge.yaml 2>/dev/null || echo '')
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Apply overrides**:
|
|
231
|
+
|
|
232
|
+
1. **Branch target override** — If `LEARNED_STAGING` is non-empty, replace `STAGING_BRANCH` with its value:
|
|
233
|
+
```bash
|
|
234
|
+
[ -n "$LEARNED_STAGING" ] && STAGING_BRANCH="$LEARNED_STAGING" && \
|
|
235
|
+
echo "Learned override: STAGING_BRANCH → $STAGING_BRANCH (from learned.branch_targets.staging)"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
2. **Test commands** — Store `LEARNED_TEST_COMMANDS` for use in Phase 3H (validate). These are appended to the `verification.commands` runs, not replaced:
|
|
239
|
+
```bash
|
|
240
|
+
# Pass LEARNED_TEST_COMMANDS to Phase 3H as additional commands to run after verification.commands
|
|
241
|
+
# (consumed in 3H — store as env var or carry forward in context)
|
|
242
|
+
echo "Learned test commands: $LEARNED_TEST_COMMANDS"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
3. **Label map** — If `LEARNED_LABEL_MAP` is non-empty, export it as `FORGE_LABEL_MAP` so that all subsequent `resolve_script 'transition-label'` invocations (which are child processes) can read it. The script performs the substitution internally: if the canonical label (e.g. `workflow:investigating`) appears as a key in the map, it uses the mapped value instead.
|
|
246
|
+
```bash
|
|
247
|
+
# Export as FORGE_LABEL_MAP so child processes (resolve_script 'transition-label') can read it.
|
|
248
|
+
# All 8 resolve_script 'transition-label' call sites in this command inherit this env var automatically.
|
|
249
|
+
# The script substitutes the canonical workflow:* label with the mapped value when found.
|
|
250
|
+
export FORGE_LABEL_MAP="$LEARNED_LABEL_MAP"
|
|
251
|
+
[ -n "$LEARNED_LABEL_MAP" ] && [ "$LEARNED_LABEL_MAP" != "{}" ] && \
|
|
252
|
+
echo "Learned override: FORGE_LABEL_MAP active — label_map will be applied by resolve_script 'transition-label'"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
4. **Commit style** — If `LEARNED_COMMIT_STYLE` is non-empty, use it in Phase 3M:
|
|
256
|
+
```bash
|
|
257
|
+
[ -n "$LEARNED_COMMIT_STYLE" ] && COMMIT_STYLE="$LEARNED_COMMIT_STYLE" && \
|
|
258
|
+
echo "Learned override: COMMIT_STYLE → $COMMIT_STYLE"
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
<!-- Added: forge#667 — learned section reader -->
|
|
262
|
+
|
|
112
263
|
### 0C: Sync to Project board
|
|
113
264
|
Add issue to project, set Status=In Progress, Lane, Component, Priority, Workflow=Investigating.
|
|
114
265
|
|
|
266
|
+
### 0C.5: Resolve minimal spec set (selective spec loading)
|
|
267
|
+
|
|
268
|
+
Rather than loading the full ~27-command corpus (~1.1 MB / ~276K tokens) into
|
|
269
|
+
context, resolve the **minimal spec set** this run actually needs from the spec
|
|
270
|
+
knowledge graph. Use the universal-tier `graph-query` script via the canonical
|
|
271
|
+
`resolve_script` tier-dispatch pattern:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
# Forward-transitive reachability: work-on + its reachable sub-phases (CONTAINS)
|
|
275
|
+
# + required devdocs (REQUIRES), as repo-relative file paths.
|
|
276
|
+
RESOLUTION=$(resolve_script 'graph-query')
|
|
277
|
+
TIER="${RESOLUTION%%:*}"
|
|
278
|
+
SCRIPT_PATH="${RESOLUTION#*:}"
|
|
279
|
+
case "$TIER" in
|
|
280
|
+
adaptive|universal)
|
|
281
|
+
SPEC_SET=$(bash "$SCRIPT_PATH" load-set work-on 2>/dev/null || echo '[]')
|
|
282
|
+
echo "$SPEC_SET" | jq -r '.[]'
|
|
283
|
+
;;
|
|
284
|
+
prose)
|
|
285
|
+
# Prose fallback: graph-query.sh unavailable — read specs on demand as each
|
|
286
|
+
# Skill(...) is invoked. Selective loading is an optimization, not required.
|
|
287
|
+
: ;;
|
|
288
|
+
esac
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Read ONLY the files returned by `load-set work-on`** when you need full spec
|
|
292
|
+
text during this run — these are the work-on orchestrator plus the sub-phases
|
|
293
|
+
and devdocs reachable from it (e.g. `commands/work-on/build/*`, `commands/work-on/review.md`,
|
|
294
|
+
`commands/review-pr.md`). Do **not** broadly read unrelated command specs
|
|
295
|
+
(`pipeline-health.md`, `audit.md`, `geo-audit.md`, …) that are not in the set.
|
|
296
|
+
Sub-phases are still invoked normally via their existing `Skill(...)` calls; this
|
|
297
|
+
step only narrows what is *pre-read* into context, it does not remove any phase.
|
|
298
|
+
|
|
299
|
+
This is the inverse of `graph-query.sh impact` (forward instead of reverse
|
|
300
|
+
reachability). It is read-only and auto-builds the graph if the gitignored JSON
|
|
301
|
+
is absent — no committed graph is required. The prose tier above handles older
|
|
302
|
+
installs without the scripts layer: selective loading is an optimization, never
|
|
303
|
+
a hard dependency.
|
|
304
|
+
|
|
115
305
|
---
|
|
116
306
|
|
|
117
307
|
## Phase 1: Investigation
|
|
@@ -127,8 +317,24 @@ gh api repos/{GH_REPO}/issues/comments/$COMMENT_ID -X DELETE
|
|
|
127
317
|
|
|
128
318
|
### 1A: Set label
|
|
129
319
|
```bash
|
|
130
|
-
|
|
131
|
-
|
|
320
|
+
RESOLUTION=$(resolve_script 'transition-label')
|
|
321
|
+
TIER="${RESOLUTION%%:*}"; SCRIPT_PATH="${RESOLUTION#*:}"
|
|
322
|
+
case "$TIER" in
|
|
323
|
+
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} investigating ;;
|
|
324
|
+
prose)
|
|
325
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:investigating" \
|
|
326
|
+
--remove-label "workflow:ready-to-build,workflow:building,workflow:in-review,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
327
|
+
;;
|
|
328
|
+
esac
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Post Phase 1 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`):
|
|
332
|
+
```bash
|
|
333
|
+
PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
334
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
335
|
+
**Phase**: Phase 1 — Investigation
|
|
336
|
+
**Timestamp**: ${PHASE_START_TIMESTAMP}
|
|
337
|
+
**Issue**: #{NUMBER}"
|
|
132
338
|
```
|
|
133
339
|
|
|
134
340
|
### 1A.5: Normalize Issue Body (MANDATORY)
|
|
@@ -222,7 +428,7 @@ Use `forge.yaml → review.tech_stack` and the issue domain labels to identify e
|
|
|
222
428
|
4.5. **Rogue commit pre-state comparison (conditional)**: If the issue body references a specific commit as rogue, bad, or unintended (e.g., "rogue commit `abc1234`", "bad commit", "this was never intended"), MUST run `git show {commit}^:{file}` to see the file before that commit. Compare the pre-commit state against the current file. Any block present in the current file but absent in the pre-commit state was introduced by that commit chain and is a candidate for full reversion — not just partial editing. Report the delta (pre vs. current) in the investigation report. Do NOT assume surrounding code near a named import/bug is correct simply because the issue only named a specific sub-problem. (Ref: forge#278 — investigator confirmed the broken import but never ran `git show 18a3a2cf3^:batch.py`; the surrounding 50-line feature gate was also rogue and was preserved by the fix PR, causing a P1 access regression for all non-Scale users)
|
|
223
429
|
5. Domain context discovery (narrow scope only, 1–5 files):
|
|
224
430
|
```bash
|
|
225
|
-
git log --oneline --all -30 -- {affected_files} | grep -
|
|
431
|
+
git log --oneline --all -30 -- {affected_files} | grep -oE '#[0-9]+' | sort -u
|
|
226
432
|
gh issue list -R {GH_REPO} --state closed --limit 8 --search "{function_name}"
|
|
227
433
|
```
|
|
228
434
|
Keep only file/function-level overlap. Max 5 related issues.
|
|
@@ -282,28 +488,149 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:INVESTIGATOR -->
|
|
|
282
488
|
<!-- INVESTIGATION:COMPLETE -->"
|
|
283
489
|
```
|
|
284
490
|
|
|
491
|
+
### 1D: Correction capture (MANDATORY — run before label update)
|
|
492
|
+
|
|
493
|
+
Before routing, scan all non-agent comments for correction signals from the repository owner. Correction signals are owner comments that contain phrases like "no, use", "actually use", "use X instead", "not X, use Y", or "wrong branch". If found, write the correction to `forge.yaml → learned:` and emit a `FORGE:LEARNED` annotation.
|
|
494
|
+
|
|
495
|
+
**Scan for correction signals**:
|
|
496
|
+
```bash
|
|
497
|
+
# Get repo owner login for filtering.
|
|
498
|
+
# Tiered resolution — necessary because project.owner is the GitHub org/user NAME,
|
|
499
|
+
# but comment .user.login is always a personal account login. For org-owned repos
|
|
500
|
+
# these are structurally different (e.g. org="RapierCraftStudios", commenter="mrdubey"),
|
|
501
|
+
# so using project.owner directly silently disables correction capture for all org repos.
|
|
502
|
+
#
|
|
503
|
+
# Resolution order:
|
|
504
|
+
# 1. project.owner_login (explicit override — required for org repos where owner ≠ personal login)
|
|
505
|
+
# 2. gh api repos/{GH_REPO} --jq '.owner.login' (auto-resolves correctly for personal repos)
|
|
506
|
+
# 3. project.owner (backward-compat fallback — still broken for org repos, but avoids hard failure)
|
|
507
|
+
REPO_OWNER=$(yq '.project.owner_login // ""' forge.yaml 2>/dev/null || echo '')
|
|
508
|
+
if [ -z "$REPO_OWNER" ]; then
|
|
509
|
+
REPO_OWNER=$(gh api repos/{GH_REPO} --jq '.owner.login' 2>/dev/null || echo '')
|
|
510
|
+
fi
|
|
511
|
+
if [ -z "$REPO_OWNER" ]; then
|
|
512
|
+
REPO_OWNER=$(yq '.project.owner' forge.yaml 2>/dev/null || echo '')
|
|
513
|
+
fi
|
|
514
|
+
|
|
515
|
+
# Fetch all comments, filter to owner-only, look for correction signals
|
|
516
|
+
CORRECTIONS=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
517
|
+
| jq -r --arg owner "$REPO_OWNER" \
|
|
518
|
+
'.[] | select(.user.login == $owner) | select(
|
|
519
|
+
(.body | test("no,? use|actually use|use .+ instead|not .+, use|wrong branch"; "i"))
|
|
520
|
+
) | .body' 2>/dev/null || echo '')
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**If correction signals found** — extract and write each correction:
|
|
524
|
+
|
|
525
|
+
```bash
|
|
526
|
+
# Example: extract branch target correction "use develop not staging"
|
|
527
|
+
# Adjust regex to the correction pattern detected
|
|
528
|
+
|
|
529
|
+
if [ -n "$CORRECTIONS" ]; then
|
|
530
|
+
echo "Correction signals detected — writing to forge.yaml → learned:"
|
|
531
|
+
echo "$CORRECTIONS"
|
|
532
|
+
|
|
533
|
+
# Write to forge.yaml using yq in-place merge (idempotent — yq merge overwrites existing keys)
|
|
534
|
+
# Always use env variable injection to avoid YAML injection from comment content
|
|
535
|
+
# Example for branch target correction:
|
|
536
|
+
# BRANCH_VALUE="develop"
|
|
537
|
+
# yq eval '.learned.branch_targets.staging = env(BRANCH_VALUE)' -i forge.yaml
|
|
538
|
+
|
|
539
|
+
# After writing, emit FORGE:LEARNED annotation
|
|
540
|
+
LEARNED_KEYS="branch_targets.staging" # replace with actual extracted keys
|
|
541
|
+
CAPTURED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
542
|
+
|
|
543
|
+
# Update captured_at and captured_by metadata
|
|
544
|
+
CAPTURED_AT_VAL="$CAPTURED_AT" yq eval '.learned.captured_at = env(CAPTURED_AT_VAL)' -i forge.yaml
|
|
545
|
+
CAPTURED_BY_VAL="work-on/{NUMBER}" yq eval '.learned.captured_by = env(CAPTURED_BY_VAL)' -i forge.yaml
|
|
546
|
+
|
|
547
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:LEARNED -->
|
|
548
|
+
## Learned Pattern Captured
|
|
549
|
+
|
|
550
|
+
**Source**: Owner correction in comment on issue #{NUMBER}
|
|
551
|
+
**Captured at**: $CAPTURED_AT
|
|
552
|
+
**Keys written**: \`$LEARNED_KEYS\`
|
|
553
|
+
|
|
554
|
+
The following project-specific pattern was detected from owner feedback and written to \`forge.yaml → learned:\`. Future sessions will use this override automatically (read in Phase 0B.1).
|
|
555
|
+
|
|
556
|
+
\`\`\`yaml
|
|
557
|
+
# Written to forge.yaml
|
|
558
|
+
learned:
|
|
559
|
+
# {key}: {value}
|
|
560
|
+
\`\`\`
|
|
561
|
+
|
|
562
|
+
**Idempotency**: yq merge-write — re-running will not duplicate entries."
|
|
563
|
+
|
|
564
|
+
echo "FORGE:LEARNED annotation posted."
|
|
565
|
+
fi
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
**Idempotency guarantee**: Use `yq eval '.learned.key = env(VAR)' -i forge.yaml` — yq overwrites existing keys rather than appending. Re-running the capture step on the same comment produces the same forge.yaml state. <!-- Added: forge#667 -->
|
|
569
|
+
|
|
285
570
|
### 1D: Update labels & route
|
|
286
571
|
|
|
287
572
|
**CONFIRMED or PARTIAL with decompose: NO**:
|
|
288
573
|
```bash
|
|
289
|
-
|
|
574
|
+
RESOLUTION=$(resolve_script 'transition-label')
|
|
575
|
+
TIER="${RESOLUTION%%:*}"; SCRIPT_PATH="${RESOLUTION#*:}"
|
|
576
|
+
case "$TIER" in
|
|
577
|
+
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} ready-to-build ;;
|
|
578
|
+
prose)
|
|
579
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:ready-to-build" \
|
|
580
|
+
--remove-label "workflow:investigating,workflow:building,workflow:in-review,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
581
|
+
;;
|
|
582
|
+
esac
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
Write machine-readable phase checkpoint (MUST execute immediately after label transition, before continuing):
|
|
586
|
+
```bash
|
|
587
|
+
CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
588
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
589
|
+
\`\`\`json
|
|
590
|
+
{\"phase\": \"INVESTIGATION\", \"status\": \"COMPLETE\", \"next_phase\": \"BUILD\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
|
|
591
|
+
\`\`\`"
|
|
290
592
|
```
|
|
291
593
|
<!-- FORGE:PHASE_COMPLETE — Investigation routed to build. See Universal Phase Dispatcher: next phase is Phase 3. Not terminal — continue immediately. -->
|
|
292
594
|
→ Continue to Phase 3.
|
|
293
595
|
|
|
294
596
|
**CONFIRMED or PARTIAL with decompose: YES**:
|
|
295
597
|
```bash
|
|
296
|
-
|
|
598
|
+
RESOLUTION=$(resolve_script 'transition-label')
|
|
599
|
+
TIER="${RESOLUTION%%:*}"; SCRIPT_PATH="${RESOLUTION#*:}"
|
|
600
|
+
case "$TIER" in
|
|
601
|
+
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} decomposed ;;
|
|
602
|
+
prose)
|
|
603
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:decomposed" \
|
|
604
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:merged,workflow:invalid" 2>/dev/null || true
|
|
605
|
+
;;
|
|
606
|
+
esac
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
Write machine-readable phase checkpoint (MUST execute immediately after label transition, before continuing):
|
|
610
|
+
```bash
|
|
611
|
+
CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
612
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
613
|
+
\`\`\`json
|
|
614
|
+
{\"phase\": \"INVESTIGATION\", \"status\": \"COMPLETE\", \"next_phase\": \"DECOMPOSE\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
|
|
615
|
+
\`\`\`"
|
|
297
616
|
```
|
|
298
617
|
<!-- FORGE:PHASE_COMPLETE — Investigation routed to decomposition. See Universal Phase Dispatcher: next phase is Phase 2. Not terminal — continue immediately. -->
|
|
299
618
|
→ Continue to Phase 2 (Decomposition).
|
|
300
619
|
|
|
301
620
|
**INVALID**:
|
|
302
621
|
```bash
|
|
303
|
-
|
|
622
|
+
RESOLUTION=$(resolve_script 'transition-label')
|
|
623
|
+
TIER="${RESOLUTION%%:*}"; SCRIPT_PATH="${RESOLUTION#*:}"
|
|
624
|
+
case "$TIER" in
|
|
625
|
+
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} invalid ;;
|
|
626
|
+
prose)
|
|
627
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:invalid" \
|
|
628
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:merged,workflow:decomposed" 2>/dev/null || true
|
|
629
|
+
;;
|
|
630
|
+
esac
|
|
304
631
|
gh issue close {NUMBER} {GH_FLAG} --comment "Closing as invalid: {reason from investigation}"
|
|
305
632
|
```
|
|
306
|
-
→ STOP.
|
|
633
|
+
→ STOP. No checkpoint written — INVALID is terminal.
|
|
307
634
|
|
|
308
635
|
---
|
|
309
636
|
|
|
@@ -398,9 +725,15 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:DECOMPOSED -->
|
|
|
398
725
|
|
|
399
726
|
### 2F: Update labels
|
|
400
727
|
```bash
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
728
|
+
RESOLUTION=$(resolve_script 'transition-label')
|
|
729
|
+
TIER="${RESOLUTION%%:*}"; SCRIPT_PATH="${RESOLUTION#*:}"
|
|
730
|
+
case "$TIER" in
|
|
731
|
+
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} decomposed ;;
|
|
732
|
+
prose)
|
|
733
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:decomposed" \
|
|
734
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:merged,workflow:invalid" 2>/dev/null || true
|
|
735
|
+
;;
|
|
736
|
+
esac
|
|
404
737
|
```
|
|
405
738
|
|
|
406
739
|
→ STOP. Each sub-issue runs its own `/work-on`.
|
|
@@ -413,9 +746,19 @@ gh issue edit {NUMBER} {GH_FLAG} \
|
|
|
413
746
|
|
|
414
747
|
**Skip if**: `<!-- FORGE:BUILDER -->` exists.
|
|
415
748
|
|
|
416
|
-
**CRITICAL: You MUST execute ALL sub-phases 3A–3M in order.
|
|
749
|
+
**CRITICAL: You MUST execute ALL sub-phases 3A–3M in order. Sub-phases 3C.5 (context) and 3C.6 (architect) are skipped ONLY for TRIVIAL tasks and Investigation tasks — see Phase 3B for classification. For STANDARD and COMPLEX tasks they post mandatory `FORGE:CONTEXT` and `FORGE:ARCHITECT` comments that Phase 3F reads as its primary input. Skipping them without a TRIVIAL/Investigation classification degrades build quality and causes review findings. After each sub-phase, continue to the next — no sub-phase is terminal.**
|
|
417
750
|
|
|
418
751
|
### 3A: Re-read state from GitHub (MANDATORY)
|
|
752
|
+
|
|
753
|
+
**Post Phase 3 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`):
|
|
754
|
+
```bash
|
|
755
|
+
PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
756
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
757
|
+
**Phase**: Phase 3 — Build
|
|
758
|
+
**Timestamp**: ${PHASE_START_TIMESTAMP}
|
|
759
|
+
**Issue**: #{NUMBER}"
|
|
760
|
+
```
|
|
761
|
+
|
|
419
762
|
```bash
|
|
420
763
|
gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state,milestone
|
|
421
764
|
|
|
@@ -426,17 +769,23 @@ gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
|
426
769
|
# Check if build already completed
|
|
427
770
|
gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
428
771
|
--jq '.[] | select(.body | contains("FORGE:BUILDER")) | .body'
|
|
772
|
+
|
|
773
|
+
# Check for existing COMPLEXITY_BAND from a prior run (resume path)
|
|
774
|
+
EXISTING_FAST_PATH=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
775
|
+
--jq '.[] | select(.body | contains("FORGE:FAST_PATH")) | .body' 2>/dev/null | head -1)
|
|
429
776
|
```
|
|
430
777
|
|
|
431
778
|
If no investigation comment with `<!-- INVESTIGATION:COMPLETE -->` → STOP (investigation not complete).
|
|
432
779
|
|
|
433
780
|
Extract from investigation: affected files, root cause, recommendation, task type.
|
|
434
781
|
|
|
435
|
-
### 3B: Classify task type
|
|
782
|
+
### 3B: Classify task type and complexity
|
|
783
|
+
|
|
784
|
+
**Step 1 — Task type classification:**
|
|
436
785
|
|
|
437
786
|
| Signal | Type | Approach |
|
|
438
787
|
|--------|------|----------|
|
|
439
|
-
| Title
|
|
788
|
+
| Title starts with "Investigate:"/"Audit:"/"Research:" | Investigation | Produce issues as deliverables |
|
|
440
789
|
| UI/UX, feature + web/ files | UI/UX | `frontend-design` skill |
|
|
441
790
|
| Feature + services/ | Backend Feature | Implement directly |
|
|
442
791
|
| Feature + both | Full-Stack | Backend first, then frontend-design |
|
|
@@ -444,7 +793,49 @@ Extract from investigation: affected files, root cause, recommendation, task typ
|
|
|
444
793
|
| Bug + services/ | Backend Fix | Direct |
|
|
445
794
|
| Refactor/docs | Maintenance | Direct |
|
|
446
795
|
|
|
447
|
-
**Investigation tasks
|
|
796
|
+
**Investigation tasks — early exit (BEFORE Phase 3C):** If task type = Investigation, skip Phases 3C, 3C.5, and 3C.6 entirely. Post `<!-- FORGE:FAST_PATH -->` comment, then jump directly to Phase 3F (implement → issue creation path). Do NOT run the Builder Contract, Context Gathering, or Architecture Plan for investigation tasks.
|
|
797
|
+
|
|
798
|
+
```bash
|
|
799
|
+
# Post fast-path comment for investigation tasks
|
|
800
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:FAST_PATH -->
|
|
801
|
+
## Fast-Path Classification
|
|
802
|
+
|
|
803
|
+
**COMPLEXITY_BAND**: INVESTIGATION
|
|
804
|
+
**Task type**: Investigation
|
|
805
|
+
**Rationale**: Title prefix 'Investigate:' (or task type = Investigation from investigator report) — skipping Builder Contract (3C), Context Gathering (3C.5), and Architecture Plan (3C.6). Jumping directly to Phase 3F (issue creation).
|
|
806
|
+
**Phases skipped**: 3C, 3C.5, 3C.6"
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
→ Jump to Phase 3F immediately. Do not continue to Phase 3C.
|
|
810
|
+
|
|
811
|
+
**Step 2 — Complexity classification (for non-Investigation tasks):**
|
|
812
|
+
|
|
813
|
+
Classify COMPLEXITY_BAND based on affected file count and task nature:
|
|
814
|
+
|
|
815
|
+
| Condition | COMPLEXITY_BAND |
|
|
816
|
+
|-----------|-----------------|
|
|
817
|
+
| Single file, doc/config/markdown only, no logic changes expected | TRIVIAL |
|
|
818
|
+
| 1–5 files, existing patterns, no cross-service impact | STANDARD |
|
|
819
|
+
| 6+ files, new abstractions, cross-service, migration, schema changes | COMPLEX |
|
|
820
|
+
|
|
821
|
+
Post `<!-- FORGE:FAST_PATH -->` comment immediately after classification:
|
|
822
|
+
|
|
823
|
+
```bash
|
|
824
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:FAST_PATH -->
|
|
825
|
+
## Fast-Path Classification
|
|
826
|
+
|
|
827
|
+
**COMPLEXITY_BAND**: {TRIVIAL|STANDARD|COMPLEX}
|
|
828
|
+
**Task type**: {TASK_TYPE}
|
|
829
|
+
**Affected file count**: {N}
|
|
830
|
+
**Rationale**: {one-sentence explanation of classification decision}
|
|
831
|
+
**Phases skipped**: {list phases skipped, or 'none — full pipeline' for STANDARD/COMPLEX}"
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
**Resume path**: If `EXISTING_FAST_PATH` was read in Phase 3A, extract COMPLEXITY_BAND from it and skip re-classification.
|
|
835
|
+
|
|
836
|
+
**TRIVIAL tasks**: After posting FORGE:FAST_PATH, skip Phase 3C.5 (Context Gathering) and Phase 3C.6 (Architecture Plan) only. Phase 3C (Builder Contract) is **retained** — it still runs. Continue: 3C (Builder Contract) → 3D → 3E → 3F → 3F.5 → 3G → 3H onward. When filling in **Phases skipped** in the FORGE:FAST_PATH comment, write: `3C.5, 3C.6`.
|
|
837
|
+
|
|
838
|
+
**STANDARD and COMPLEX tasks**: Run full pipeline — 3C → 3C.5 → 3C.6 → 3D onward. No phases skipped.
|
|
448
839
|
|
|
449
840
|
### 3C: Builder Contract (MANDATORY)
|
|
450
841
|
|
|
@@ -476,9 +867,11 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CONTRACT -->
|
|
|
476
867
|
|
|
477
868
|
Contract must be grounded in the investigation report. Adversarially validate proposed fixes against adjacent system layers.
|
|
478
869
|
|
|
479
|
-
### 3C.5: Context Gathering (MANDATORY —
|
|
870
|
+
### 3C.5: Context Gathering (MANDATORY for STANDARD/COMPLEX — skip for TRIVIAL)
|
|
480
871
|
|
|
481
|
-
**
|
|
872
|
+
**Skip if COMPLEXITY_BAND: TRIVIAL** (classified in Phase 3B) — post nothing, proceed directly to Phase 3C.6. Trivial single-file changes have no institutional memory value to surface.
|
|
873
|
+
|
|
874
|
+
**For STANDARD and COMPLEX tasks**: This phase is NOT optional. Run it regardless. Do NOT skip it without a TRIVIAL classification from Phase 3B.
|
|
482
875
|
|
|
483
876
|
Surface institutional memory before writing code. Extract function names from the contract deliverables table:
|
|
484
877
|
|
|
@@ -486,7 +879,7 @@ Surface institutional memory before writing code. Extract function names from th
|
|
|
486
879
|
FUNCTION_NAMES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
487
880
|
--jq '.[] | select(.body | contains("FORGE:CONTRACT")) | .body' \
|
|
488
881
|
| awk '/^### Deliverables/{p=1; next} /^### /{p=0} p' \
|
|
489
|
-
| grep -
|
|
882
|
+
| grep -oE '`[A-Za-z_][A-Za-z0-9_]*`' \
|
|
490
883
|
| tr -d '`' | sort -u | tr '\n' ' ' | xargs)
|
|
491
884
|
```
|
|
492
885
|
|
|
@@ -511,7 +904,7 @@ done
|
|
|
511
904
|
|
|
512
905
|
**C2: Past Bugs in the Same Module**
|
|
513
906
|
```bash
|
|
514
|
-
git log --oneline -30 -- {AFFECTED_FILES} | grep -
|
|
907
|
+
git log --oneline -30 -- {AFFECTED_FILES} | grep -oE '#[0-9]+' | sort -u | head -8
|
|
515
908
|
# For each issue: fetch title + root cause, keep only bug/fix/review-finding labeled. Max 5.
|
|
516
909
|
```
|
|
517
910
|
|
|
@@ -557,13 +950,15 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CONTEXT -->
|
|
|
557
950
|
|
|
558
951
|
If total time exceeds 2 minutes, post partial results with `<!-- FORGE:CONTEXT:PARTIAL -->`.
|
|
559
952
|
|
|
560
|
-
### 3C.6: Architecture Plan (MANDATORY)
|
|
953
|
+
### 3C.6: Architecture Plan (MANDATORY for STANDARD/COMPLEX — skip for TRIVIAL)
|
|
954
|
+
|
|
955
|
+
**Skip if COMPLEXITY_BAND: TRIVIAL** (classified in Phase 3B) — post nothing, proceed directly to Phase 3D. Trivial single-file changes have no multi-path consistency risk.
|
|
561
956
|
|
|
562
|
-
**This phase is NOT optional
|
|
957
|
+
**For STANDARD and COMPLEX tasks**: This phase is NOT optional. Always run it. Even a 1-file STANDARD fix benefits from cross-path consistency checks. Do NOT skip without a TRIVIAL classification from Phase 3B.
|
|
563
958
|
|
|
564
959
|
Trace ALL affected code paths before writing code.
|
|
565
960
|
|
|
566
|
-
**
|
|
961
|
+
**Additional skip condition** (STANDARD tasks only): Issue creates ONLY new files with no existing callers AND title starts with "docs:" or "chore:".
|
|
567
962
|
|
|
568
963
|
**A1: Read Entry Points** — For each affected file: identify the primary function, all callers (grep), and sibling implementations. Read 3–5 most relevant files, max 8 total.
|
|
569
964
|
|
|
@@ -633,25 +1028,57 @@ If budget exceeded (3 min), use `<!-- FORGE:ARCHITECT:PARTIAL -->`.
|
|
|
633
1028
|
|
|
634
1029
|
### 3D: Set building label
|
|
635
1030
|
```bash
|
|
636
|
-
|
|
1031
|
+
RESOLUTION=$(resolve_script 'transition-label')
|
|
1032
|
+
TIER="${RESOLUTION%%:*}"; SCRIPT_PATH="${RESOLUTION#*:}"
|
|
1033
|
+
case "$TIER" in
|
|
1034
|
+
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} building ;;
|
|
1035
|
+
prose)
|
|
1036
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:building" \
|
|
1037
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:in-review,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
1038
|
+
;;
|
|
1039
|
+
esac
|
|
637
1040
|
```
|
|
638
1041
|
|
|
639
1042
|
### 3E: Create worktree
|
|
640
1043
|
|
|
641
1044
|
Branch slug from title (lowercase, hyphenated, max 40 chars). Prefix: `fix/` (bugs) or `feat/` (features).
|
|
642
1045
|
|
|
1046
|
+
**Compute `PR_BASE` before worktree creation** — the source branch for the worktree MUST match the PR target. Compute `PR_BASE` now using `classify-lane.sh` so both worktree creation and Phase 4C use the same deterministic value. <!-- Added: forge#639 -->
|
|
1047
|
+
|
|
1048
|
+
```bash
|
|
1049
|
+
# Compute PR_BASE deterministically from issue milestone — no LLM interpretation
|
|
1050
|
+
RESOLUTION=$(resolve_script 'classify-lane')
|
|
1051
|
+
TIER="${RESOLUTION%%:*}"; SCRIPT_PATH="${RESOLUTION#*:}"
|
|
1052
|
+
case "$TIER" in
|
|
1053
|
+
adaptive|universal)
|
|
1054
|
+
if ! PR_BASE=$(bash "$SCRIPT_PATH" {NUMBER} -R {GH_REPO}); then
|
|
1055
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "BLOCKER: classify-lane.sh failed to compute PR target — see script error above. Adding needs-human."
|
|
1056
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human"
|
|
1057
|
+
exit 1
|
|
1058
|
+
fi
|
|
1059
|
+
;;
|
|
1060
|
+
prose)
|
|
1061
|
+
# classify-lane has no valid prose fallback — the script output is authoritative.
|
|
1062
|
+
# Without it, PR target cannot be determined safely. Add needs-human and stop.
|
|
1063
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "BLOCKER: classify-lane.sh not installed (prose tier). Cannot compute PR target deterministically. Adding needs-human."
|
|
1064
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human"
|
|
1065
|
+
exit 1
|
|
1066
|
+
;;
|
|
1067
|
+
esac
|
|
1068
|
+
```
|
|
1069
|
+
|
|
643
1070
|
**Determine source branch**:
|
|
644
1071
|
- Review-finding → parse `**Code branch**: \`{branch}\`` from issue body; branch from `origin/{branch}`
|
|
645
1072
|
- **Milestone review-finding hybrid lane** (Code branch matches `milestone/*`): High-risk lane. NEVER use `git merge` to resolve conflicts — use `git rebase` or `git cherry-pick` only. If conflicts can't be resolved without merge, post comment, add `needs-human`, STOP.
|
|
646
|
-
- Feature lane (has milestone) → branch from `origin/{PR_BASE}`
|
|
647
|
-
- Fast lane (no milestone) → branch from `origin/{STAGING_BRANCH}`
|
|
1073
|
+
- Feature lane (has milestone) → branch from `origin/{PR_BASE}` (PR_BASE now set above)
|
|
1074
|
+
- Fast lane (no milestone) → branch from `origin/{PR_BASE}` (PR_BASE = `{STAGING_BRANCH}`)
|
|
648
1075
|
|
|
649
1076
|
```bash
|
|
650
1077
|
cd {REPO_PATH}
|
|
651
1078
|
git fetch origin
|
|
652
1079
|
BRANCH="fix/{slug}-{NUMBER}"
|
|
653
1080
|
WORKTREE_PATH="{REPO_PATH}/.claude/worktrees/{BRANCH_SLUG}"
|
|
654
|
-
git worktree add {WORKTREE_PATH} -b {BRANCH} origin/{
|
|
1081
|
+
git worktree add {WORKTREE_PATH} -b {BRANCH} origin/{PR_BASE}
|
|
655
1082
|
```
|
|
656
1083
|
|
|
657
1084
|
If worktree already exists: verify correct branch, reuse or remove and recreate.
|
|
@@ -794,6 +1221,30 @@ fi
|
|
|
794
1221
|
```
|
|
795
1222
|
Typecheck or build failures are BLOCKING.
|
|
796
1223
|
|
|
1224
|
+
**Learned test commands** — After all `verification.commands` steps complete, run any commands from `learned.test_commands` (captured from owner corrections in Phase 1D or set manually in forge.yaml):
|
|
1225
|
+
|
|
1226
|
+
```bash
|
|
1227
|
+
# LEARNED_TEST_COMMANDS was set in Phase 0B.1 from forge.yaml → learned.test_commands
|
|
1228
|
+
# If empty/null, this block is a no-op
|
|
1229
|
+
if [ -n "$LEARNED_TEST_COMMANDS" ] && [ "$LEARNED_TEST_COMMANDS" != "[]" ]; then
|
|
1230
|
+
echo "Running learned test commands..."
|
|
1231
|
+
# yq outputs each entry on its own line with -r flag
|
|
1232
|
+
echo "$LEARNED_TEST_COMMANDS" | yq '.[]' | while IFS= read -r cmd; do
|
|
1233
|
+
[ -z "$cmd" ] && continue
|
|
1234
|
+
echo "Running learned command: $cmd"
|
|
1235
|
+
eval "$cmd" 2>&1 | tail -30
|
|
1236
|
+
CMD_EXIT=$?
|
|
1237
|
+
if [ $CMD_EXIT -ne 0 ]; then
|
|
1238
|
+
echo "FAILED (exit $CMD_EXIT): $cmd"
|
|
1239
|
+
exit $CMD_EXIT
|
|
1240
|
+
fi
|
|
1241
|
+
done
|
|
1242
|
+
else
|
|
1243
|
+
echo "No learned test commands configured — skipping"
|
|
1244
|
+
fi
|
|
1245
|
+
```
|
|
1246
|
+
Learned test command failures are BLOCKING (same as verification.commands failures). <!-- Added: forge#667 -->
|
|
1247
|
+
|
|
797
1248
|
### 3I: Frontend proxy wiring check (MANDATORY)
|
|
798
1249
|
|
|
799
1250
|
Skip if no TS/TSX files changed.
|
|
@@ -922,12 +1373,23 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:BUILDER -->
|
|
|
922
1373
|
{checklist from contract, marked pass/fail}
|
|
923
1374
|
|
|
924
1375
|
### Testing Checklist
|
|
925
|
-
- [ ] {scenario 1}
|
|
926
|
-
- [ ] {scenario 2}
|
|
1376
|
+
- [ ] {scenario 1} [type:api]
|
|
1377
|
+
- [ ] {scenario 2} [type:unit]
|
|
1378
|
+
|
|
1379
|
+
> **Test-type annotation** (optional): Append `[type:api]`, `[type:unit]`, `[type:e2e]`, or `[type:manual]` to each checklist item. The test gate reads this annotation directly and skips regex inference. Omit it to rely on regex classification fallback.
|
|
927
1380
|
|
|
928
1381
|
<!-- FORGE:BUILDER:COMPLETE -->"
|
|
929
1382
|
```
|
|
930
1383
|
|
|
1384
|
+
Write machine-readable phase checkpoint (MUST execute immediately after FORGE:BUILDER comment is posted, before Phase 4):
|
|
1385
|
+
```bash
|
|
1386
|
+
CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1387
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
1388
|
+
\`\`\`json
|
|
1389
|
+
{\"phase\": \"BUILD\", \"status\": \"COMPLETE\", \"next_phase\": \"REVIEW\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
|
|
1390
|
+
\`\`\`"
|
|
1391
|
+
```
|
|
1392
|
+
|
|
931
1393
|
---
|
|
932
1394
|
|
|
933
1395
|
## Phase 4: PR Creation
|
|
@@ -951,7 +1413,50 @@ cd {WORKTREE_PATH} && git push -u origin {BRANCH}
|
|
|
951
1413
|
If fails: try `--force-with-lease`. If still fails: post comment, add `needs-human`, STOP.
|
|
952
1414
|
|
|
953
1415
|
### 4C: Determine PR target
|
|
954
|
-
|
|
1416
|
+
`PR_BASE` was computed in Phase 3E. If somehow unset (e.g., resumed session after compaction), recompute:
|
|
1417
|
+
```bash
|
|
1418
|
+
RESOLUTION=$(resolve_script 'classify-lane')
|
|
1419
|
+
TIER="${RESOLUTION%%:*}"; SCRIPT_PATH="${RESOLUTION#*:}"
|
|
1420
|
+
case "$TIER" in
|
|
1421
|
+
adaptive|universal)
|
|
1422
|
+
if ! PR_BASE=$(bash "$SCRIPT_PATH" {NUMBER} -R {GH_REPO}); then
|
|
1423
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "BLOCKER: classify-lane.sh failed to recompute PR target — see script error above. Adding needs-human."
|
|
1424
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human"
|
|
1425
|
+
exit 1
|
|
1426
|
+
fi
|
|
1427
|
+
;;
|
|
1428
|
+
prose)
|
|
1429
|
+
# No valid prose fallback — see Phase 3E note.
|
|
1430
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "BLOCKER: classify-lane.sh not installed (prose tier). Cannot recompute PR target. Adding needs-human."
|
|
1431
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human"
|
|
1432
|
+
exit 1
|
|
1433
|
+
;;
|
|
1434
|
+
esac
|
|
1435
|
+
```
|
|
1436
|
+
Output is authoritative — no prose fallback. Script exits 1 on error (invalid issue, `gh` auth failure, or milestone branch absent on remote); treat non-zero exit as `needs-human` and STOP. <!-- Added: forge#669, forge#639 -->
|
|
1437
|
+
|
|
1438
|
+
### 4C.5: Validate PR target against classified lane
|
|
1439
|
+
```bash
|
|
1440
|
+
RESOLUTION=$(resolve_script 'validate-pr-target')
|
|
1441
|
+
TIER="${RESOLUTION%%:*}"; SCRIPT_PATH="${RESOLUTION#*:}"
|
|
1442
|
+
case "$TIER" in
|
|
1443
|
+
adaptive|universal)
|
|
1444
|
+
bash "$SCRIPT_PATH" {PR_BASE} {CLASSIFIED_LANE}
|
|
1445
|
+
;;
|
|
1446
|
+
prose)
|
|
1447
|
+
# validate-pr-target has no safe prose fallback — silently skipping validation risks
|
|
1448
|
+
# merging to the wrong branch. Log a warning but do NOT block the pipeline; the PR
|
|
1449
|
+
# review step will catch a mismatched target before merge.
|
|
1450
|
+
echo "WARNING: validate-pr-target.sh not installed (prose tier) — skipping lane validation. Confirm PR base manually." >&2
|
|
1451
|
+
;;
|
|
1452
|
+
esac
|
|
1453
|
+
```
|
|
1454
|
+
`{CLASSIFIED_LANE}` is the value returned by `classify-lane.sh` in Phase 4C. `{PR_BASE}` is the branch the PR will target. If exit code is 1 (mismatch):
|
|
1455
|
+
```bash
|
|
1456
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "BLOCKING: validate-pr-target.sh — PR base \`{PR_BASE}\` does not match classified lane \`{CLASSIFIED_LANE}\`. Manual intervention required."
|
|
1457
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human"
|
|
1458
|
+
```
|
|
1459
|
+
→ STOP. Do NOT proceed to Phase 4D. <!-- Added: forge#671 -->
|
|
955
1460
|
|
|
956
1461
|
### 4D: Create PR
|
|
957
1462
|
```bash
|
|
@@ -978,7 +1483,15 @@ If PR already exists for this branch, use the existing PR number.
|
|
|
978
1483
|
|
|
979
1484
|
### 4E: Update labels
|
|
980
1485
|
```bash
|
|
981
|
-
|
|
1486
|
+
RESOLUTION=$(resolve_script 'transition-label')
|
|
1487
|
+
TIER="${RESOLUTION%%:*}"; SCRIPT_PATH="${RESOLUTION#*:}"
|
|
1488
|
+
case "$TIER" in
|
|
1489
|
+
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} in-review ;;
|
|
1490
|
+
prose)
|
|
1491
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:in-review" \
|
|
1492
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
1493
|
+
;;
|
|
1494
|
+
esac
|
|
982
1495
|
```
|
|
983
1496
|
|
|
984
1497
|
---
|
|
@@ -986,6 +1499,16 @@ gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:in-review" --remove-label
|
|
|
986
1499
|
## Phase 5: Auto-Review
|
|
987
1500
|
|
|
988
1501
|
### 5A: Re-read state from GitHub (MANDATORY)
|
|
1502
|
+
|
|
1503
|
+
**Post Phase 5 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`):
|
|
1504
|
+
```bash
|
|
1505
|
+
PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
1506
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
1507
|
+
**Phase**: Phase 5 — Review
|
|
1508
|
+
**Timestamp**: ${PHASE_START_TIMESTAMP}
|
|
1509
|
+
**Issue**: #{NUMBER}"
|
|
1510
|
+
```
|
|
1511
|
+
|
|
989
1512
|
```bash
|
|
990
1513
|
gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state
|
|
991
1514
|
PR_NUMBER=$(gh pr list {GH_FLAG} --head {BRANCH} --json number --jq '.[0].number')
|
|
@@ -1029,10 +1552,19 @@ gh pr view {PR_NUMBER} {GH_FLAG} --json state,mergedAt --jq '{state: .state, mer
|
|
|
1029
1552
|
gh issue view {NUMBER} {GH_FLAG} --json state --jq '.state'
|
|
1030
1553
|
```
|
|
1031
1554
|
|
|
1032
|
-
- PR MERGED + issue CLOSED → proceed to Phase 6
|
|
1033
|
-
- PR MERGED + issue OPEN → close issue manually
|
|
1555
|
+
- PR MERGED + issue CLOSED → write checkpoint, then proceed to Phase 6
|
|
1556
|
+
- PR MERGED + issue OPEN → close issue manually, write checkpoint, proceed to Phase 6
|
|
1034
1557
|
- PR NOT MERGED → `gh pr merge {PR_NUMBER} {GH_FLAG} --merge --auto`. If fails → post comment, add `needs-human`, STOP.
|
|
1035
1558
|
|
|
1559
|
+
**When PR is MERGED — write machine-readable phase checkpoint (MANDATORY)**:
|
|
1560
|
+
```bash
|
|
1561
|
+
CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1562
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
1563
|
+
\`\`\`json
|
|
1564
|
+
{\"phase\": \"REVIEW\", \"status\": \"COMPLETE\", \"next_phase\": \"CLOSE\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
|
|
1565
|
+
\`\`\`"
|
|
1566
|
+
```
|
|
1567
|
+
|
|
1036
1568
|
<!-- FORGE:PHASE_COMPLETE — Review done, PR merged. See Universal Phase Dispatcher: next phase is Phase 6 (Close & Cleanup). Not terminal — continue immediately. -->
|
|
1037
1569
|
|
|
1038
1570
|
**After /review-pr returns and the PR is confirmed merged: immediately proceed to Phase 6 (Close & Cleanup). Do NOT stop here. `REVIEW_RESULT: status: COMPLETE` is an intermediate result — the pipeline is NOT done. Invoke Phase 6 now to close the issue, update labels, post the trajectory log, and clean up the worktree.**
|
|
@@ -1077,9 +1609,15 @@ If all phases complete:
|
|
|
1077
1609
|
```bash
|
|
1078
1610
|
gh issue close {NUMBER} {GH_FLAG} \
|
|
1079
1611
|
--comment "Closed: PR #{PR_NUMBER} merged to \`{PR_BASE}\`. Closes #{NUMBER}."
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1612
|
+
RESOLUTION=$(resolve_script 'transition-label')
|
|
1613
|
+
TIER="${RESOLUTION%%:*}"; SCRIPT_PATH="${RESOLUTION#*:}"
|
|
1614
|
+
case "$TIER" in
|
|
1615
|
+
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} merged ;;
|
|
1616
|
+
prose)
|
|
1617
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:merged" \
|
|
1618
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
1619
|
+
;;
|
|
1620
|
+
esac
|
|
1083
1621
|
```
|
|
1084
1622
|
|
|
1085
1623
|
### 6D: Parent tracker update (sub-issues only)
|
|
@@ -1088,7 +1626,8 @@ gh issue edit {NUMBER} {GH_FLAG} \
|
|
|
1088
1626
|
|
|
1089
1627
|
```bash
|
|
1090
1628
|
PARENT_REF=$(gh issue view {NUMBER} {GH_FLAG} --json body --jq '.body' \
|
|
1091
|
-
| grep -
|
|
1629
|
+
| grep -iE '(part of|spawned from|sub-issue of|parent issue:?|parent:)\s*#[0-9]+' \
|
|
1630
|
+
| sed -n 's/.*#\([0-9][0-9]*\).*/\1/p' | head -1)
|
|
1092
1631
|
```
|
|
1093
1632
|
|
|
1094
1633
|
If parent found: check off this sub-issue in parent body. If ALL sub-issues checked off → close parent with `workflow:merged`.
|
|
@@ -1155,6 +1694,105 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:TRAJECTORY -->
|
|
|
1155
1694
|
**Pipeline completed**: {TIMESTAMP}"
|
|
1156
1695
|
```
|
|
1157
1696
|
|
|
1697
|
+
### 7C: Graph Decision Record (MANDATORY when PR exists)
|
|
1698
|
+
|
|
1699
|
+
**Skip if**: `{PR_NUMBER}` is empty (investigation-only tasks with no PR) OR `<!-- FORGE:DECISION_RECORD -->` already posted on the PR.
|
|
1700
|
+
|
|
1701
|
+
**Purpose**: Post a single consolidated provenance artifact to the PR that proves the merge was backed by citable evidence. Enables downstream benchmarking queries (repeated-mistake rate, stale-edge hit rate, review escape rate) by making every pipeline run queryable via `gh api`.
|
|
1702
|
+
|
|
1703
|
+
**Idempotency check**:
|
|
1704
|
+
```bash
|
|
1705
|
+
GDR_EXISTS=$(gh api repos/{GH_REPO}/issues/{PR_NUMBER}/comments \
|
|
1706
|
+
--jq '[.[] | select(.body | contains("FORGE:DECISION_RECORD"))] | length > 0' 2>/dev/null || echo "false")
|
|
1707
|
+
```
|
|
1708
|
+
|
|
1709
|
+
**Extract context edge counts** from FORGE:CONTEXT comment (already posted on issue):
|
|
1710
|
+
```bash
|
|
1711
|
+
CONTEXT_COMMENT=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
1712
|
+
--jq '.[] | select(.body | contains("FORGE:CONTEXT")) | .body' 2>/dev/null | head -1)
|
|
1713
|
+
|
|
1714
|
+
# Count historical review-finding issue references (#NNN patterns in Context comment)
|
|
1715
|
+
REVIEW_FINDING_COUNT=$(echo "$CONTEXT_COMMENT" | grep -oE '#[0-9]+' | wc -l | tr -d ' ')
|
|
1716
|
+
REVIEW_FINDING_COUNT=${REVIEW_FINDING_COUNT:-0}
|
|
1717
|
+
```
|
|
1718
|
+
|
|
1719
|
+
**Extract review verdict and findings count** from PR review summary (Phase 9 of review-pr):
|
|
1720
|
+
```bash
|
|
1721
|
+
REVIEW_SUMMARY=$(gh api repos/{GH_REPO}/issues/{PR_NUMBER}/comments \
|
|
1722
|
+
--jq '[.[] | select(.body | contains("FORGE:REVIEWER") or (.body | test("APPROVED:|CHANGES REQUESTED:"; "i")))] | last | .body // ""' 2>/dev/null || echo '')
|
|
1723
|
+
|
|
1724
|
+
REVIEW_VERDICT=$(echo "$REVIEW_SUMMARY" | sed -n 's/.*Verdict: \(APPROVED\|CHANGES REQUESTED\).*/\1/p' | head -1 || echo "APPROVED")
|
|
1725
|
+
REVIEW_VERDICT="${REVIEW_VERDICT:-APPROVED}"
|
|
1726
|
+
FINDINGS_COUNT=$(echo "$REVIEW_SUMMARY" | grep -oE '[0-9]+ findings' | grep -oE '[0-9]+' | head -1 || echo "0")
|
|
1727
|
+
FINDINGS_COUNT="${FINDINGS_COUNT:-0}"
|
|
1728
|
+
AGENTS_RUN=$(echo "$REVIEW_SUMMARY" | grep -oE '[0-9]+ agents' | grep -oE '[0-9]+' | head -1 || echo "0")
|
|
1729
|
+
AGENTS_RUN="${AGENTS_RUN:-0}"
|
|
1730
|
+
```
|
|
1731
|
+
|
|
1732
|
+
**Post GDR to PR** (not to issue — PR comment survives as permanent artifact on the merged diff):
|
|
1733
|
+
```bash
|
|
1734
|
+
if [ "$GDR_EXISTS" != "true" ] && [ -n "{PR_NUMBER}" ]; then
|
|
1735
|
+
GDR_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1736
|
+
HEAD_SHA=$(gh pr view {PR_NUMBER} {GH_FLAG} --json headRefOid --jq '.headRefOid' 2>/dev/null || echo "")
|
|
1737
|
+
MERGE_COMMIT=$(gh pr view {PR_NUMBER} {GH_FLAG} --json mergeCommit --jq '.mergeCommit.oid // ""' 2>/dev/null || echo "")
|
|
1738
|
+
|
|
1739
|
+
gh pr comment {PR_NUMBER} {GH_FLAG} --body "<!-- FORGE:DECISION_RECORD -->
|
|
1740
|
+
## Graph Decision Record — Issue #${NUMBER} / PR #${PR_NUMBER}
|
|
1741
|
+
|
|
1742
|
+
\`\`\`json
|
|
1743
|
+
{
|
|
1744
|
+
\"schema_version\": \"1\",
|
|
1745
|
+
\"issue\": ${NUMBER},
|
|
1746
|
+
\"pr\": ${PR_NUMBER},
|
|
1747
|
+
\"repo\": \"{GH_REPO}\",
|
|
1748
|
+
\"lane\": \"{lane}\",
|
|
1749
|
+
\"pr_base\": \"{PR_BASE}\",
|
|
1750
|
+
\"branch\": \"{BRANCH}\",
|
|
1751
|
+
\"head_sha\": \"${HEAD_SHA}\",
|
|
1752
|
+
\"merge_commit\": \"${MERGE_COMMIT}\",
|
|
1753
|
+
\"investigation\": {
|
|
1754
|
+
\"verdict\": \"{VERDICT}\",
|
|
1755
|
+
\"confidence\": \"{CONFIDENCE}\",
|
|
1756
|
+
\"task_type\": \"{TASK_TYPE}\"
|
|
1757
|
+
},
|
|
1758
|
+
\"context\": {
|
|
1759
|
+
\"historical_edges_referenced\": ${REVIEW_FINDING_COUNT},
|
|
1760
|
+
\"forge_annotations_read\": [\"FORGE:INVESTIGATOR\", \"FORGE:CONTRACT\", \"FORGE:CONTEXT\", \"FORGE:ARCHITECT\", \"FORGE:BUILDER\"]
|
|
1761
|
+
},
|
|
1762
|
+
\"build\": {
|
|
1763
|
+
\"files_changed\": {FILES_CHANGED},
|
|
1764
|
+
\"quality_gate\": \"{pass|fail}\",
|
|
1765
|
+
\"quality_gate_iterations\": {GATE_ITERATIONS}
|
|
1766
|
+
},
|
|
1767
|
+
\"review\": {
|
|
1768
|
+
\"verdict\": \"${REVIEW_VERDICT:-APPROVED}\",
|
|
1769
|
+
\"findings_created\": ${FINDINGS_COUNT},
|
|
1770
|
+
\"agents_run\": ${AGENTS_RUN}
|
|
1771
|
+
},
|
|
1772
|
+
\"merge\": {
|
|
1773
|
+
\"merged_at\": \"${GDR_TIMESTAMP}\",
|
|
1774
|
+
\"justification\": \"Investigation confirmed ({VERDICT}/{CONFIDENCE}), quality gate passed, review ${REVIEW_VERDICT:-approved}\"
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
\`\`\`
|
|
1778
|
+
|
|
1779
|
+
**Queryable**: \`gh api repos/{GH_REPO}/issues/{PR_NUMBER}/comments --jq '[.[] | select(.body | contains(\"FORGE:DECISION_RECORD\"))] | .[0].body\`"
|
|
1780
|
+
fi
|
|
1781
|
+
```
|
|
1782
|
+
|
|
1783
|
+
**Benchmarking**: Query all GDRs for a repo to compute pipeline metrics:
|
|
1784
|
+
```bash
|
|
1785
|
+
# Fetch all merged PRs and extract their GDR JSON blocks for metric computation
|
|
1786
|
+
# (used by /pipeline-health to measure repeated-mistake rate, stale-edge hit rate, etc.)
|
|
1787
|
+
gh pr list -R {GH_REPO} --state merged --limit 100 --json number \
|
|
1788
|
+
--jq '.[].number' | while read pr; do
|
|
1789
|
+
gh api repos/{GH_REPO}/issues/$pr/comments \
|
|
1790
|
+
--jq '.[] | select(.body | contains("FORGE:DECISION_RECORD")) | .body' 2>/dev/null
|
|
1791
|
+
done
|
|
1792
|
+
```
|
|
1793
|
+
|
|
1794
|
+
<!-- Added: forge#776 -->
|
|
1795
|
+
|
|
1158
1796
|
---
|
|
1159
1797
|
|
|
1160
1798
|
## Error Handling
|