forgedock 1.0.16 → 1.0.17
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 +21 -3
- package/bin/forgedock.mjs +1992 -540
- 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/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/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 +2 -0
- package/commands/milestone.md +2 -0
- package/commands/optimize.md +411 -0
- package/commands/orchestrate.md +118 -0
- package/commands/pipeline-health.md +60 -1
- package/commands/qa-sweep.md +2 -0
- package/commands/quality-gate.md +37 -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 +2 -0
- 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/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 +2 -0
- 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 +624 -36
- package/docs/BRAND.md +51 -0
- package/docs/CONFIG.md +94 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +174 -0
- package/docs/claude-breakpoints.json +47 -0
- package/docs/site/getting-started.md +21 -0
- package/package.json +3 -3
- package/scripts/classify-lane.sh +145 -0
- package/scripts/transition-label.sh +161 -0
- package/scripts/validate-pr-target.sh +30 -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
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
description: Pre-implementation context gathering — surfaces historical findings, bug patterns, and related code paths before the builder writes any code
|
|
3
3
|
argument-hint: [issue number] [affected_files...] [--functions function_names...]
|
|
4
4
|
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
5
7
|
|
|
6
8
|
# work-on/build/context — Pre-Implementation Context Gathering
|
|
7
9
|
|
|
@@ -11,6 +13,23 @@ argument-hint: [issue number] [affected_files...] [--functions function_names...
|
|
|
11
13
|
|
|
12
14
|
---
|
|
13
15
|
|
|
16
|
+
## COMPLEXITY_BAND Guard (check BEFORE all phases)
|
|
17
|
+
|
|
18
|
+
Read COMPLEXITY_BAND from the `<!-- FORGE:FAST_PATH -->` comment on the issue:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
COMPLEXITY_BAND=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
22
|
+
--jq '.[] | select(.body | contains("FORGE:FAST_PATH")) | .body' 2>/dev/null \
|
|
23
|
+
| sed -n 's/.*\*\*COMPLEXITY_BAND\*\*: \([A-Z_]*\).*/\1/p' | head -1)
|
|
24
|
+
COMPLEXITY_BAND="${COMPLEXITY_BAND:-STANDARD}"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**If COMPLEXITY_BAND: TRIVIAL** → skip all phases (C-1 through C4), post NO comment, return empty briefing to caller immediately. Do not query GitHub, do not read files. This is not an error — trivial single-file changes have no institutional memory to surface. <!-- Added: forge#679 -->
|
|
28
|
+
|
|
29
|
+
**If COMPLEXITY_BAND: STANDARD or COMPLEX** → proceed to Phase C-1 below.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
14
33
|
## Mission
|
|
15
34
|
|
|
16
35
|
Surface what went wrong in this area before the builder writes a single line of code. The builder starts with the investigator report and contract — this step adds institutional memory: what did review agents catch last time someone touched these files, what bugs recurred, what other paths must stay consistent. When prior investigation Gists are linked in the issue body, fetch and summarize them so the builder has cross-issue context without manual lookups. When a milestone-level index Gist exists, use it to discover all investigation Gists for the milestone — providing full cross-issue context from a single URL. <!-- Updated: forge#341 -->
|
|
@@ -75,13 +94,106 @@ fi
|
|
|
75
94
|
|
|
76
95
|
### Step 1: Enumerate and filter applicable files
|
|
77
96
|
|
|
78
|
-
|
|
97
|
+
**Index-first path** (preferred when `index.yaml` exists): Read the lightweight index, extract issue labels to filter by domain, load only matching docs. Falls back to O(N) enumerate when index is absent.
|
|
79
98
|
|
|
80
99
|
```bash
|
|
81
100
|
DEVDOCS_APPLICABLE="" # list of paths that apply
|
|
101
|
+
INDEX_PATH="${DEVDOCS_PATH}/index.yaml"
|
|
102
|
+
|
|
103
|
+
# --- Read devdocs.index_path override from forge.yaml (optional) ---
|
|
104
|
+
if [ -f "$FORGE_YAML_PATH" ]; then
|
|
105
|
+
INDEX_PATH_OVERRIDE=$(grep -A5 '^devdocs:' "$FORGE_YAML_PATH" \
|
|
106
|
+
| grep '^\s*index_path:' \
|
|
107
|
+
| head -1 \
|
|
108
|
+
| sed 's/.*index_path:\s*//' \
|
|
109
|
+
| tr -d '"'"'"' \
|
|
110
|
+
| tr -d '[:space:]')
|
|
111
|
+
if [ -n "$INDEX_PATH_OVERRIDE" ]; then
|
|
112
|
+
# Resolve relative to REPO_PATH if not absolute
|
|
113
|
+
case "$INDEX_PATH_OVERRIDE" in
|
|
114
|
+
/*) INDEX_PATH="$INDEX_PATH_OVERRIDE" ;;
|
|
115
|
+
*) INDEX_PATH="{REPO_PATH}/${INDEX_PATH_OVERRIDE}" ;;
|
|
116
|
+
esac
|
|
117
|
+
fi
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
if [ -n "$DEVDOCS_PATH" ] && [ -f "$INDEX_PATH" ]; then
|
|
121
|
+
# --- Index-first loading path ---
|
|
122
|
+
echo "Devdocs index found at ${INDEX_PATH} — using selective domain loading"
|
|
123
|
+
|
|
124
|
+
# Read issue labels to determine domain(s)
|
|
125
|
+
ISSUE_LABELS=$(gh issue view {NUMBER} -R {GH_REPO} --json labels \
|
|
126
|
+
--jq '[.labels[].name] | join(" ")' 2>/dev/null || echo "")
|
|
127
|
+
echo "Issue labels: ${ISSUE_LABELS:-none}"
|
|
128
|
+
|
|
129
|
+
# Read index content
|
|
130
|
+
INDEX_CONTENT=$(cat "$INDEX_PATH" 2>/dev/null || echo "")
|
|
131
|
+
|
|
132
|
+
# Extract always_load paths (lines with "path:" under "always_load:" block)
|
|
133
|
+
ALWAYS_LOAD_PATHS=$(echo "$INDEX_CONTENT" \
|
|
134
|
+
| awk '/^always_load:/,/^[a-z]/' \
|
|
135
|
+
| grep '^\s*-\s*path:' \
|
|
136
|
+
| sed 's/.*path:\s*//' \
|
|
137
|
+
| tr -d '"'"'"' \
|
|
138
|
+
| tr -d '[:space:]')
|
|
139
|
+
|
|
140
|
+
# Extract domain blocks matching any issue label keyword
|
|
141
|
+
DOMAIN_PATHS=""
|
|
142
|
+
for label in $ISSUE_LABELS; do
|
|
143
|
+
# Strip workflow:, priority:, review-finding prefixes — use bare keyword
|
|
144
|
+
KEYWORD=$(echo "$label" | sed 's/^workflow://; s/^priority://; s/^review-finding$//')
|
|
145
|
+
[ -z "$KEYWORD" ] && continue
|
|
146
|
+
# Sanitize KEYWORD before AWK injection — strip chars that would break awk regex delimiters
|
|
147
|
+
SAFE_KEYWORD=$(printf '%s' "$KEYWORD" | tr -cd 'a-zA-Z0-9_-')
|
|
148
|
+
[ -z "$SAFE_KEYWORD" ] && continue
|
|
149
|
+
|
|
150
|
+
# Find the domain block matching this keyword, extract its doc paths
|
|
151
|
+
BLOCK_PATHS=$(echo "$INDEX_CONTENT" \
|
|
152
|
+
| awk "/^ ${SAFE_KEYWORD}:/{found=1; next} found && /^ [a-z]/{found=0} found && /^\s*-\s*path:/{print}" \
|
|
153
|
+
| sed 's/.*path:\s*//' \
|
|
154
|
+
| tr -d '"'"'"' \
|
|
155
|
+
| tr -d '[:space:]')
|
|
156
|
+
|
|
157
|
+
if [ -n "$BLOCK_PATHS" ]; then
|
|
158
|
+
echo "Domain '${SAFE_KEYWORD}' matched — adding docs: $(echo "$BLOCK_PATHS" | tr '\n' ' ')"
|
|
159
|
+
DOMAIN_PATHS="${DOMAIN_PATHS}${BLOCK_PATHS}"$'\n'
|
|
160
|
+
fi
|
|
161
|
+
done
|
|
162
|
+
|
|
163
|
+
# Combine always_load + domain paths (deduplicate)
|
|
164
|
+
ALL_PATHS=$(printf "%s\n%s" "$ALWAYS_LOAD_PATHS" "$DOMAIN_PATHS" \
|
|
165
|
+
| grep -v '^$' | sort -u)
|
|
166
|
+
|
|
167
|
+
# When no domain labels matched, load only authority:required files from always_load
|
|
168
|
+
if [ -z "$DOMAIN_PATHS" ]; then
|
|
169
|
+
echo "No domain labels matched index — loading always_load entries only"
|
|
170
|
+
ALL_PATHS="$ALWAYS_LOAD_PATHS"
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# Build DEVDOCS_APPLICABLE list with sort keys
|
|
174
|
+
while IFS= read -r rel_path; do
|
|
175
|
+
[ -z "$rel_path" ] && continue
|
|
176
|
+
ABS_PATH="${DEVDOCS_PATH}/${rel_path}"
|
|
177
|
+
[ -f "$ABS_PATH" ] || { echo "WARN: index references missing file: ${rel_path} — skipping"; continue; }
|
|
178
|
+
|
|
179
|
+
# Extract authority from frontmatter for sort key
|
|
180
|
+
FRONTMATTER=$(awk '/^---/{c++; if(c==1){next} if(c==2){exit}} c==1{print}' "$ABS_PATH" 2>/dev/null)
|
|
181
|
+
AUTHORITY=$(echo "$FRONTMATTER" | grep 'authority:' | head -1 | sed 's/.*authority:\s*//' | tr -d ' ')
|
|
182
|
+
case "$AUTHORITY" in
|
|
183
|
+
required) SORT_KEY="1" ;;
|
|
184
|
+
recommended) SORT_KEY="2" ;;
|
|
185
|
+
reference) SORT_KEY="3" ;;
|
|
186
|
+
*) SORT_KEY="4" ;;
|
|
187
|
+
esac
|
|
188
|
+
DEVDOCS_APPLICABLE="${DEVDOCS_APPLICABLE}${SORT_KEY}|${ABS_PATH}"$'\n'
|
|
189
|
+
done <<< "$ALL_PATHS"
|
|
190
|
+
|
|
191
|
+
DEVDOCS_APPLICABLE=$(printf "%s" "$DEVDOCS_APPLICABLE" | sort | cut -d'|' -f2-)
|
|
192
|
+
|
|
193
|
+
elif [ -n "$DEVDOCS_PATH" ]; then
|
|
194
|
+
# --- Fallback: O(N) enumerate (backward compatible — no index.yaml present) ---
|
|
195
|
+
echo "No index.yaml found at ${INDEX_PATH} — falling back to full enumerate (backward compatible)"
|
|
82
196
|
|
|
83
|
-
if [ -n "$DEVDOCS_PATH" ]; then
|
|
84
|
-
# Find all markdown files recursively
|
|
85
197
|
while IFS= read -r -d '' mdfile; do
|
|
86
198
|
# Extract frontmatter block (between first two --- markers)
|
|
87
199
|
FRONTMATTER=$(awk '/^---/{c++; if(c==1){next} if(c==2){exit}} c==1{print}' "$mdfile" 2>/dev/null)
|
|
@@ -96,16 +208,16 @@ if [ -n "$DEVDOCS_PATH" ]; then
|
|
|
96
208
|
reference) SORT_KEY="3" ;;
|
|
97
209
|
*) SORT_KEY="4" ;;
|
|
98
210
|
esac
|
|
99
|
-
DEVDOCS_APPLICABLE="${DEVDOCS_APPLICABLE}${SORT_KEY}|${mdfile}\n
|
|
211
|
+
DEVDOCS_APPLICABLE="${DEVDOCS_APPLICABLE}${SORT_KEY}|${mdfile}"$'\n'
|
|
100
212
|
fi
|
|
101
213
|
done < <(find "$DEVDOCS_PATH" -name "*.md" -print0 2>/dev/null)
|
|
102
214
|
|
|
103
215
|
# Sort by authority key and extract paths
|
|
104
|
-
DEVDOCS_APPLICABLE=$(printf "$DEVDOCS_APPLICABLE" | sort | cut -d'|' -f2-)
|
|
216
|
+
DEVDOCS_APPLICABLE=$(printf "%s" "$DEVDOCS_APPLICABLE" | sort | cut -d'|' -f2-)
|
|
105
217
|
fi
|
|
106
218
|
|
|
107
219
|
if [ -z "$DEVDOCS_APPLICABLE" ]; then
|
|
108
|
-
echo "No devdocs files
|
|
220
|
+
echo "No applicable devdocs files found — skipping Phase C-1 content read"
|
|
109
221
|
fi
|
|
110
222
|
```
|
|
111
223
|
|
|
@@ -165,26 +277,26 @@ INDEX_GIST_URLS=""
|
|
|
165
277
|
|
|
166
278
|
# Check issue body for milestone index annotation
|
|
167
279
|
MILESTONE_INDEX_URL=$(echo "$ISSUE_BODY" \
|
|
168
|
-
|
|
|
280
|
+
| sed -n 's/.*<!-- FORGE:MILESTONE_INDEX: \(https:\/\/[^ ]*\) -->.*/\1/p' \
|
|
169
281
|
| head -1)
|
|
170
282
|
|
|
171
283
|
# If not in issue body, check milestone description
|
|
172
284
|
if [ -z "$MILESTONE_INDEX_URL" ] && [ -n "$MILESTONE_NUM" ]; then
|
|
173
285
|
MILESTONE_DESC=$(gh api repos/{GH_REPO}/milestones/${MILESTONE_NUM} --jq '.description // ""' 2>/dev/null)
|
|
174
286
|
MILESTONE_INDEX_URL=$(echo "$MILESTONE_DESC" \
|
|
175
|
-
|
|
|
287
|
+
| sed -n 's/.*<!-- FORGE:MILESTONE_INDEX: \(https:\/\/[^ ]*\) -->.*/\1/p' \
|
|
176
288
|
| head -1)
|
|
177
289
|
fi
|
|
178
290
|
|
|
179
291
|
# If found, fetch the index and extract individual Gist URLs from table rows
|
|
180
292
|
if [ -n "$MILESTONE_INDEX_URL" ]; then
|
|
181
|
-
INDEX_GIST_ID=$(echo "$MILESTONE_INDEX_URL" | grep -
|
|
293
|
+
INDEX_GIST_ID=$(echo "$MILESTONE_INDEX_URL" | grep -oE '[a-f0-9]{20,}' | tail -1)
|
|
182
294
|
if [ -n "$INDEX_GIST_ID" ]; then
|
|
183
295
|
INDEX_CONTENT=$(timeout 15 gh gist view "$INDEX_GIST_ID" --raw 2>/dev/null)
|
|
184
296
|
if [ -n "$INDEX_CONTENT" ]; then
|
|
185
297
|
# Extract Gist URLs from table rows (format: | ... | https://gist.github.com/... | ... |)
|
|
186
298
|
INDEX_GIST_URLS=$(echo "$INDEX_CONTENT" \
|
|
187
|
-
| grep -
|
|
299
|
+
| grep -oE 'https://gist\.github\.com/[a-f0-9/]+' \
|
|
188
300
|
| head -10)
|
|
189
301
|
echo "Milestone index fetched: found $(echo "$INDEX_GIST_URLS" | wc -l) investigation Gist(s)"
|
|
190
302
|
else
|
|
@@ -198,7 +310,7 @@ fi
|
|
|
198
310
|
|
|
199
311
|
```bash
|
|
200
312
|
GIST_URLS=$(echo "$ISSUE_BODY" \
|
|
201
|
-
|
|
|
313
|
+
| sed -n 's/.*<!-- FORGE:PRIOR_GIST: \(https:\/\/[^ ]*\) -->.*/\1/p' \
|
|
202
314
|
| head -5)
|
|
203
315
|
|
|
204
316
|
# Merge with any URLs discovered from milestone index (deduplicate)
|
|
@@ -223,7 +335,7 @@ GIST_SUMMARIES=""
|
|
|
223
335
|
|
|
224
336
|
for url in $GIST_URLS; do
|
|
225
337
|
# Extract Gist ID from URL (last path segment, strip any trailing slash)
|
|
226
|
-
GIST_ID=$(echo "$url" | grep -
|
|
338
|
+
GIST_ID=$(echo "$url" | grep -oE '[a-f0-9]{20,}' | tail -1)
|
|
227
339
|
|
|
228
340
|
if [ -z "$GIST_ID" ]; then
|
|
229
341
|
echo "WARNING: Could not extract Gist ID from URL: $url — skipping"
|
|
@@ -241,10 +353,10 @@ for url in $GIST_URLS; do
|
|
|
241
353
|
fi
|
|
242
354
|
|
|
243
355
|
# Extract key sections for summary (~2K chars target per Gist)
|
|
244
|
-
VERDICT=$(echo "$GIST_CONTENT" |
|
|
245
|
-
TASK_TYPE=$(echo "$GIST_CONTENT" |
|
|
246
|
-
SEVERITY=$(echo "$GIST_CONTENT" |
|
|
247
|
-
SOURCE_ISSUE=$(echo "$GIST_CONTENT" |
|
|
356
|
+
VERDICT=$(echo "$GIST_CONTENT" | sed -n 's/.*verdict: \([A-Za-z_]*\).*/\1/p' | head -1)
|
|
357
|
+
TASK_TYPE=$(echo "$GIST_CONTENT" | sed -n 's/.*task_type: \(.*\)/\1/p' | head -1)
|
|
358
|
+
SEVERITY=$(echo "$GIST_CONTENT" | sed -n 's/.*severity: \([A-Za-z_]*\).*/\1/p' | head -1)
|
|
359
|
+
SOURCE_ISSUE=$(echo "$GIST_CONTENT" | sed -n 's/.*issue: \([0-9]*\).*/\1/p' | head -1)
|
|
248
360
|
|
|
249
361
|
# Extract structured sections: Root Cause, Recommendation, Affected Files
|
|
250
362
|
ROOT_CAUSE=$(echo "$GIST_CONTENT" \
|
|
@@ -318,7 +430,7 @@ Mine git log for commit messages referencing issues, then fetch those issues:
|
|
|
318
430
|
```bash
|
|
319
431
|
# Step 1: find issue numbers from git history on affected files
|
|
320
432
|
git log --oneline -30 -- {AFFECTED_FILES} \
|
|
321
|
-
| grep -
|
|
433
|
+
| grep -oE '#[0-9]+' \
|
|
322
434
|
| sort -u \
|
|
323
435
|
| head -8
|
|
324
436
|
|
|
@@ -448,6 +560,7 @@ gh issue comment {NUMBER} -R {GH_REPO} --body "<!-- FORGE:CONTEXT -->
|
|
|
448
560
|
## Skip Conditions
|
|
449
561
|
|
|
450
562
|
Skip this entire step (post nothing, return empty briefing) if:
|
|
563
|
+
- **COMPLEXITY_BAND: TRIVIAL** — checked via FORGE:FAST_PATH comment at entry (see guard above) <!-- Primary skip path: forge#679 -->
|
|
451
564
|
- Issue is a 1-file config or docs edit with no code logic
|
|
452
565
|
- The affected files have zero git history (new files being created)
|
|
453
566
|
- `{AFFECTED_FILES}` is empty (investigation produced no file list)
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
description: Implementation agent — writes code, makes commits, posts builder comment
|
|
3
3
|
argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--worktree PATH] [--branch BRANCH]
|
|
4
4
|
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
5
7
|
|
|
6
8
|
# work-on/build/implement — Implementation Subcommand
|
|
7
9
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
description: Validation agent — quality gate loop, format/verify, proxy check, deploy check
|
|
3
3
|
argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--worktree PATH] [--files FILE1 FILE2...]
|
|
4
4
|
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
5
7
|
|
|
6
8
|
# work-on/build/validate — Validation Subcommand
|
|
7
9
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
description: Build subcommand — create worktree, post contract, sequence context/architect/implement/validate
|
|
3
3
|
argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--base PR_BASE]
|
|
4
4
|
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
5
7
|
|
|
6
8
|
# work-on/build — Build Phase Orchestrator
|
|
7
9
|
|
|
@@ -13,7 +15,7 @@ argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--base PR_BA
|
|
|
13
15
|
**Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`.
|
|
14
16
|
**NEVER use plan mode (EnterPlanMode).**
|
|
15
17
|
|
|
16
|
-
**CRITICAL: You MUST execute ALL phases B0–B6 in order. After each Skill() call returns, you MUST continue to the next phase.
|
|
18
|
+
**CRITICAL: You MUST execute ALL phases B0–B6 in order. After each Skill() call returns, you MUST continue to the next phase. Phases B3 (context) and B4 (architect) are skipped ONLY when COMPLEXITY_BAND: TRIVIAL (read from FORGE:FAST_PATH comment in Phase B0). For STANDARD and COMPLEX tasks they are NOT optional — skipping them degrades build quality.**
|
|
17
19
|
|
|
18
20
|
---
|
|
19
21
|
|
|
@@ -53,6 +55,16 @@ Extract from investigation report:
|
|
|
53
55
|
- Recommendation
|
|
54
56
|
- Task type (Bug Fix / Feature / Refactor / Maintenance / UI/UX / Full-Stack)
|
|
55
57
|
|
|
58
|
+
**Read COMPLEXITY_BAND** (from FORGE:FAST_PATH comment posted by Phase 3B):
|
|
59
|
+
```bash
|
|
60
|
+
COMPLEXITY_BAND=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
61
|
+
--jq '.[] | select(.body | contains("FORGE:FAST_PATH")) | .body' 2>/dev/null \
|
|
62
|
+
| sed -n 's/.*\*\*COMPLEXITY_BAND\*\*: \([A-Z_]*\).*/\1/p' | head -1)
|
|
63
|
+
# Default to STANDARD if not found (conservative — runs full pipeline)
|
|
64
|
+
COMPLEXITY_BAND="${COMPLEXITY_BAND:-STANDARD}"
|
|
65
|
+
echo "COMPLEXITY_BAND: $COMPLEXITY_BAND"
|
|
66
|
+
```
|
|
67
|
+
|
|
56
68
|
---
|
|
57
69
|
|
|
58
70
|
## Phase B1: Create Worktree & Branch
|
|
@@ -146,7 +158,7 @@ After posting the Builder Contract, extract the primary function/class names fro
|
|
|
146
158
|
FUNCTION_NAMES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
147
159
|
--jq '.[] | select(.body | contains("FORGE:CONTRACT")) | .body' \
|
|
148
160
|
| awk '/^### Deliverables/{p=1; next} /^### /{p=0} p' \
|
|
149
|
-
| grep -
|
|
161
|
+
| grep -oE '`[A-Za-z_][A-Za-z0-9_]*`' \
|
|
150
162
|
| tr -d '`' \
|
|
151
163
|
| sort -u \
|
|
152
164
|
| tr '\n' ' ' \
|
|
@@ -161,9 +173,11 @@ If `FUNCTION_NAMES` is non-empty, it will be passed via `--functions` to the con
|
|
|
161
173
|
|
|
162
174
|
---
|
|
163
175
|
|
|
164
|
-
## Phase B3: Context Gathering (MANDATORY
|
|
176
|
+
## Phase B3: Context Gathering (MANDATORY for STANDARD/COMPLEX — skip for TRIVIAL)
|
|
177
|
+
|
|
178
|
+
**Skip if COMPLEXITY_BAND: TRIVIAL** (read from FORGE:FAST_PATH in Phase B0) — do not invoke the context subcommand. Proceed directly to Phase B4.
|
|
165
179
|
|
|
166
|
-
**
|
|
180
|
+
**For STANDARD and COMPLEX tasks**: Always invoke. Do NOT skip without a TRIVIAL COMPLEXITY_BAND.
|
|
167
181
|
|
|
168
182
|
Invoke the context subcommand to surface historical review findings and bug patterns:
|
|
169
183
|
|
|
@@ -184,9 +198,11 @@ Skill("work-on:build:context", args="{NUMBER} --repo {GH_REPO} --gh-flag {GH_FLA
|
|
|
184
198
|
|
|
185
199
|
---
|
|
186
200
|
|
|
187
|
-
## Phase B4: Architecture Planning (MANDATORY
|
|
201
|
+
## Phase B4: Architecture Planning (MANDATORY for STANDARD/COMPLEX — skip for TRIVIAL)
|
|
188
202
|
|
|
189
|
-
**
|
|
203
|
+
**Skip if COMPLEXITY_BAND: TRIVIAL** (read from FORGE:FAST_PATH in Phase B0) — do not invoke the architect subcommand. Proceed directly to Phase B5.
|
|
204
|
+
|
|
205
|
+
**For STANDARD and COMPLEX tasks**: Always invoke. Even a 1-file STANDARD fix benefits from cross-path consistency checks. Do NOT skip without a TRIVIAL COMPLEXITY_BAND.
|
|
190
206
|
|
|
191
207
|
Invoke the architect subcommand to trace all affected code paths and produce an ordered implementation plan:
|
|
192
208
|
|
|
@@ -229,9 +245,18 @@ Skill("work-on:build:validate", args="{NUMBER} --repo {GH_REPO} --gh-flag {GH_FL
|
|
|
229
245
|
Where `{CHANGED_FILES}` is the space-separated list of files changed by the implement subcommand (read from `IMPLEMENT_RESULT` or from the `<!-- FORGE:BUILDER -->` comment).
|
|
230
246
|
|
|
231
247
|
**After subcommand returns**:
|
|
232
|
-
- `VALIDATE_RESULT: gate_passed: true` →
|
|
248
|
+
- `VALIDATE_RESULT: gate_passed: true` → write checkpoint, then return `BUILD_RESULT: status: COMPLETE`
|
|
233
249
|
- `VALIDATE_RESULT: gate_passed: false` → subcommand has already posted comment and added `needs-human` label; return `BUILD_RESULT: status: BLOCKED`
|
|
234
250
|
|
|
251
|
+
**When gate_passed is true — write machine-readable phase checkpoint before returning (MANDATORY)**:
|
|
252
|
+
```bash
|
|
253
|
+
CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
254
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
255
|
+
\`\`\`json
|
|
256
|
+
{\"phase\": \"BUILD\", \"status\": \"COMPLETE\", \"next_phase\": \"REVIEW\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
|
|
257
|
+
\`\`\`"
|
|
258
|
+
```
|
|
259
|
+
|
|
235
260
|
---
|
|
236
261
|
|
|
237
262
|
## Output
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
description: Close subcommand — update project board, final issue body, parent tracker, summary report, trajectory log
|
|
3
3
|
argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--pr PR_NUMBER] [--base PR_BASE] [--branch BRANCH] [--worktree WORKTREE_PATH]
|
|
4
4
|
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
5
7
|
|
|
6
8
|
# work-on/close — Close & Trajectory Subcommand
|
|
7
9
|
|
|
@@ -314,6 +316,88 @@ Where:
|
|
|
314
316
|
|
|
315
317
|
---
|
|
316
318
|
|
|
319
|
+
## Phase C5.5: Graph Decision Record (MANDATORY when PR exists)
|
|
320
|
+
|
|
321
|
+
**Skip if**: `{PR_NUMBER}` is empty OR `<!-- FORGE:DECISION_RECORD -->` already posted on the PR.
|
|
322
|
+
|
|
323
|
+
Post a consolidated provenance artifact to the PR that proves the merge was backed by citable evidence. Mirrors the Phase 7C step in `work-on.md` — must stay in sync.
|
|
324
|
+
|
|
325
|
+
**Idempotency check**:
|
|
326
|
+
```bash
|
|
327
|
+
GDR_EXISTS=$(gh api repos/{GH_REPO}/issues/{PR_NUMBER}/comments \
|
|
328
|
+
--jq '[.[] | select(.body | contains("FORGE:DECISION_RECORD"))] | length > 0' 2>/dev/null || echo "false")
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Extract context edge counts** from FORGE:CONTEXT comment:
|
|
332
|
+
```bash
|
|
333
|
+
CONTEXT_COMMENT=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
334
|
+
--jq '.[] | select(.body | contains("FORGE:CONTEXT")) | .body' 2>/dev/null | head -1)
|
|
335
|
+
REVIEW_FINDING_COUNT=$(echo "$CONTEXT_COMMENT" | grep -oP '#\d+' | wc -l | tr -d ' ')
|
|
336
|
+
REVIEW_FINDING_COUNT=${REVIEW_FINDING_COUNT:-0}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Extract review verdict** from PR review summary:
|
|
340
|
+
```bash
|
|
341
|
+
REVIEW_SUMMARY=$(gh api repos/{GH_REPO}/issues/{PR_NUMBER}/comments \
|
|
342
|
+
--jq '[.[] | select(.body | contains("FORGE:REVIEWER") or (.body | test("APPROVED:|CHANGES REQUESTED:"; "i")))] | last | .body // ""' 2>/dev/null || echo '')
|
|
343
|
+
REVIEW_VERDICT=$(echo "$REVIEW_SUMMARY" | grep -oP '(?<=Verdict: )(APPROVED|CHANGES REQUESTED)' | head -1 || echo "APPROVED")
|
|
344
|
+
FINDINGS_COUNT=$(echo "$REVIEW_SUMMARY" | grep -oP '\d+(?= findings)' | head -1 || echo "0")
|
|
345
|
+
AGENTS_RUN=$(echo "$REVIEW_SUMMARY" | grep -oP '\d+(?= agents)' | head -1 || echo "0")
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Post GDR to PR** (not to issue — PR comment is the permanent artifact):
|
|
349
|
+
```bash
|
|
350
|
+
if [ "$GDR_EXISTS" != "true" ] && [ -n "{PR_NUMBER}" ]; then
|
|
351
|
+
GDR_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
352
|
+
HEAD_SHA=$(gh pr view {PR_NUMBER} {GH_FLAG} --json headRefOid --jq '.headRefOid' 2>/dev/null || echo "")
|
|
353
|
+
MERGE_COMMIT=$(gh pr view {PR_NUMBER} {GH_FLAG} --json mergeCommit --jq '.mergeCommit.oid // ""' 2>/dev/null || echo "")
|
|
354
|
+
|
|
355
|
+
gh pr comment {PR_NUMBER} {GH_FLAG} --body "<!-- FORGE:DECISION_RECORD -->
|
|
356
|
+
## Graph Decision Record — Issue #${NUMBER} / PR #${PR_NUMBER}
|
|
357
|
+
|
|
358
|
+
\`\`\`json
|
|
359
|
+
{
|
|
360
|
+
\"schema_version\": \"1\",
|
|
361
|
+
\"issue\": ${NUMBER},
|
|
362
|
+
\"pr\": ${PR_NUMBER},
|
|
363
|
+
\"repo\": \"{GH_REPO}\",
|
|
364
|
+
\"lane\": \"feature\",
|
|
365
|
+
\"pr_base\": \"{PR_BASE}\",
|
|
366
|
+
\"branch\": \"{BRANCH}\",
|
|
367
|
+
\"head_sha\": \"${HEAD_SHA}\",
|
|
368
|
+
\"merge_commit\": \"${MERGE_COMMIT}\",
|
|
369
|
+
\"investigation\": {
|
|
370
|
+
\"verdict\": \"{VERDICT}\",
|
|
371
|
+
\"confidence\": \"{CONFIDENCE}\",
|
|
372
|
+
\"task_type\": \"{TASK_TYPE}\"
|
|
373
|
+
},
|
|
374
|
+
\"context\": {
|
|
375
|
+
\"historical_edges_referenced\": ${REVIEW_FINDING_COUNT},
|
|
376
|
+
\"forge_annotations_read\": [\"FORGE:INVESTIGATOR\", \"FORGE:CONTRACT\", \"FORGE:CONTEXT\", \"FORGE:ARCHITECT\", \"FORGE:BUILDER\"]
|
|
377
|
+
},
|
|
378
|
+
\"build\": {
|
|
379
|
+
\"files_changed\": {FILES_CHANGED},
|
|
380
|
+
\"quality_gate\": \"{pass|fail}\",
|
|
381
|
+
\"quality_gate_iterations\": {GATE_ITERATIONS}
|
|
382
|
+
},
|
|
383
|
+
\"review\": {
|
|
384
|
+
\"verdict\": \"${REVIEW_VERDICT:-APPROVED}\",
|
|
385
|
+
\"findings_created\": ${FINDINGS_COUNT},
|
|
386
|
+
\"agents_run\": ${AGENTS_RUN}
|
|
387
|
+
},
|
|
388
|
+
\"merge\": {
|
|
389
|
+
\"merged_at\": \"${GDR_TIMESTAMP}\",
|
|
390
|
+
\"justification\": \"Investigation confirmed ({VERDICT}/{CONFIDENCE}), quality gate passed, review ${REVIEW_VERDICT:-approved}\"
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
\`\`\`"
|
|
394
|
+
fi
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
<!-- Added: forge#776 -->
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
317
401
|
## Phase C6: Worktree & Branch Cleanup
|
|
318
402
|
|
|
319
403
|
Remove the git worktree and delete the local feature branch after the PR has merged. This prevents worktree accumulation across pipeline runs.
|
|
@@ -360,6 +444,7 @@ CLOSE_RESULT:
|
|
|
360
444
|
status: COMPLETE | ALREADY_DONE | PHASE_COMPLETE
|
|
361
445
|
issue_state: closed | open
|
|
362
446
|
trajectory_url: {url of FORGE:TRAJECTORY comment}
|
|
447
|
+
decision_record_url: {url of FORGE:DECISION_RECORD comment on PR, or "" if skipped}
|
|
363
448
|
parent_updated: {true|false}
|
|
364
449
|
parent_closed: {true|false}
|
|
365
450
|
```
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
description: Decompose subcommand — break a complex issue into ordered sub-issues, post FORGE:DECOMPOSED, stop
|
|
3
3
|
argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG]
|
|
4
4
|
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
5
7
|
|
|
6
8
|
# work-on/decompose — Decomposition Subcommand
|
|
7
9
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
description: Investigate a GitHub issue — validate it's real, determine root cause, post findings
|
|
3
3
|
argument-hint: [issue number] [--repo {owner}/{repo}] [--gh-flag "-R {owner}/{repo}"]
|
|
4
4
|
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
5
7
|
|
|
6
8
|
# /work-on:investigate — Issue Investigation Subcommand
|
|
7
9
|
|
|
@@ -352,8 +354,13 @@ ${INDEX_ENTRY}"
|
|
|
352
354
|
fi
|
|
353
355
|
|
|
354
356
|
# Update the Gist
|
|
355
|
-
|
|
356
|
-
|
|
357
|
+
# gh gist edit does not support stdin via '-'; use a temp file instead
|
|
358
|
+
TMPFILE=$(mktemp --suffix=.md)
|
|
359
|
+
echo "$UPDATED_INDEX" > "$TMPFILE"
|
|
360
|
+
gh gist edit "$INDEX_GIST_ID" -f "$INDEX_FILENAME" "$TMPFILE" 2>/dev/null
|
|
361
|
+
EDIT_EXIT=$?
|
|
362
|
+
rm -f "$TMPFILE"
|
|
363
|
+
if [ $EDIT_EXIT -eq 0 ]; then
|
|
357
364
|
INDEX_URL="$EXISTING_INDEX_URL"
|
|
358
365
|
echo "Milestone index Gist updated: ${INDEX_URL}"
|
|
359
366
|
else
|
|
@@ -400,6 +407,16 @@ fi
|
|
|
400
407
|
```bash
|
|
401
408
|
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:ready-to-build" --remove-label "workflow:investigating"
|
|
402
409
|
```
|
|
410
|
+
|
|
411
|
+
Write machine-readable phase checkpoint (MUST execute immediately after label update, before returning):
|
|
412
|
+
```bash
|
|
413
|
+
CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
414
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
415
|
+
\`\`\`json
|
|
416
|
+
{\"phase\": \"INVESTIGATION\", \"status\": \"COMPLETE\", \"next_phase\": \"BUILD\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
|
|
417
|
+
\`\`\`"
|
|
418
|
+
```
|
|
419
|
+
|
|
403
420
|
Return verdict to caller (work-on routing loop proceeds to build).
|
|
404
421
|
|
|
405
422
|
**CONFIRMED or PARTIAL with decompose: YES**:
|
|
@@ -407,6 +424,16 @@ Return verdict to caller (work-on routing loop proceeds to build).
|
|
|
407
424
|
gh issue edit {NUMBER} {GH_FLAG} --remove-label "workflow:investigating"
|
|
408
425
|
```
|
|
409
426
|
Do NOT add `workflow:ready-to-build` — the routing loop will invoke `work-on:decompose` based on the `decompose: YES` return value.
|
|
427
|
+
|
|
428
|
+
Write machine-readable phase checkpoint (MUST execute immediately after label update, before returning):
|
|
429
|
+
```bash
|
|
430
|
+
CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
431
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
432
|
+
\`\`\`json
|
|
433
|
+
{\"phase\": \"INVESTIGATION\", \"status\": \"COMPLETE\", \"next_phase\": \"DECOMPOSE\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
|
|
434
|
+
\`\`\`"
|
|
435
|
+
```
|
|
436
|
+
|
|
410
437
|
Return verdict to caller (work-on routing loop proceeds to decompose).
|
|
411
438
|
|
|
412
439
|
**INVALID**:
|
|
@@ -414,7 +441,7 @@ Return verdict to caller (work-on routing loop proceeds to decompose).
|
|
|
414
441
|
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:invalid" --remove-label "workflow:investigating"
|
|
415
442
|
gh issue close {NUMBER} {GH_FLAG} --comment "Closing as invalid: {reason from investigation}"
|
|
416
443
|
```
|
|
417
|
-
Return INVALID to caller (work-on stops).
|
|
444
|
+
Return INVALID to caller (work-on stops). No checkpoint written — INVALID is terminal.
|
|
418
445
|
|
|
419
446
|
---
|
|
420
447
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
description: Review subcommand — push branch, create PR, invoke /review-pr with --auto-merge
|
|
3
3
|
argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--worktree PATH] [--branch BRANCH] [--base PR_BASE]
|
|
4
4
|
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
5
7
|
|
|
6
8
|
# work-on/review — Review & PR Creation Subcommand
|
|
7
9
|
|
|
@@ -208,7 +210,17 @@ gh issue view {NUMBER} {GH_FLAG} --json state --jq '.state'
|
|
|
208
210
|
```
|
|
209
211
|
|
|
210
212
|
**Cases**:
|
|
211
|
-
- PR MERGED (issue OPEN or CLOSED) → `REVIEW_RESULT: status: COMPLETE` — do NOT close the issue or add labels here; the router will route to `work-on:close` which handles issue closure, label updates, project board, trajectory log, and worktree cleanup.
|
|
213
|
+
- PR MERGED (issue OPEN or CLOSED) → write checkpoint, then return `REVIEW_RESULT: status: COMPLETE` — do NOT close the issue or add labels here; the router will route to `work-on:close` which handles issue closure, label updates, project board, trajectory log, and worktree cleanup.
|
|
214
|
+
|
|
215
|
+
Write machine-readable phase checkpoint before returning (MANDATORY when PR is MERGED):
|
|
216
|
+
```bash
|
|
217
|
+
CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
218
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
219
|
+
\`\`\`json
|
|
220
|
+
{\"phase\": \"REVIEW\", \"status\": \"COMPLETE\", \"next_phase\": \"CLOSE\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
|
|
221
|
+
\`\`\`"
|
|
222
|
+
```
|
|
223
|
+
|
|
212
224
|
- PR NOT MERGED → attempt manual merge:
|
|
213
225
|
```bash
|
|
214
226
|
gh pr merge {PR_NUMBER} {GH_FLAG} --merge --auto
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
description: "[BENCHMARK] Monolithic work-on — single-prompt pipeline with no Skill sub-phase boundaries"
|
|
3
3
|
argument-hint: [issue number]
|
|
4
4
|
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
5
7
|
|
|
6
8
|
# /work-on-monolithic — Full Issue Pipeline (Single Prompt)
|
|
7
9
|
|