forgedock 1.1.7 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/engine/invariants.mjs +361 -0
- package/bin/hooks/pre-tool-use.mjs +182 -0
- package/bin/labels.json +10 -0
- package/bin/recall.mjs +540 -0
- package/commands/autopilot.md +957 -76
- package/commands/cleanup.md +52 -3
- package/commands/optimize.md +585 -0
- package/commands/orchestrate/phase-1-resolve.md +35 -25
- package/commands/orchestrate/phase-3-dependency.md +465 -34
- package/commands/orchestrate/phase-4-execution.md +447 -28
- package/commands/orchestrate/phase-6-report.md +198 -9
- package/commands/pipeline-health/phase-2-metrics.md +305 -1
- package/commands/pipeline-health/phase-4-proposals.md +127 -0
- package/commands/quality-gate.md +417 -0
- package/commands/review-pr.md +391 -13
- package/commands/spec-doctor.md +429 -0
- package/commands/validate.md +52 -0
- package/commands/work-on/build/architect.md +77 -0
- package/commands/work-on/build/context.md +357 -22
- package/commands/work-on/build/implement.md +2 -0
- package/commands/work-on/build/validate.md +28 -0
- package/commands/work-on/build.md +44 -0
- package/commands/work-on/close.md +621 -8
- package/commands/work-on/investigate.md +221 -0
- package/commands/work-on.md +15 -14
- package/docs/FORGE-PROTOCOL.md +101 -0
- package/docs/spec/forge-protocol-v1.md +97 -5
- package/docs/spec/label-state-machine.md +6 -1
- package/package.json +2 -2
- package/packages/protocol/src/cli.js +374 -84
- package/packages/protocol/src/types.js +75 -1
- package/scripts/build-knowledge-index.mjs +1761 -0
- package/scripts/calibration.mjs +996 -0
- package/scripts/danger-zones.mjs +782 -0
- package/scripts/doctor-pipeline-state.sh +88 -2
- package/scripts/select-fix-targets.sh +234 -0
- package/scripts/transition-label.sh +157 -6
|
@@ -90,6 +90,171 @@ Print these blocks to stdout before Phase 1A begins. During Phase 1B (step 3 —
|
|
|
90
90
|
|
|
91
91
|
---
|
|
92
92
|
|
|
93
|
+
## Phase 0.6: Forge Ledger Pre-Recall <!-- Added: forge#1740 -->
|
|
94
|
+
|
|
95
|
+
**Goal**: Query the Forge Ledger knowledge index for prior cards matching this issue's title terms, affected files, and symbols — **before reading any code**. Inject above-threshold results with explicit delta-verification framing so the investigator builds on prior knowledge rather than re-deriving it.
|
|
96
|
+
|
|
97
|
+
**This phase is non-blocking** — if the index is absent, empty, or returns no match above threshold, log a single line and proceed to Phase 1A. Never stall the pipeline for recall.
|
|
98
|
+
|
|
99
|
+
**Relationship to Phase 0.5**: Phase 0.5 retrieves structured priors from the Gist-based FORGE:MEMORY_INDEX (coarse keyword scoring over MEMORY_ENTRY lines). Phase 0.6 queries the Forge Ledger index (BM25 over structured knowledge cards, indexed by `build-knowledge-index.mjs`). Both are complementary and both run — neither replaces the other.
|
|
100
|
+
|
|
101
|
+
### Step 1: Probe for index
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
RECALL_PATH="${REPO_PATH:-$(git rev-parse --show-toplevel 2>/dev/null)}/bin/recall.mjs"
|
|
105
|
+
LEDGER_AVAILABLE=0
|
|
106
|
+
|
|
107
|
+
if [ -f "$RECALL_PATH" ]; then
|
|
108
|
+
# Quick probe: does the index exist and have at least one card?
|
|
109
|
+
PROBE=$(node "$RECALL_PATH" --doctor 2>/dev/null | grep "^Total cards:" | grep -v "^Total cards: 0" || true)
|
|
110
|
+
[ -n "$PROBE" ] && LEDGER_AVAILABLE=1
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
if [ "$LEDGER_AVAILABLE" -eq 0 ]; then
|
|
114
|
+
echo "[recall] Forge Ledger index absent or empty — skipping Phase 0.6, proceeding to Phase 1A"
|
|
115
|
+
# → Continue to Phase 1A
|
|
116
|
+
fi
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Step 2: Build combined query (title terms + symbols + affected files)
|
|
120
|
+
|
|
121
|
+
Extract query components from the issue body and title. The recall CLI accepts free text (BM25 ranked) plus repeated `--file` flags (exact-match boost):
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Extract title keywords: lowercase, remove stop words, keep noun/verb tokens (≥4 chars)
|
|
125
|
+
ISSUE_TITLE=$(gh issue view {NUMBER} {GH_FLAG} --json title --jq '.title' 2>/dev/null || echo '')
|
|
126
|
+
TITLE_TERMS=$(echo "$ISSUE_TITLE" | tr '[:upper:]' '[:lower:]' \
|
|
127
|
+
| sed 's/[^a-z0-9 ]/ /g' \
|
|
128
|
+
| tr ' ' '\n' \
|
|
129
|
+
| grep -E '^[a-z]{4,}$' \
|
|
130
|
+
| grep -vE '^(with|that|this|from|into|have|will|when|then|than|them|they|been|were|also|only|does|some|each|more|over|such|both|most|other|many|after|about|should|would|could|their|these|those|which|where|there|being|while|using|since|until|before|under|above)$' \
|
|
131
|
+
| sort -u | head -10 | tr '\n' ' ' | xargs)
|
|
132
|
+
|
|
133
|
+
# Extract affected files from the issue body's ## Affected Files section
|
|
134
|
+
ISSUE_BODY=$(gh issue view {NUMBER} {GH_FLAG} --json body --jq '.body' 2>/dev/null || echo '')
|
|
135
|
+
AFFECTED_FILES_RAW=$(echo "$ISSUE_BODY" \
|
|
136
|
+
| sed -n '/^## Affected Files/,/^## /p' \
|
|
137
|
+
| grep -oE '`[^`]+\.(md|mjs|js|ts|py|sh|yaml|yml|json)`' \
|
|
138
|
+
| tr -d '`' | sort -u | head -5)
|
|
139
|
+
|
|
140
|
+
# Extract symbols: backtick-quoted identifiers from the issue body (≥4 chars, camelCase or snake_case)
|
|
141
|
+
SYMBOLS_RAW=$(echo "$ISSUE_BODY" \
|
|
142
|
+
| grep -oE '`[A-Za-z_][A-Za-z0-9_]{3,}`' \
|
|
143
|
+
| tr -d '`' | sort -u | head -5)
|
|
144
|
+
|
|
145
|
+
# Build --file flags (one per affected file)
|
|
146
|
+
FILE_FLAGS=""
|
|
147
|
+
while IFS= read -r f; do
|
|
148
|
+
[ -n "$f" ] && FILE_FLAGS="$FILE_FLAGS --file $f"
|
|
149
|
+
done <<< "$AFFECTED_FILES_RAW"
|
|
150
|
+
|
|
151
|
+
# Build --symbol flag (first symbol only — recall supports one --symbol)
|
|
152
|
+
SYMBOL_FLAG=""
|
|
153
|
+
FIRST_SYMBOL=$(echo "$SYMBOLS_RAW" | head -1)
|
|
154
|
+
[ -n "$FIRST_SYMBOL" ] && SYMBOL_FLAG="--symbol $FIRST_SYMBOL"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Step 3: Run recall query and capture results
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
RECALL_RESULTS=""
|
|
161
|
+
RECALL_ISSUE_CITATIONS=""
|
|
162
|
+
|
|
163
|
+
if [ "$LEDGER_AVAILABLE" -eq 1 ] && [ -n "$TITLE_TERMS$FILE_FLAGS$SYMBOL_FLAG" ]; then
|
|
164
|
+
echo "[recall] Querying Forge Ledger: terms='${TITLE_TERMS}' files='${AFFECTED_FILES_RAW}' symbol='${FIRST_SYMBOL}'"
|
|
165
|
+
|
|
166
|
+
# Run combined query: title terms (free text) + file flags + symbol + min-score threshold
|
|
167
|
+
# --json for machine-readable output; --min-score 0.3 filters weak matches (noise gate)
|
|
168
|
+
RECALL_JSON=$(node "$RECALL_PATH" \
|
|
169
|
+
$TITLE_TERMS \
|
|
170
|
+
$FILE_FLAGS \
|
|
171
|
+
$SYMBOL_FLAG \
|
|
172
|
+
--k 5 \
|
|
173
|
+
--min-score 0.3 \
|
|
174
|
+
--json \
|
|
175
|
+
2>/dev/null || echo '[]')
|
|
176
|
+
|
|
177
|
+
# Count results
|
|
178
|
+
RECALL_COUNT=$(echo "$RECALL_JSON" | node -e "
|
|
179
|
+
let d = ''; process.stdin.on('data', c => d += c);
|
|
180
|
+
process.stdin.on('end', () => {
|
|
181
|
+
try { console.log(JSON.parse(d).length); } catch { console.log(0); }
|
|
182
|
+
});
|
|
183
|
+
" 2>/dev/null || echo "0")
|
|
184
|
+
|
|
185
|
+
if [ "${RECALL_COUNT:-0}" -gt 0 ]; then
|
|
186
|
+
echo "[recall] Found ${RECALL_COUNT} prior card(s) above threshold — injecting with delta-verification framing"
|
|
187
|
+
|
|
188
|
+
# Extract issue citations for the Prior Investigations field in the FORGE:INVESTIGATOR comment
|
|
189
|
+
RECALL_ISSUE_CITATIONS=$(echo "$RECALL_JSON" | node -e "
|
|
190
|
+
let d = ''; process.stdin.on('data', c => d += c);
|
|
191
|
+
process.stdin.on('end', () => {
|
|
192
|
+
try {
|
|
193
|
+
const cards = JSON.parse(d);
|
|
194
|
+
const seen = new Set();
|
|
195
|
+
const cites = cards
|
|
196
|
+
.filter(c => { if (seen.has(c.issue)) return false; seen.add(c.issue); return true; })
|
|
197
|
+
.map(c => '#' + c.issue)
|
|
198
|
+
.join(', ');
|
|
199
|
+
console.log(cites);
|
|
200
|
+
} catch { console.log(''); }
|
|
201
|
+
});
|
|
202
|
+
" 2>/dev/null || echo '')
|
|
203
|
+
|
|
204
|
+
# Format cards as human-readable text for injection (not raw JSON)
|
|
205
|
+
RECALL_FORMATTED=$(echo "$RECALL_JSON" | node -e "
|
|
206
|
+
let d = ''; process.stdin.on('data', c => d += c);
|
|
207
|
+
process.stdin.on('end', () => {
|
|
208
|
+
try {
|
|
209
|
+
const cards = JSON.parse(d);
|
|
210
|
+
const lines = [];
|
|
211
|
+
for (const c of cards) {
|
|
212
|
+
lines.push('── ' + c.kind.toUpperCase() + ' from #' + c.issue + ' (score: ' + c.score.toFixed(2) + ') ──');
|
|
213
|
+
if (c.rootCause) lines.push('Root Cause: ' + c.rootCause);
|
|
214
|
+
if (c.prevention) lines.push('Fix/Prevention: ' + c.prevention);
|
|
215
|
+
if (c.pattern) lines.push('Pattern: ' + c.pattern);
|
|
216
|
+
if (c.verdict) lines.push('Verdict: ' + c.verdict + ' (' + (c.confidence || '?') + ')');
|
|
217
|
+
if (c.paths && c.paths.length) lines.push('Files: ' + c.paths.slice(0,3).join(', '));
|
|
218
|
+
}
|
|
219
|
+
console.log(lines.join('\n'));
|
|
220
|
+
} catch (e) { console.log(''); }
|
|
221
|
+
});
|
|
222
|
+
" 2>/dev/null || echo '')
|
|
223
|
+
|
|
224
|
+
# Cap at ≤ 2K chars (same budget as context C0 Gist summaries)
|
|
225
|
+
RECALL_RESULTS=$(echo "$RECALL_FORMATTED" | head -c 2048)
|
|
226
|
+
else
|
|
227
|
+
echo "[recall] No prior cards above threshold (min-score 0.3) — investigating from scratch"
|
|
228
|
+
fi
|
|
229
|
+
fi
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Step 4: Inject recall results into investigation context
|
|
233
|
+
|
|
234
|
+
If `RECALL_RESULTS` is non-empty, print the following block to stdout **before Phase 1A begins**. The investigation steps in Phase 1B MUST treat this as starting context — verify deltas against current code rather than re-deriving the prior model.
|
|
235
|
+
|
|
236
|
+
**If `RECALL_RESULTS` is empty**: skip this step entirely and proceed to Phase 1A.
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
[FORGE:RECALL_PRIOR — Forge Ledger pre-recall results]
|
|
240
|
+
Prior cards matched this issue above threshold (min-score 0.3).
|
|
241
|
+
|
|
242
|
+
CRITICAL FRAMING: These cards describe prior findings on the same files/symbols.
|
|
243
|
+
→ Verify the deltas against current code — do NOT re-derive the prior model.
|
|
244
|
+
→ Stale cards (status: stale) are excluded by default — the query runs without
|
|
245
|
+
--include-stale. Do not treat any card as authoritative without confirming
|
|
246
|
+
the finding still exists in the current code.
|
|
247
|
+
→ Cite confirmed priors in the FORGE:INVESTIGATOR "Prior Investigations" field.
|
|
248
|
+
|
|
249
|
+
${RECALL_RESULTS}
|
|
250
|
+
|
|
251
|
+
[END FORGE:RECALL_PRIOR]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Store `RECALL_ISSUE_CITATIONS` in a variable — it is used in Phase 1C to populate the `**Prior Investigations (via recall)**` field in the FORGE:INVESTIGATOR comment.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
93
258
|
## Phase 1A: Load Issue & Check Resume State
|
|
94
259
|
|
|
95
260
|
```bash
|
|
@@ -192,6 +357,20 @@ bash {REPO_PATH}/scripts/code-index.sh query --domain {DOMAIN_LABEL} --repo-path
|
|
|
192
357
|
|
|
193
358
|
The comment MUST include `<!-- INVESTIGATION:COMPLETE -->` at the very end, AFTER all required sections are present. This marker signals the investigation finished successfully.
|
|
194
359
|
|
|
360
|
+
**CODEC PATH (forge#1727)**: Construct the annotation body via the protocol codec — do NOT hand-roll the `<!-- FORGE:INVESTIGATOR -->` header. Use `forge-annotation.sh write INVESTIGATOR --field ...` or `node packages/protocol/src/cli.js emit INVESTIGATOR --field ...` to produce the opening tag and completion sentinel. Fill in the Markdown body sections below. The full pattern:
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
# Build the annotation body via codec (escaping and sentinel handled by codec)
|
|
364
|
+
ANNOTATION_BODY=$(node packages/protocol/src/cli.js emit INVESTIGATOR \
|
|
365
|
+
--field "Verdict={VERDICT}" \
|
|
366
|
+
--field "Confidence={CONFIDENCE}" \
|
|
367
|
+
--field "Severity={SEVERITY}" \
|
|
368
|
+
--field "Task Type={TASK_TYPE}" \
|
|
369
|
+
--field "Decomposition Assessment={YES|NO} — {reason}")
|
|
370
|
+
# ANNOTATION_BODY now has opening tag + required fields + INVESTIGATION:COMPLETE sentinel.
|
|
371
|
+
# Append the Markdown body sections to it before posting.
|
|
372
|
+
```
|
|
373
|
+
|
|
195
374
|
Before posting, resolve the attribution annotation link from `forge.yaml`:
|
|
196
375
|
|
|
197
376
|
```bash
|
|
@@ -234,6 +413,7 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:INVESTIGATOR -->
|
|
|
234
413
|
**Last touched**: {hash — author — date — subject}
|
|
235
414
|
**Pickaxe hits (prior fixes / regressions)**: {commit(s) found via \`git log -S\`/\`-G\`, or 'None found' — max 5}
|
|
236
415
|
{This field is MANDATORY — populate from the git blame + pickaxe commands in step 4/5. If a file is newly created (no history), write 'New file — no history.'}
|
|
416
|
+
**Prior Investigations (via recall)**: {Comma-separated issue citations from \`RECALL_ISSUE_CITATIONS\` (e.g. '#1172, #1243 — building on, not repeating'), or 'None — no Forge Ledger match above threshold' if \`RECALL_RESULTS\` was empty. Use the \`RECALL_ISSUE_CITATIONS\` variable populated in Phase 0.6.}
|
|
237
417
|
|
|
238
418
|
### Recommendation
|
|
239
419
|
{what to build/fix, concrete and actionable}
|
|
@@ -541,6 +721,47 @@ fi
|
|
|
541
721
|
|
|
542
722
|
## Phase 1D: Update Labels & Return Verdict
|
|
543
723
|
|
|
724
|
+
### 1D.0: Finding Lifecycle Label Transition (MANDATORY — run before workflow label update)
|
|
725
|
+
|
|
726
|
+
**Purpose**: Wire the investigation verdict into the finding-validation lifecycle. If the issue under investigation is a review-finding (carries `needs-validation`), translate the verdict into `validated` or `false-positive` using `transition-label.sh --validate`. This is the primary mechanism that resolves the `needs-validation → validated/false-positive` lifecycle gap. <!-- Added: forge#1730 -->
|
|
727
|
+
|
|
728
|
+
**Run this block regardless of verdict (CONFIRMED, PARTIAL, INVALID) — the verdict-to-label mapping handles all cases:**
|
|
729
|
+
|
|
730
|
+
```bash
|
|
731
|
+
# Check if this issue is a finding awaiting validation
|
|
732
|
+
ISSUE_LABELS=$(gh issue view {NUMBER} {GH_FLAG} --json labels --jq '[.labels[].name] | join(",")' 2>/dev/null || echo "")
|
|
733
|
+
|
|
734
|
+
if echo "$ISSUE_LABELS" | grep -q "needs-validation"; then
|
|
735
|
+
echo "Issue #{NUMBER} has needs-validation — applying verdict label transition..."
|
|
736
|
+
RESOLUTION=$(resolve_script 'transition-label')
|
|
737
|
+
TIER="${RESOLUTION%%:*}"
|
|
738
|
+
SCRIPT_PATH="${RESOLUTION#*:}"
|
|
739
|
+
case "$TIER" in
|
|
740
|
+
adaptive|universal)
|
|
741
|
+
bash "$SCRIPT_PATH" --validate {VERDICT} {NUMBER} {GH_FLAG} || true
|
|
742
|
+
;;
|
|
743
|
+
prose)
|
|
744
|
+
# Prose fallback: apply label transition directly via gh
|
|
745
|
+
if [ "{VERDICT}" = "CONFIRMED" ]; then
|
|
746
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "validated" --remove-label "needs-validation" 2>/dev/null || true
|
|
747
|
+
echo "Applied: needs-validation → validated (verdict: CONFIRMED)"
|
|
748
|
+
else
|
|
749
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "false-positive" --remove-label "needs-validation" 2>/dev/null || true
|
|
750
|
+
echo "Applied: needs-validation → false-positive (verdict: {VERDICT})"
|
|
751
|
+
fi
|
|
752
|
+
;;
|
|
753
|
+
esac
|
|
754
|
+
else
|
|
755
|
+
echo "Issue #{NUMBER} does not have needs-validation — no finding lifecycle transition needed"
|
|
756
|
+
fi
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
**Verdict → label mapping**: `CONFIRMED` → `validated`; `PARTIAL`, `NOT-CONFIRMED`, `INVALID` → `false-positive`.
|
|
760
|
+
|
|
761
|
+
**Idempotency**: `transition-label.sh --validate` is a no-op if the issue already has `validated` or `false-positive`, or if it lacks `needs-validation`. Safe to call on any issue.
|
|
762
|
+
|
|
763
|
+
---
|
|
764
|
+
|
|
544
765
|
**CONFIRMED or PARTIAL with decompose: NO**:
|
|
545
766
|
```bash
|
|
546
767
|
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:ready-to-build" --remove-label "workflow:investigating"
|
package/commands/work-on.md
CHANGED
|
@@ -62,6 +62,7 @@ Orchestrator for the full issue lifecycle: investigate → decompose (if needed)
|
|
|
62
62
|
- `workflow:merged` label is set
|
|
63
63
|
- `workflow:invalid` label is set
|
|
64
64
|
- `needs-human` label is set
|
|
65
|
+
- `workflow:awaiting-merge` label is set (remediated + re-reviewed, awaiting a human merge decision — see #1810) <!-- Added: forge#1810 -->
|
|
65
66
|
- `workflow:decomposed` label is set (sub-issues spawned)
|
|
66
67
|
- Issue state is CLOSED with terminal label
|
|
67
68
|
|
|
@@ -216,7 +217,7 @@ fi
|
|
|
216
217
|
Worktree/branch-already-exists and stale-label conditions are surfaced later (Phase 3E worktree creation and the `## Error Handling` section) with their own recovery guidance in `docs/site/troubleshooting.md`.
|
|
217
218
|
|
|
218
219
|
### 0A: Parse input
|
|
219
|
-
Extract project prefix and issue number. If `next`/`pick`: list open issues sorted by priority, skip `needs-human` and `workflow:
|
|
220
|
+
Extract project prefix and issue number. If `next`/`pick`: list open issues sorted by priority, skip `needs-human`, `workflow:decomposed`, and `workflow:awaiting-merge`, pick highest priority.
|
|
220
221
|
|
|
221
222
|
**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.
|
|
222
223
|
|
|
@@ -234,7 +235,7 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
|
234
235
|
|
|
235
236
|
**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 -->
|
|
236
237
|
|
|
237
|
-
**Skip if**: Issue already has a terminal label (`workflow:merged`, `workflow:invalid`, `needs-human`) — no heartbeat needed on a completed issue.
|
|
238
|
+
**Skip if**: Issue already has a terminal label (`workflow:merged`, `workflow:invalid`, `needs-human`, `workflow:awaiting-merge`) — no heartbeat needed on a completed issue.
|
|
238
239
|
|
|
239
240
|
### 0B: Load issue + existing context
|
|
240
241
|
```bash
|
|
@@ -242,7 +243,7 @@ gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state,comments,
|
|
|
242
243
|
gh api repos/{GH_REPO}/issues/{NUMBER}/comments --jq '.[] | {id: .id, author: .user.login, body: .body}'
|
|
243
244
|
```
|
|
244
245
|
|
|
245
|
-
**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.
|
|
246
|
+
**Check**: state (closed → STOP), terminal labels (`workflow:merged`/`workflow:invalid`/`workflow:awaiting-merge` → STOP), existing agent comments (`FORGE:INVESTIGATOR`, `FORGE:DECOMPOSED`, `FORGE:CONTRACT`, `FORGE:BUILDER`, `FORGE:TRAJECTORY`, `FORGE:DECISION_RECORD`), parent tracker status, sub-issue status.
|
|
246
247
|
|
|
247
248
|
**Determine resume point**: No comments → Phase 1. Investigation exists + ready-to-build → Phase 3. Builder:COMPLETE + no PR → Phase 4. Builder without :COMPLETE (partial/interrupted build) + no PR → Phase 3 (partial-build cleanup). Builder + PR open → Phase 5. PR merged + issue open → Phase 6.
|
|
248
249
|
|
|
@@ -471,12 +472,12 @@ case "$TIER" in
|
|
|
471
472
|
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} investigating ;;
|
|
472
473
|
prose)
|
|
473
474
|
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:investigating" \
|
|
474
|
-
--remove-label "workflow:ready-to-build,workflow:building,workflow:in-review,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
475
|
+
--remove-label "workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
475
476
|
;;
|
|
476
477
|
esac
|
|
477
478
|
```
|
|
478
479
|
|
|
479
|
-
**Post Phase 1 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`):
|
|
480
|
+
**Post Phase 1 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`, `workflow:awaiting-merge`):
|
|
480
481
|
```bash
|
|
481
482
|
PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
482
483
|
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
@@ -748,7 +749,7 @@ case "$TIER" in
|
|
|
748
749
|
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} ready-to-build ;;
|
|
749
750
|
prose)
|
|
750
751
|
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:ready-to-build" \
|
|
751
|
-
--remove-label "workflow:investigating,workflow:building,workflow:in-review,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
752
|
+
--remove-label "workflow:investigating,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
752
753
|
;;
|
|
753
754
|
esac
|
|
754
755
|
```
|
|
@@ -797,7 +798,7 @@ case "$TIER" in
|
|
|
797
798
|
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} decomposed ;;
|
|
798
799
|
prose)
|
|
799
800
|
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:decomposed" \
|
|
800
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:merged,workflow:invalid" 2>/dev/null || true
|
|
801
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:invalid" 2>/dev/null || true
|
|
801
802
|
;;
|
|
802
803
|
esac
|
|
803
804
|
```
|
|
@@ -821,7 +822,7 @@ case "$TIER" in
|
|
|
821
822
|
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} invalid ;;
|
|
822
823
|
prose)
|
|
823
824
|
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:invalid" \
|
|
824
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:merged,workflow:decomposed" 2>/dev/null || true
|
|
825
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:decomposed" 2>/dev/null || true
|
|
825
826
|
;;
|
|
826
827
|
esac
|
|
827
828
|
gh issue close {NUMBER} {GH_FLAG} --comment "Closing as invalid: {reason from investigation}"
|
|
@@ -927,7 +928,7 @@ case "$TIER" in
|
|
|
927
928
|
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} decomposed ;;
|
|
928
929
|
prose)
|
|
929
930
|
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:decomposed" \
|
|
930
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:merged,workflow:invalid" 2>/dev/null || true
|
|
931
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:invalid" 2>/dev/null || true
|
|
931
932
|
;;
|
|
932
933
|
esac
|
|
933
934
|
```
|
|
@@ -958,7 +959,7 @@ fi
|
|
|
958
959
|
|
|
959
960
|
### 3A: Re-read state from GitHub (MANDATORY)
|
|
960
961
|
|
|
961
|
-
**Post Phase 3 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`):
|
|
962
|
+
**Post Phase 3 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`, `workflow:awaiting-merge`):
|
|
962
963
|
```bash
|
|
963
964
|
PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
964
965
|
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
@@ -1262,7 +1263,7 @@ case "$TIER" in
|
|
|
1262
1263
|
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} building ;;
|
|
1263
1264
|
prose)
|
|
1264
1265
|
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:building" \
|
|
1265
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:in-review,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
1266
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
1266
1267
|
;;
|
|
1267
1268
|
esac
|
|
1268
1269
|
```
|
|
@@ -1768,7 +1769,7 @@ case "$TIER" in
|
|
|
1768
1769
|
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} in-review ;;
|
|
1769
1770
|
prose)
|
|
1770
1771
|
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:in-review" \
|
|
1771
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
1772
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:awaiting-merge,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
1772
1773
|
;;
|
|
1773
1774
|
esac
|
|
1774
1775
|
```
|
|
@@ -1779,7 +1780,7 @@ esac
|
|
|
1779
1780
|
|
|
1780
1781
|
### 5A: Re-read state from GitHub (MANDATORY)
|
|
1781
1782
|
|
|
1782
|
-
**Post Phase 5 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`):
|
|
1783
|
+
**Post Phase 5 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`, `workflow:awaiting-merge`):
|
|
1783
1784
|
```bash
|
|
1784
1785
|
PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
1785
1786
|
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
@@ -1924,7 +1925,7 @@ case "$TIER" in
|
|
|
1924
1925
|
adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} merged ;;
|
|
1925
1926
|
prose)
|
|
1926
1927
|
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:merged" \
|
|
1927
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
1928
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:invalid,workflow:decomposed" 2>/dev/null || true
|
|
1928
1929
|
;;
|
|
1929
1930
|
esac
|
|
1930
1931
|
```
|
package/docs/FORGE-PROTOCOL.md
CHANGED
|
@@ -13,3 +13,104 @@
|
|
|
13
13
|
> and usable in any product with attribution. This older draft was mistakenly
|
|
14
14
|
> marked AGPL-3.0; the protocol description itself has always been intended for
|
|
15
15
|
> open, license-compatible adoption.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Forge Ledger — Derived Artifact of the Protocol
|
|
20
|
+
|
|
21
|
+
The **Forge Ledger** is the persistent, file/symbol-keyed knowledge index built from FORGE
|
|
22
|
+
annotations. It is a _derived artifact_ of the protocol: the ledger is rebuilt by re-parsing
|
|
23
|
+
the annotation corpus on GitHub (source of truth), not by maintaining separate authoritative
|
|
24
|
+
state.
|
|
25
|
+
|
|
26
|
+
### Architecture
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
GitHub Issues/PRs (annotation corpus, source of truth)
|
|
30
|
+
│
|
|
31
|
+
▼
|
|
32
|
+
scripts/build-knowledge-index.mjs ← incremental indexer (watermarked by issue.updated_at)
|
|
33
|
+
│ uses packages/protocol/src/parse.js (FORGE annotation parser)
|
|
34
|
+
▼
|
|
35
|
+
~/.forge/index/
|
|
36
|
+
knowledge.jsonl ← card records (one JSON per line, keyed by issue/kind/seq)
|
|
37
|
+
postings.json ← BM25-lite inverted index (term → card IDs with per-field tf)
|
|
38
|
+
manifest.json ← issue → content-hash map + watermark + card count
|
|
39
|
+
renames.jsonl ← git rename log (old path → new path for query-time resolution)
|
|
40
|
+
│
|
|
41
|
+
▼ (mirrored to orphan branch)
|
|
42
|
+
forge-knowledge branch ← versioned, diffable, hydratable by any clone or CI
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Card Schema
|
|
46
|
+
|
|
47
|
+
Cards are keyed by provenance (`{issue}/{kind}/{seq}`), never by file path. Paths are
|
|
48
|
+
attributes maintained by the indexer; renames are metadata updates, not re-keys.
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"schemaVersion": 1,
|
|
53
|
+
"id": "1370/pattern/0",
|
|
54
|
+
"kind": "pattern",
|
|
55
|
+
"issue": 1370,
|
|
56
|
+
"pattern": "...",
|
|
57
|
+
"rootCause": "...",
|
|
58
|
+
"prevention": "...",
|
|
59
|
+
"paths": ["bin/runner.mjs"],
|
|
60
|
+
"symbols": ["scrubEnv"],
|
|
61
|
+
"anchor": { "path": "bin/runner.mjs", "symbol": "scrubEnv" },
|
|
62
|
+
"status": "fresh",
|
|
63
|
+
"createdAt": "2026-07-04"
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### CLI
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
# Query by file path (exact lookup):
|
|
71
|
+
forge recall --file bin/runner.mjs --json
|
|
72
|
+
|
|
73
|
+
# Query by symbol:
|
|
74
|
+
forge recall --symbol scrubEnv --json
|
|
75
|
+
|
|
76
|
+
# Free-text BM25 search:
|
|
77
|
+
forge recall "env scrub proc environ" --k 5
|
|
78
|
+
|
|
79
|
+
# Index health:
|
|
80
|
+
forge recall --doctor
|
|
81
|
+
|
|
82
|
+
# Rebuild index from scratch:
|
|
83
|
+
node scripts/build-knowledge-index.mjs --full-rebuild
|
|
84
|
+
|
|
85
|
+
# Incremental update (used by close phase):
|
|
86
|
+
node scripts/build-knowledge-index.mjs --issue {NUMBER}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Invariants
|
|
90
|
+
|
|
91
|
+
- **Full rebuild = incremental parity**: running a full rebuild produces byte-identical
|
|
92
|
+
card content to a sequence of incremental runs over the same issue range.
|
|
93
|
+
- **Idempotency**: re-indexing the same issue twice produces the same card content (card IDs
|
|
94
|
+
are deterministic; content is a pure function of current annotation state).
|
|
95
|
+
- **GitHub wins**: the index is always rebuildable from the annotation corpus alone. Deleting
|
|
96
|
+
`~/.forge/index/` loses nothing — a rebuild restores the same state.
|
|
97
|
+
- **Non-blocking**: ledger indexing at close time never stalls the pipeline. Failure is logged
|
|
98
|
+
and the close phase continues.
|
|
99
|
+
|
|
100
|
+
### Staleness Detection (tiered, cheapest first)
|
|
101
|
+
|
|
102
|
+
1. Current blob sha of `anchor.path` matches stored blob sha → **FRESH**
|
|
103
|
+
2. `anchor.symbol` is still defined in the current file → **FRESH**
|
|
104
|
+
3. Rolling-hash scan finds `snippetHash` elsewhere in file → **MOVED** (re-anchor)
|
|
105
|
+
4. `snippetHash` found in a file touched by rename log → **MOVED** (re-anchor path)
|
|
106
|
+
5. Otherwise → **STALE** (excluded from binding injection; recallable with `--include-stale`)
|
|
107
|
+
|
|
108
|
+
### Integration Points
|
|
109
|
+
|
|
110
|
+
- **`commands/work-on/build/context.md` Phase C1**: uses `forge recall --file` for exact
|
|
111
|
+
file-path lookup; falls back to live `gh issue list --search` when index is absent.
|
|
112
|
+
- **`commands/work-on/close.md` Phase C5.3**: calls `build-knowledge-index.mjs --issue`
|
|
113
|
+
after merge to index the just-closed issue; advances watermark.
|
|
114
|
+
|
|
115
|
+
See issue [#1732](https://github.com/RapierCraftStudios/ForgeDock/issues/1732) for the
|
|
116
|
+
implementation history and design decisions.
|
|
@@ -449,6 +449,43 @@ artifact so a sub-issue builder can fetch upstream context directly.
|
|
|
449
449
|
<!-- FORGE:PRIOR_GIST: {https://example.com/artifact} -->
|
|
450
450
|
```
|
|
451
451
|
|
|
452
|
+
#### Security: Gist Visibility — Normative Requirement <!-- Added: forge#1729 -->
|
|
453
|
+
|
|
454
|
+
**All FORGE gist types (`FORGE:KNOWLEDGE_GIST`, `FORGE:MILESTONE_INDEX`, `FORGE:PRIOR_GIST`,
|
|
455
|
+
and the memory-index Gist referenced by `FORGE:MEMORY_INDEX` in `close.md`) MUST be created
|
|
456
|
+
secret. Publishing them as public Gists is a protocol violation.**
|
|
457
|
+
|
|
458
|
+
Rationale: these artifacts embed investigation findings — root causes, file paths, affected
|
|
459
|
+
service names, and security details. A public Gist exposes this content to a world-readable
|
|
460
|
+
URL with no access control. `gh gist create` is secret by default; the requirement is that
|
|
461
|
+
no pipeline spec or operator script must pass `--public` when creating any of these artifacts.
|
|
462
|
+
|
|
463
|
+
**Enforcement**: The `pre-tool-use.mjs` hook (Rule 3) hard-blocks `gh gist create --public`
|
|
464
|
+
in pipeline sessions. The `doctor-pipeline-state.sh` check I8 audits existing annotation-referenced
|
|
465
|
+
gists and reports a CRITICAL finding if any are public.
|
|
466
|
+
|
|
467
|
+
**Migration** (if a public gist is found): GitHub does not support changing a Gist's visibility
|
|
468
|
+
in place. To replace a public pipeline gist with a secret one:
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
# 1. Save current content
|
|
472
|
+
gh gist view {GIST_ID} > /tmp/gist_migrate.md
|
|
473
|
+
[ -s /tmp/gist_migrate.md ] || { echo "ABORT: empty save"; exit 1; }
|
|
474
|
+
|
|
475
|
+
# 2. Delete the public gist
|
|
476
|
+
gh gist delete {GIST_ID} --yes
|
|
477
|
+
|
|
478
|
+
# 3. Recreate secret (no --public) with same description
|
|
479
|
+
gh gist create /tmp/gist_migrate.md --desc "{ORIGINAL_DESCRIPTION}"
|
|
480
|
+
# gh gist create without --public creates a secret gist.
|
|
481
|
+
|
|
482
|
+
# 4. Update the FORGE annotation on the issue to point to the new URL
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**Override**: set `FORGE_ALLOW_PUBLIC_GIST=1` in the operator's shell environment before
|
|
486
|
+
starting Claude Code only if a public gist is genuinely required (e.g., sharing a demo
|
|
487
|
+
artifact). This must not be used for pipeline memory gists.
|
|
488
|
+
|
|
452
489
|
### 4.3 Control and error markers
|
|
453
490
|
|
|
454
491
|
These markers carry no Markdown body; their presence is the signal.
|
|
@@ -1143,16 +1180,71 @@ separate, MIT-licensed npm package:
|
|
|
1143
1180
|
**`@forgedock/protocol`** — available at `packages/protocol/` in the ForgeDock
|
|
1144
1181
|
repository.
|
|
1145
1182
|
|
|
1146
|
-
|
|
1183
|
+
### 13.1 Normative codec path
|
|
1184
|
+
|
|
1185
|
+
**`packages/protocol/` is the normative write and read path for all FORGE
|
|
1186
|
+
annotations.** Hand-rolled annotation construction (embedding raw
|
|
1187
|
+
`<!-- FORGE:TYPE -->` strings in inline heredocs or `--body` blocks) is
|
|
1188
|
+
**non-conformant** because it bypasses the codec's escaping and round-trip
|
|
1189
|
+
guarantees. Every producer MUST route annotation construction through one of:
|
|
1190
|
+
|
|
1191
|
+
1. **`forge-annotation.sh write <TYPE> --field KEY=VALUE ...`** (AGPL-licensed,
|
|
1192
|
+
ships with the npm package at `scripts/forge-annotation.sh`), or
|
|
1193
|
+
2. **`node packages/protocol/src/cli.js emit <TYPE> --field KEY=VALUE ...`**
|
|
1194
|
+
(MIT-licensed, part of the `@forgedock/protocol` package)
|
|
1195
|
+
|
|
1196
|
+
Both paths delegate to `emit()` from `packages/protocol/src/emit.js`, which
|
|
1197
|
+
applies the complete escaping and sanitization policy (§3.5 injection hardening).
|
|
1198
|
+
|
|
1199
|
+
### 13.2 JavaScript API
|
|
1200
|
+
|
|
1201
|
+
```js
|
|
1202
|
+
import { parse, validate, emit, emitPartial } from '@forgedock/protocol';
|
|
1203
|
+
```
|
|
1147
1204
|
|
|
1148
1205
|
- `parse(commentBody)` — extracts all annotations from a comment body string
|
|
1149
1206
|
- `validate(annotation)` — checks conformance per §3–4; returns `{ valid, errors, warnings }`
|
|
1150
|
-
- `emit(type, fields)` — produces well-formed annotation
|
|
1207
|
+
- `emit(type, fields)` — produces a well-formed annotation string; applies all §3.5 escaping
|
|
1151
1208
|
- `emitPartial(type)` — produces the partial sentinel for a type (§3.3)
|
|
1152
1209
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1210
|
+
### 13.3 CLI subcommands
|
|
1211
|
+
|
|
1212
|
+
The CLI at `packages/protocol/src/cli.js` exposes three modes:
|
|
1213
|
+
|
|
1214
|
+
```bash
|
|
1215
|
+
# Emit — print a well-formed annotation body to stdout
|
|
1216
|
+
node packages/protocol/src/cli.js emit INVESTIGATOR \
|
|
1217
|
+
--field Verdict=CONFIRMED --field Confidence=HIGH \
|
|
1218
|
+
--field Severity=MEDIUM --field "Task Type=Bug Fix" \
|
|
1219
|
+
--field "Decomposition Assessment=NO — single fix."
|
|
1220
|
+
|
|
1221
|
+
# Parse — read a comment body from stdin, extract a field value
|
|
1222
|
+
echo '<!-- FORGE:INVESTIGATOR -->...' | \
|
|
1223
|
+
node packages/protocol/src/cli.js parse --type INVESTIGATOR --field Verdict
|
|
1224
|
+
# → CONFIRMED
|
|
1225
|
+
|
|
1226
|
+
# Conformance runner (legacy — no subcommand keyword)
|
|
1227
|
+
node packages/protocol/src/cli.js fixtures/
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
The `emit` subcommand also supports the Base64url machine-surface form for
|
|
1231
|
+
`FORGE:CARD` annotations (design decision 2026-07-08: encoding beats escaping):
|
|
1232
|
+
|
|
1233
|
+
```bash
|
|
1234
|
+
node packages/protocol/src/cli.js emit CARD \
|
|
1235
|
+
--field issue=1370 --field status=merged --b64
|
|
1236
|
+
# → <!-- FORGE:CARD: v1 sha:7234c2d8 b64:eyJ... -->
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
The Base64url alphabet `[A-Za-z0-9_-]` cannot contain `>`, `<`, or `!`, making
|
|
1240
|
+
`-->`, `--!>`, and `<!--` structurally unrepresentable in the machine surface.
|
|
1241
|
+
|
|
1242
|
+
### 13.4 Conformance suite
|
|
1243
|
+
|
|
1244
|
+
A conformance suite in `packages/protocol/fixtures/` covers all reserved types
|
|
1245
|
+
with valid and invalid examples, including adversarial payloads that reproduce
|
|
1246
|
+
historical escaping vulnerabilities. The suite can be run against any fixtures
|
|
1247
|
+
directory:
|
|
1156
1248
|
|
|
1157
1249
|
```bash
|
|
1158
1250
|
npx @forgedock/protocol fixtures/
|
|
@@ -14,11 +14,14 @@ See also: [FORGE Annotation Protocol §6](forge-protocol-v1.md#6-label-state-mac
|
|
|
14
14
|
| `workflow:ready-to-build` | Investigation complete, build not started | Investigator agent |
|
|
15
15
|
| `workflow:building` | Build phase active | Builder agent |
|
|
16
16
|
| `workflow:in-review` | PR created, review active | Orchestrator agent |
|
|
17
|
+
| `workflow:awaiting-merge` | Remediated + re-reviewed, awaiting a human merge decision | Review-pr Phase 8 (auto-merge guard) |
|
|
17
18
|
| `workflow:merged` | PR merged, issue closed | Close phase agent |
|
|
18
19
|
| `workflow:invalid` | Issue closed as invalid | Investigator agent |
|
|
19
20
|
| `workflow:decomposed` | Issue decomposed into sub-issues | Decomposer agent |
|
|
20
21
|
| `needs-human` | Pipeline blocked, human intervention required | Any agent on error |
|
|
21
22
|
|
|
23
|
+
`workflow:awaiting-merge` vs `needs-human`: both are terminal (the pipeline stops advancing the issue automatically), but they mean different things. `needs-human` means the pipeline hit a condition it cannot resolve on its own (genuinely blocked — conflicting PR, failed verdict, calibration/trust escalation, etc.) and a human must diagnose and act. `workflow:awaiting-merge` means the opposite: the PR was previously escalated to `needs-human`, has since been remediated and re-reviewed to a clean `APPROVED` verdict with no mergeability blockers, but does not yet meet the automated auto-land bar (see forge#1809 Q1) — a human only needs to click merge, not diagnose a problem. `scripts/transition-label.sh` clears `needs-human` (best-effort) on every transition, so moving an issue onto `workflow:awaiting-merge` also clears any stale `needs-human` label.
|
|
24
|
+
|
|
22
25
|
## Transitions
|
|
23
26
|
|
|
24
27
|
```
|
|
@@ -27,6 +30,7 @@ See also: [FORGE Annotation Protocol §6](forge-protocol-v1.md#6-label-state-mac
|
|
|
27
30
|
→ workflow:ready-to-build [Phase 2: Investigation complete]
|
|
28
31
|
→ workflow:building [Phase 3: Build started]
|
|
29
32
|
→ workflow:in-review [Phase 4: PR created]
|
|
33
|
+
→ workflow:awaiting-merge [TERMINAL] [Phase 5/review-pr: re-reviewed after needs-human, awaiting human merge]
|
|
30
34
|
→ workflow:merged [TERMINAL] [Phase 5: PR merged]
|
|
31
35
|
→ workflow:invalid [TERMINAL] [Any phase: closed as invalid]
|
|
32
36
|
→ workflow:decomposed [TERMINAL] [Phase 2: split into sub-issues]
|
|
@@ -40,6 +44,7 @@ Processing stops when any of these labels is set:
|
|
|
40
44
|
- `workflow:merged`
|
|
41
45
|
- `workflow:invalid`
|
|
42
46
|
- `workflow:decomposed`
|
|
47
|
+
- `workflow:awaiting-merge`
|
|
43
48
|
- `needs-human`
|
|
44
49
|
|
|
45
50
|
## Label Exclusivity
|
|
@@ -49,7 +54,7 @@ At most one `workflow:*` label should be active on an issue at any time. When tr
|
|
|
49
54
|
```bash
|
|
50
55
|
gh issue edit {NUMBER} -R {REPO} \
|
|
51
56
|
--add-label "workflow:building" \
|
|
52
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:in-review,workflow:merged,workflow:invalid,workflow:decomposed"
|
|
57
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:invalid,workflow:decomposed"
|
|
53
58
|
```
|
|
54
59
|
|
|
55
60
|
This is enforced by `scripts/transition-label.sh` — use `resolve_script 'transition-label'` rather than calling `gh issue edit` directly when possible.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgedock",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"description": "Autonomous dev pipeline for Claude Code. GitHub as institutional memory — every issue, PR, and annotation survives context resets and makes the next agent smarter.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"forgedock": "bin/forgedock.mjs"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"README.md"
|
|
46
46
|
],
|
|
47
47
|
"scripts": {
|
|
48
|
-
"test": "node --test \"bin/tests/**/*.test.mjs\"",
|
|
48
|
+
"test": "node --test \"bin/tests/**/*.test.mjs\" \"packages/protocol/test/**/*.test.mjs\"",
|
|
49
49
|
"docs:dev": "vitepress dev docs/site",
|
|
50
50
|
"docs:build": "vitepress build docs/site",
|
|
51
51
|
"docs:preview": "vitepress preview docs/site"
|