forgedock 1.1.6 → 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/README.md +3 -1
- package/bin/engine/invariants.mjs +361 -0
- package/bin/hooks/pre-tool-use.mjs +182 -0
- package/bin/journey.mjs +77 -7
- package/bin/labels.json +10 -0
- package/bin/recall.mjs +540 -0
- package/commands/autopilot.md +959 -78
- 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 +448 -29
- 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/design/awesome-list-submissions.md +105 -0
- package/docs/design/f5bot-monitoring-setup.md +43 -0
- package/docs/design/growth-strategy-2026.md +579 -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/refresh-bot-token.sh +97 -0
- package/scripts/select-fix-targets.sh +234 -0
- package/scripts/transition-label.sh +157 -6
|
@@ -202,6 +202,50 @@ if [ -n "$DEVDOCS_PATH" ] && [ -f "$INDEX_PATH" ]; then
|
|
|
202
202
|
|
|
203
203
|
DEVDOCS_APPLICABLE=$(printf "%s" "$DEVDOCS_APPLICABLE" | sort | cut -d'|' -f2-)
|
|
204
204
|
|
|
205
|
+
# --- Module dossier glob pass (index-first path only) ---
|
|
206
|
+
# When AFFECTED_FILES are known (passed via --repo-path or from the contract),
|
|
207
|
+
# match each file against every modules[].glob in index.yaml. Inject matched
|
|
208
|
+
# dossier files into DEVDOCS_APPLICABLE as authority:required entries.
|
|
209
|
+
#
|
|
210
|
+
# Skip if: AFFECTED_FILES is empty, modules section absent, or DEVDOCS_PATH unset.
|
|
211
|
+
#
|
|
212
|
+
# Glob matching uses bash `case` (fnmatch semantics): supports * ? and [class].
|
|
213
|
+
# Operators must use patterns that match the BASENAME of the affected file
|
|
214
|
+
# (e.g. "runner*" matches "bin/runner.mjs" via basename extraction below)
|
|
215
|
+
# OR a relative-path prefix pattern (e.g. "bin/runner*").
|
|
216
|
+
# Both forms are tried; first match wins for each module entry.
|
|
217
|
+
if [ -n "${AFFECTED_FILES:-}" ] && [ -n "$DEVDOCS_PATH" ] && [ -f "$INDEX_PATH" ]; then
|
|
218
|
+
# Extract modules[] entries from index.yaml
|
|
219
|
+
# Format per entry: "name|glob|path"
|
|
220
|
+
MODULE_ENTRIES=$(yq '.modules[]? | .name + "|" + .glob + "|" + .path' "$INDEX_PATH" 2>/dev/null || echo "")
|
|
221
|
+
|
|
222
|
+
if [ -n "$MODULE_ENTRIES" ]; then
|
|
223
|
+
IFS=' ' read -ra AFFECTED_FILES_GLOB_ARR <<< "${AFFECTED_FILES}"
|
|
224
|
+
while IFS='|' read -r MOD_NAME MOD_GLOB MOD_PATH; do
|
|
225
|
+
[ -z "$MOD_GLOB" ] || [ -z "$MOD_PATH" ] && continue
|
|
226
|
+
DOSSIER_ABS="${DEVDOCS_PATH}/${MOD_PATH}"
|
|
227
|
+
[ -f "$DOSSIER_ABS" ] || { echo "WARN: modules[${MOD_NAME}] references missing dossier '${MOD_PATH}' — skipping"; continue; }
|
|
228
|
+
MATCHED=0
|
|
229
|
+
for af in "${AFFECTED_FILES_GLOB_ARR[@]}"; do
|
|
230
|
+
[ -z "$af" ] && continue
|
|
231
|
+
AF_BASENAME=$(basename "$af")
|
|
232
|
+
# Try basename match first, then relative-path match
|
|
233
|
+
case "$AF_BASENAME" in
|
|
234
|
+
$MOD_GLOB) MATCHED=1; break ;;
|
|
235
|
+
esac
|
|
236
|
+
case "$af" in
|
|
237
|
+
$MOD_GLOB) MATCHED=1; break ;;
|
|
238
|
+
esac
|
|
239
|
+
done
|
|
240
|
+
if [ "$MATCHED" -eq 1 ]; then
|
|
241
|
+
echo "Module dossier matched: '${MOD_GLOB}' → ${MOD_PATH} (module: ${MOD_NAME})"
|
|
242
|
+
# Add as sort key "0" (higher priority than required=1) so dossier appears first
|
|
243
|
+
DEVDOCS_APPLICABLE="0|${DOSSIER_ABS}"$'\n'"${DEVDOCS_APPLICABLE}"
|
|
244
|
+
fi
|
|
245
|
+
done <<< "$MODULE_ENTRIES"
|
|
246
|
+
fi
|
|
247
|
+
fi
|
|
248
|
+
|
|
205
249
|
elif [ -n "$DEVDOCS_PATH" ]; then
|
|
206
250
|
# --- Fallback: O(N) enumerate (backward compatible — no index.yaml present) ---
|
|
207
251
|
echo "No index.yaml found at ${INDEX_PATH} — falling back to full enumerate (backward compatible)"
|
|
@@ -266,6 +310,229 @@ done <<< "$DEVDOCS_APPLICABLE"
|
|
|
266
310
|
|
|
267
311
|
`DEVDOCS_CONTENT` is used in the `### Authoritative Devdocs` section of the FORGE:CONTEXT comment output. If empty (path absent or no applicable files), the section is replaced with a skip note.
|
|
268
312
|
|
|
313
|
+
**Module dossier injection**: When the glob pass (Step 1, index-first path) matched one or more dossiers, they appear first in `DEVDOCS_APPLICABLE` (sort key `0`) and are rendered as `#### Module Dossier: {name}` sub-sections within `### Authoritative Devdocs`. The 200-line/file cap applies to each dossier individually — the same cap used for all other devdocs files. This ensures dossier injection is bounded by the existing token budget even as dossiers grow. <!-- Added: forge#1733 -->
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Phase C-0.5: Active Peer Claims Reader (conditional — when running under orchestration batch) <!-- Added: forge#1736 -->
|
|
318
|
+
|
|
319
|
+
**Skip if**: `FORGE_COORD_ISSUE` is not set. This phase is a no-op when the agent is not running under an `/orchestrate` batch — no error, no output.
|
|
320
|
+
|
|
321
|
+
**Purpose**: Before writing any code, check whether peer agents in the same orchestration batch have posted `FORGE:CLAIM` annotations on the coordination issue that overlap with this agent's planned files. If overlapping claims exist, inject them into the builder's mental model as explicit constraints so the builder avoids modifying interfaces the peer has reserved.
|
|
322
|
+
|
|
323
|
+
**Time budget**: 20 seconds. If exceeded, log a warning and continue without peer-claim constraints.
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
if [ -n "${FORGE_COORD_ISSUE:-}" ]; then
|
|
327
|
+
COORD_NUM=$(echo "$FORGE_COORD_ISSUE" | grep -oE '[0-9]+$')
|
|
328
|
+
if [ -n "$COORD_NUM" ]; then
|
|
329
|
+
echo "Phase C-0.5: reading active peer claims from coordination issue #${COORD_NUM}"
|
|
330
|
+
|
|
331
|
+
# Fetch all comments from coordination issue
|
|
332
|
+
COORD_COMMENTS=$(gh api repos/{GH_REPO}/issues/${COORD_NUM}/comments \
|
|
333
|
+
--jq 'map(select(.body | contains("<!-- FORGE:CLAIM -->")))' 2>/dev/null || echo '[]')
|
|
334
|
+
|
|
335
|
+
# Extract active claims: FORGE:CLAIM comments that are NOT followed by FORGE:CLAIM_RELEASED
|
|
336
|
+
# from the same Holder. We identify active claims by the presence of CLAIM:COMPLETE
|
|
337
|
+
# and the absence of a subsequent CLAIM_RELEASED referencing the same Holder.
|
|
338
|
+
ACTIVE_PEER_CLAIMS=$(echo "$COORD_COMMENTS" | jq -r '.[] |
|
|
339
|
+
select(.body | contains("<!-- CLAIM:COMPLETE -->")) |
|
|
340
|
+
select(.body | contains("<!-- FORGE:CLAIM_RELEASED -->") | not) |
|
|
341
|
+
"Holder: " + (.body | capture("\\*\\*Holder\\*\\*: (?P<h>[^\n]+)").h // "unknown") +
|
|
342
|
+
"\nFiles: " + (.body | capture("\\*\\*Files\\*\\*: (?P<f>[^\n]+)").f // "none")
|
|
343
|
+
' 2>/dev/null | grep -v "^Holder: #${NUMBER}" || echo "")
|
|
344
|
+
# Note: grep -v filters out this agent's own claim (same issue number) — peers only
|
|
345
|
+
|
|
346
|
+
if [ -n "$ACTIVE_PEER_CLAIMS" ]; then
|
|
347
|
+
echo "Active peer claims found:"
|
|
348
|
+
echo "$ACTIVE_PEER_CLAIMS"
|
|
349
|
+
|
|
350
|
+
# Extract claimed file paths from peer claims
|
|
351
|
+
PEER_CLAIMED_FILES=$(echo "$COORD_COMMENTS" | jq -r '.[] |
|
|
352
|
+
select(.body | contains("<!-- CLAIM:COMPLETE -->")) |
|
|
353
|
+
select(.body | contains("<!-- FORGE:CLAIM_RELEASED -->") | not) |
|
|
354
|
+
.body' 2>/dev/null \
|
|
355
|
+
| grep -v "^#${NUMBER}" \
|
|
356
|
+
| awk '/\*\*Files\*\*:/{found=1; next} /\*\*Interfaces\*\*:/{found=0} found{print}' \
|
|
357
|
+
| grep -oP '[a-zA-Z0-9._/-]+\.(py|tsx?|jsx?|sql|json|ya?ml|md|mjs|sh)' \
|
|
358
|
+
| sort -u || echo "")
|
|
359
|
+
|
|
360
|
+
# Check overlap with this agent's planned files (from FORGE:CONTRACT)
|
|
361
|
+
OWN_FILES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
362
|
+
--jq '[.[] | select(.body | contains("FORGE:CONTRACT"))] | last | .body' 2>/dev/null \
|
|
363
|
+
| grep -oP '`[^`]+\.(py|tsx?|jsx?|sql|json|ya?ml|md|mjs|sh)`' \
|
|
364
|
+
| tr -d '`' | sort -u || echo "")
|
|
365
|
+
|
|
366
|
+
OVERLAP=$(comm -12 <(echo "$PEER_CLAIMED_FILES") <(echo "$OWN_FILES") 2>/dev/null || echo "")
|
|
367
|
+
|
|
368
|
+
if [ -n "$OVERLAP" ]; then
|
|
369
|
+
PEER_CLAIMS_CONSTRAINT="
|
|
370
|
+
⚠ CLAIMS BOARD CONSTRAINT: The following files are claimed by peer agents in this orchestration batch.
|
|
371
|
+
You MUST NOT modify the public interfaces of these files without first checking the peer's FORGE:CLAIM
|
|
372
|
+
on coordination issue #${COORD_NUM} to understand what interfaces they have reserved.
|
|
373
|
+
|
|
374
|
+
Overlapping claimed files:
|
|
375
|
+
$(echo "$OVERLAP" | sed 's/^/ - /')
|
|
376
|
+
|
|
377
|
+
Peer claims:
|
|
378
|
+
$(echo "$ACTIVE_PEER_CLAIMS" | sed 's/^/ /')
|
|
379
|
+
|
|
380
|
+
If you need to change an interface claimed by a peer, post a comment on coordination issue #${COORD_NUM}
|
|
381
|
+
describing the conflict — this routes to Phase 2.5 arbitration."
|
|
382
|
+
else
|
|
383
|
+
PEER_CLAIMS_CONSTRAINT="
|
|
384
|
+
ℹ CLAIMS BOARD: Peer claims exist but no file overlap with this agent's planned files.
|
|
385
|
+
Peer agents are working on independent file sets — no interface constraint applies.
|
|
386
|
+
Peer claims (for reference): $(echo "$ACTIVE_PEER_CLAIMS" | head -5)"
|
|
387
|
+
fi
|
|
388
|
+
|
|
389
|
+
# Store for injection into the FORGE:CONTEXT comment (Phase C4)
|
|
390
|
+
export PEER_CLAIMS_CONSTRAINT
|
|
391
|
+
else
|
|
392
|
+
echo "Phase C-0.5: no active peer claims found on coordination issue #${COORD_NUM}"
|
|
393
|
+
PEER_CLAIMS_CONSTRAINT=""
|
|
394
|
+
export PEER_CLAIMS_CONSTRAINT
|
|
395
|
+
fi
|
|
396
|
+
fi
|
|
397
|
+
fi
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Constraint injection into FORGE:CONTEXT**: When `PEER_CLAIMS_CONSTRAINT` is set, append it to the `### Known Pitfalls for This Area` section of the FORGE:CONTEXT comment (Phase C4). This ensures the builder sees peer claims in the same context block as historical review findings — a single consolidated constraint surface. If `PEER_CLAIMS_CONSTRAINT` is empty, the section is omitted.
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## Phase C0.5: Danger-Zone Rule Cards (fixed 400-token slot) <!-- Added: forge#1744 -->
|
|
405
|
+
|
|
406
|
+
Surface the highest-value risk knowledge for exactly the files this build will touch. Reads the persisted danger-zones index produced by `scripts/danger-zones.mjs`, filters to files that overlap the Builder Contract's deliverables table, ranks by risk score, and emits one-line rule cards cut at a hard 400-token ceiling. The slot is constant by construction — never more, never less — so risk injection adds zero variance to the builder's token budget.
|
|
407
|
+
|
|
408
|
+
**Time budget**: 10 seconds. If exceeded, log a warning and continue without danger-zone cards.
|
|
409
|
+
|
|
410
|
+
**Skip if**: `~/.forge/index/danger-zones.json` is absent (cold start — no index built yet). Zero cards → section omitted from FORGE:CONTEXT (no empty scaffolding). <!-- Cold-start safety: required — the index only exists after danger-zones.mjs has run at least once -->
|
|
411
|
+
|
|
412
|
+
### Step 0: Locate danger-zones.json
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
# Resolve index directory — forge.yaml may override ~/.forge/index
|
|
416
|
+
FORGE_INDEX_DIR="${HOME}/.forge/index"
|
|
417
|
+
if [ -f "{REPO_PATH}/forge.yaml" ]; then
|
|
418
|
+
FORGE_INDEX_OVERRIDE=$(yq '.forge_index.directory // ""' "{REPO_PATH}/forge.yaml" 2>/dev/null || echo '')
|
|
419
|
+
[ -n "$FORGE_INDEX_OVERRIDE" ] && FORGE_INDEX_DIR="$FORGE_INDEX_OVERRIDE"
|
|
420
|
+
fi
|
|
421
|
+
|
|
422
|
+
DANGER_ZONES_PATH="${FORGE_INDEX_DIR}/danger-zones.json"
|
|
423
|
+
|
|
424
|
+
if [ ! -f "$DANGER_ZONES_PATH" ]; then
|
|
425
|
+
echo "Phase C0.5: danger-zones.json absent at ${DANGER_ZONES_PATH} — skipping (cold start)"
|
|
426
|
+
DANGER_ZONE_CARDS=""
|
|
427
|
+
# → Continue to Phase C0 without emitting any cards
|
|
428
|
+
fi
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Step 1: Extract contract-overlapping files
|
|
432
|
+
|
|
433
|
+
Read the Builder Contract from the FORGE:CONTRACT comment on the issue. Extract the file paths listed in the Deliverables table:
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
CONTRACT_FILES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
437
|
+
--jq '[.[] | select(.body | contains("FORGE:CONTRACT")) | .body] | last // ""' 2>/dev/null \
|
|
438
|
+
| grep -oE '`[^`]+\.(py|ts|tsx|js|mjs|md|sh|yaml|yml|json)[^`]*`' \
|
|
439
|
+
| tr -d '`' \
|
|
440
|
+
| sort -u)
|
|
441
|
+
|
|
442
|
+
if [ -z "$CONTRACT_FILES" ]; then
|
|
443
|
+
# Fallback: use AFFECTED_FILES from arguments
|
|
444
|
+
CONTRACT_FILES=$(echo "{AFFECTED_FILES}" | tr ' ' '\n' | grep -v '^$' | sort -u)
|
|
445
|
+
fi
|
|
446
|
+
echo "Phase C0.5: contract files: $(echo "$CONTRACT_FILES" | tr '\n' ' ')"
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Step 2: Filter danger-zones.json to overlapping files
|
|
450
|
+
|
|
451
|
+
Read the danger-zones index and select only entries whose `file` key matches (by basename or full path) any file in the contract:
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
DANGER_ZONE_CARDS=""
|
|
455
|
+
TOKEN_BUDGET=400 # hard cap (tokens)
|
|
456
|
+
CHAR_BUDGET=$((TOKEN_BUDGET * 4)) # proxy: 1 token ≈ 4 chars → 1600 chars
|
|
457
|
+
CHARS_USED=0
|
|
458
|
+
|
|
459
|
+
# Read and filter danger-zones.json entries (files key is a dict keyed by file path)
|
|
460
|
+
# For each contract file: look up its entry in danger-zones.json by basename or full path
|
|
461
|
+
while IFS= read -r contract_file; do
|
|
462
|
+
[ -z "$contract_file" ] && continue
|
|
463
|
+
BASENAME=$(basename "$contract_file")
|
|
464
|
+
|
|
465
|
+
# Look up by exact path first, then by basename match
|
|
466
|
+
DZ_ENTRY=$(python3 -c "
|
|
467
|
+
import sys, json, os
|
|
468
|
+
|
|
469
|
+
dz = json.load(open('${DANGER_ZONES_PATH}'))
|
|
470
|
+
files = dz.get('files', {})
|
|
471
|
+
|
|
472
|
+
# Try exact match first
|
|
473
|
+
target = '${contract_file}'
|
|
474
|
+
if target in files:
|
|
475
|
+
entry = files[target]
|
|
476
|
+
entry['file'] = target
|
|
477
|
+
print(json.dumps(entry))
|
|
478
|
+
sys.exit(0)
|
|
479
|
+
|
|
480
|
+
# Basename fallback: match any file whose basename equals the target basename
|
|
481
|
+
basename = os.path.basename(target)
|
|
482
|
+
for path, entry in files.items():
|
|
483
|
+
if os.path.basename(path) == basename:
|
|
484
|
+
entry['file'] = path
|
|
485
|
+
print(json.dumps(entry))
|
|
486
|
+
sys.exit(0)
|
|
487
|
+
" 2>/dev/null || echo '')
|
|
488
|
+
|
|
489
|
+
[ -z "$DZ_ENTRY" ] && continue
|
|
490
|
+
|
|
491
|
+
# Extract fields for the card
|
|
492
|
+
FILE_PATH=$(echo "$DZ_ENTRY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('file',''))" 2>/dev/null || echo '')
|
|
493
|
+
FINDINGS=$(echo "$DZ_ENTRY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('findingCount90d',0))" 2>/dev/null || echo '0')
|
|
494
|
+
TOP_PATTERN=$(echo "$DZ_ENTRY" | python3 -c "import sys,json; d=json.load(sys.stdin); p=d.get('topPatterns',[]); print(p[0] if p else '')" 2>/dev/null || echo '')
|
|
495
|
+
TOP_ISSUE=$(echo "$DZ_ENTRY" | python3 -c "import sys,json; d=json.load(sys.stdin); ci=d.get('citedIssues',[]); print('#'+str(ci[0]) if ci else '')" 2>/dev/null || echo '')
|
|
496
|
+
RISK_SCORE=$(echo "$DZ_ENTRY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('riskScore',0))" 2>/dev/null || echo '0')
|
|
497
|
+
|
|
498
|
+
# Skip files with zero findings (only in danger-zones due to co-change, not findings)
|
|
499
|
+
[ "$FINDINGS" -eq 0 ] && continue
|
|
500
|
+
|
|
501
|
+
# Build one-line card in issue-spec format:
|
|
502
|
+
# {file} — {N} findings/90d — recurring: {pattern} (#{issue}); rule: {prevention}
|
|
503
|
+
if [ -n "$TOP_PATTERN" ] && [ -n "$TOP_ISSUE" ]; then
|
|
504
|
+
CARD="${FILE_PATH} — ${FINDINGS} findings/90d — recurring: ${TOP_PATTERN} (${TOP_ISSUE}); rule: do not repeat this pattern"
|
|
505
|
+
elif [ -n "$TOP_PATTERN" ]; then
|
|
506
|
+
CARD="${FILE_PATH} — ${FINDINGS} findings/90d — recurring: ${TOP_PATTERN}; rule: do not repeat this pattern"
|
|
507
|
+
else
|
|
508
|
+
CARD="${FILE_PATH} — ${FINDINGS} findings/90d; rule: review before modifying"
|
|
509
|
+
fi
|
|
510
|
+
|
|
511
|
+
# Enforce 400-token cap (1600 char proxy)
|
|
512
|
+
CARD_LEN=${#CARD}
|
|
513
|
+
if [ $((CHARS_USED + CARD_LEN + 2)) -gt $CHAR_BUDGET ]; then
|
|
514
|
+
echo "Phase C0.5: token cap reached at ${CHARS_USED} chars (≈$((CHARS_USED / 4)) tokens) — truncating card list"
|
|
515
|
+
break
|
|
516
|
+
fi
|
|
517
|
+
|
|
518
|
+
DANGER_ZONE_CARDS="${DANGER_ZONE_CARDS}
|
|
519
|
+
- ${CARD}"
|
|
520
|
+
CHARS_USED=$((CHARS_USED + CARD_LEN + 2))
|
|
521
|
+
|
|
522
|
+
done < <(echo "$CONTRACT_FILES")
|
|
523
|
+
|
|
524
|
+
CARDS_TOKEN_COUNT=$((CHARS_USED / 4))
|
|
525
|
+
if [ -n "$DANGER_ZONE_CARDS" ]; then
|
|
526
|
+
echo "Phase C0.5: emitting ${CARDS_TOKEN_COUNT} tokens of danger-zone cards (cap: ${TOKEN_BUDGET})"
|
|
527
|
+
else
|
|
528
|
+
echo "Phase C0.5: no danger-zone entries for contract files — skipping section"
|
|
529
|
+
fi
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### Step 3: Store for output
|
|
533
|
+
|
|
534
|
+
`DANGER_ZONE_CARDS` and `CARDS_TOKEN_COUNT` are used in the `### Danger-Zone Rule Cards` section of the FORGE:CONTEXT comment output. If `DANGER_ZONE_CARDS` is empty (no overlapping files with findings, or index absent), the section is **omitted entirely** — no empty scaffolding.
|
|
535
|
+
|
|
269
536
|
---
|
|
270
537
|
|
|
271
538
|
## Phase C0: Prior Investigation Findings (from Gists)
|
|
@@ -406,30 +673,73 @@ If `GIST_SUMMARIES` is non-empty, it will be included in the `### Prior Investig
|
|
|
406
673
|
|
|
407
674
|
## Phase C1: Past Review Findings on These Files
|
|
408
675
|
|
|
676
|
+
**Primary path — Forge Ledger** (O(1) local index lookup, zero API calls): <!-- Added: forge#1732 -->
|
|
677
|
+
|
|
678
|
+
Check for a local knowledge index before making any GitHub API calls. If the index exists, use
|
|
679
|
+
`forge recall` for exact file-path lookups. Fall back to live `gh issue list --search` only when
|
|
680
|
+
the index is absent or returns no results for a given file.
|
|
681
|
+
|
|
682
|
+
```bash
|
|
683
|
+
# Resolve recall CLI path relative to repository root
|
|
684
|
+
RECALL_PATH="${REPO_PATH:-$(git rev-parse --show-toplevel 2>/dev/null)}/bin/recall.mjs"
|
|
685
|
+
LEDGER_AVAILABLE=0
|
|
686
|
+
|
|
687
|
+
if [ -f "$RECALL_PATH" ]; then
|
|
688
|
+
# Quick probe: does the index exist and have cards?
|
|
689
|
+
PROBE=$(node "$RECALL_PATH" --doctor 2>/dev/null | grep "^Total cards:" | grep -v "^Total cards: 0" || true)
|
|
690
|
+
[ -n "$PROBE" ] && LEDGER_AVAILABLE=1
|
|
691
|
+
fi
|
|
692
|
+
|
|
693
|
+
LEDGER_FINDINGS=""
|
|
694
|
+
|
|
695
|
+
if [ "$LEDGER_AVAILABLE" -eq 1 ]; then
|
|
696
|
+
echo "[context:C1] Using Forge Ledger for file-path recall (zero API calls)"
|
|
697
|
+
IFS=' ' read -ra AFFECTED_FILES_ARR <<< "{AFFECTED_FILES}"
|
|
698
|
+
for file in "${AFFECTED_FILES_ARR[@]}"; do
|
|
699
|
+
FILE_CARDS=$(node "$RECALL_PATH" --file "$file" --k 5 --json 2>/dev/null || echo "[]")
|
|
700
|
+
if [ "$FILE_CARDS" != "[]" ] && [ -n "$FILE_CARDS" ]; then
|
|
701
|
+
LEDGER_FINDINGS="${LEDGER_FINDINGS}
|
|
702
|
+
### Ledger findings for \`${file}\`
|
|
703
|
+
\`\`\`json
|
|
704
|
+
${FILE_CARDS}
|
|
705
|
+
\`\`\`"
|
|
706
|
+
fi
|
|
707
|
+
done
|
|
708
|
+
fi
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
**Extract from ledger results** (when `LEDGER_AVAILABLE=1` and `LEDGER_FINDINGS` is non-empty):
|
|
712
|
+
Parse the JSON card array. For each card: extract `kind`, `rootCause`, `pattern`, `prevention`,
|
|
713
|
+
`paths`, `symbols`, `issue` (citation). Include in the FORGE:CONTEXT output under
|
|
714
|
+
**### Known Pitfalls for This Area** (pattern/stale cards) and **### Historical Findings on These
|
|
715
|
+
Files** (investigation cards). Cards with `status: "stale"` are noted as such but still included
|
|
716
|
+
— they may describe a bug class that has since moved files.
|
|
717
|
+
|
|
718
|
+
**Fallback path — live GitHub search** (when `LEDGER_AVAILABLE=0` or `LEDGER_FINDINGS` is empty):
|
|
719
|
+
|
|
409
720
|
Query closed issues with `review-finding` label, searching by filename:
|
|
410
721
|
|
|
411
722
|
```bash
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
done
|
|
723
|
+
if [ "$LEDGER_AVAILABLE" -eq 0 ] || [ -z "$LEDGER_FINDINGS" ]; then
|
|
724
|
+
echo "[context:C1] Forge Ledger unavailable or empty — falling back to live gh search"
|
|
725
|
+
IFS=' ' read -ra AFFECTED_FILES_ARR <<< "{AFFECTED_FILES}"
|
|
726
|
+
for file in "${AFFECTED_FILES_ARR[@]}"; do
|
|
727
|
+
basename=$(basename "$file" .py)
|
|
728
|
+
gh issue list -R {GH_REPO} \
|
|
729
|
+
--state closed \
|
|
730
|
+
--label "review-finding" \
|
|
731
|
+
--search "$basename" \
|
|
732
|
+
--limit 10 \
|
|
733
|
+
--json number,title,body \
|
|
734
|
+
--jq '.[] | {
|
|
735
|
+
number,
|
|
736
|
+
title,
|
|
737
|
+
pattern: (.body | capture("\\*\\*Pattern\\*\\*: *(?<p>[^\\n]+)").p // null),
|
|
738
|
+
prevention: (.body | capture("\\*\\*Prevention\\*\\*: *(?<v>[^\\n]+)").v // null),
|
|
739
|
+
root_cause: (.body | capture("\\*\\*Root cause\\*\\*: *(?<rc>[^\\n]+)").rc // (.body | capture("Root Cause[^\\n]*\\n(?<rc>[^\\n]+)").rc // "see body"))
|
|
740
|
+
}'
|
|
741
|
+
done
|
|
742
|
+
fi
|
|
433
743
|
```
|
|
434
744
|
|
|
435
745
|
Keep findings where the filename or function name appears in the title or body. Discard false matches (same word, different module).
|
|
@@ -526,6 +836,15 @@ Use 2-3 keywords from the issue title. If no results, skip this phase — do not
|
|
|
526
836
|
|
|
527
837
|
## Output Format
|
|
528
838
|
|
|
839
|
+
**CODEC PATH (forge#1727)**: Post the `<!-- FORGE:CONTEXT -->` comment via the protocol codec — do NOT hand-roll the opening tag or completion sentinel. Use `forge-annotation.sh write CONTEXT` or `node packages/protocol/src/cli.js emit CONTEXT` to produce the tag and sentinel (`<!-- FORGE:CONTEXT:COMPLETE -->`). The codec handles completion sentinel emission automatically.
|
|
840
|
+
|
|
841
|
+
```bash
|
|
842
|
+
# Codec produces the opening tag and completion sentinel
|
|
843
|
+
CONTEXT_BODY=$(node packages/protocol/src/cli.js emit CONTEXT)
|
|
844
|
+
# $CONTEXT_BODY = "<!-- FORGE:CONTEXT -->\n<!-- FORGE:CONTEXT:COMPLETE -->"
|
|
845
|
+
# Insert the Markdown sections between the opening tag line and the sentinel.
|
|
846
|
+
```
|
|
847
|
+
|
|
529
848
|
Post the following as a GitHub comment on `{NUMBER}`:
|
|
530
849
|
|
|
531
850
|
```bash
|
|
@@ -540,12 +859,26 @@ gh issue comment {NUMBER} -R {GH_REPO} --body "<!-- FORGE:CONTEXT -->
|
|
|
540
859
|
'No devdocs found at {DEVDOCS_PATH} — skipping. Run `npx forgedock docs init` to scaffold.' -->
|
|
541
860
|
{DEVDOCS_CONTENT}
|
|
542
861
|
|
|
862
|
+
### Danger-Zone Rule Cards
|
|
863
|
+
<!-- Ranked risk cards for contract-overlapping files from the Forge Ledger danger-zones index (Phase C0.5).
|
|
864
|
+
Token budget: {CARDS_TOKEN_COUNT} / 400 tokens used.
|
|
865
|
+
Each card: {file} — {N} findings/90d — recurring: {pattern} (#{issue}); rule: {prevention}
|
|
866
|
+
BINDING CONSTRAINT: treat each card as a must-not-violate rule before committing.
|
|
867
|
+
If danger-zones.json was absent or no contract files had findings — omit this section entirely (no empty scaffolding). -->
|
|
868
|
+
{DANGER_ZONE_CARDS}
|
|
869
|
+
|
|
543
870
|
### Prior Investigation Findings
|
|
544
871
|
<!-- Summarized Knowledge Gist content from upstream investigations (Phase C0).
|
|
545
872
|
If no FORGE:PRIOR_GIST annotations were found in the issue body: omit this section entirely.
|
|
546
873
|
If Gist fetches failed: include the failure note so the builder knows context was attempted. -->
|
|
547
874
|
{GIST_SUMMARIES}
|
|
548
875
|
|
|
876
|
+
### Claims Board Constraints
|
|
877
|
+
<!-- Active peer claims from the orchestration coordination issue (Phase C-0.5).
|
|
878
|
+
If FORGE_COORD_ISSUE is not set (not running under /orchestrate): omit this section entirely.
|
|
879
|
+
If no peer claims overlap this agent's planned files: write 'No overlapping peer claims.' -->
|
|
880
|
+
{PEER_CLAIMS_CONSTRAINT}
|
|
881
|
+
|
|
549
882
|
### Known Pitfalls for This Area
|
|
550
883
|
<!-- Structured prevention rules extracted from past review-finding issues (Pattern Metadata section).
|
|
551
884
|
If a finding has a Prevention field, list it here. Builder MUST read these before writing code.
|
|
@@ -607,7 +940,9 @@ This module runs at **Step 3C.5** — after Builder Contract is posted, before I
|
|
|
607
940
|
```
|
|
608
941
|
3C → Builder Contract posted
|
|
609
942
|
3C.5 → [THIS MODULE] Context gathering (max 2 min)
|
|
610
|
-
Phase C-1:
|
|
943
|
+
Phase C-1: Authoritative Devdocs (project-resident knowledge — highest precedence)
|
|
944
|
+
Phase C-0.5: Active Peer Claims Reader (conditional — orchestration only)
|
|
945
|
+
Phase C0.5: Danger-Zone Rule Cards (fixed 400-token slot — forge#1744)
|
|
611
946
|
Phase C0: Prior Investigation Findings (from Gists)
|
|
612
947
|
Phase C1: Past Review Findings on These Files
|
|
613
948
|
Phase C2: Past Bugs in the Same Module
|
|
@@ -87,6 +87,8 @@ Extract from contract:
|
|
|
87
87
|
- Deliverables table (file, change, why)
|
|
88
88
|
- Acceptance criteria
|
|
89
89
|
|
|
90
|
+
**Danger-Zone Rule Cards (BINDING CONSTRAINTS)**: When the FORGE:CONTEXT comment contains a `### Danger-Zone Rule Cards` section, each card is a **binding must-not-violate constraint** with the same authority as devdocs custom instructions. Before writing any code, read all injected cards and internalize them as explicit prohibitions. If the implementation plan requires touching a file listed in a card, the card's rule overrides the plan — choose an implementation path that satisfies the rule, or post a `needs-human` comment explaining the conflict. Do NOT silently proceed past a rule card that the implementation would violate. The quality gate (check 2G.9) will flag violations as `known-pattern-recurrence` (highest embarrassment class). <!-- Added: forge#1744 -->
|
|
91
|
+
|
|
90
92
|
---
|
|
91
93
|
|
|
92
94
|
## Phase I2: Route by Task Type
|
|
@@ -39,6 +39,34 @@ In all other cases, the gate MUST run.
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## Phase V1: Builder Self-Check — Wire-Through Proof (MANDATORY, run BEFORE quality gate loop)
|
|
43
|
+
|
|
44
|
+
<!-- Added: forge#1731 -->
|
|
45
|
+
|
|
46
|
+
Before invoking the quality gate, the builder MUST perform a self-check on newly added conditional paths. This mirrors the quality gate's 2G.8 check and allows the builder to resolve gaps before the gate invocation rather than after.
|
|
47
|
+
|
|
48
|
+
**Self-check protocol**:
|
|
49
|
+
|
|
50
|
+
1. Scan the staged diff for newly added conditional lines:
|
|
51
|
+
```bash
|
|
52
|
+
git diff HEAD -- {CHANGED_FILES} | grep -E '^\+' | grep -v '^+++' \
|
|
53
|
+
| grep -E '\bif\b|\belif\b|\belse\b|guard|feature.?flag|FEATURE_FLAG|ENABLE_|DISABLE_'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
2. For each new conditional found, confirm at least ONE of:
|
|
57
|
+
- **Test in diff**: A test function or assertion in the diff exercises this conditional branch (e.g., calls the function with parameters that trigger the `if`, asserts the guarded output, or triggers the error path deliberately)
|
|
58
|
+
- **WIRE:PROVEN annotation**: Add `# WIRE:PROVEN — <method>` immediately before or after the new conditional, describing how you verified it fires (e.g., `# WIRE:PROVEN — gate logic: condition is checked before every call; unreachable path would raise ValueError visible in tests`)
|
|
59
|
+
- **Trivial re-guard** (auto-exempt): The conditional is a null/length/type check whose body is `return`/`continue`/`break`/`pass` or a single-line assignment — defensive re-guards with no new behavior
|
|
60
|
+
|
|
61
|
+
3. If none of the above is true for a new conditional, either:
|
|
62
|
+
- Add a test or trace that exercises the path before staging
|
|
63
|
+
- Add a `# WIRE:PROVEN — <method>` annotation explaining how you verified reachability
|
|
64
|
+
- Confirm it qualifies as a trivial re-guard
|
|
65
|
+
|
|
66
|
+
**Why this matters**: Guards, flags, and validators that are never exercised are functionally dead code. This class has cost multiple sprint cycles in this pipeline (#1230, #1244, #1522, #1580). The self-check catches gaps before the quality gate fires, reducing iteration count. <!-- Added: forge#1731 -->
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
42
70
|
## Phase V1: Quality Gate Loop
|
|
43
71
|
|
|
44
72
|
**Loop protocol** — the gate MUST pass or exhaust iterations before returning:
|
|
@@ -185,6 +185,50 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CONTRACT -->
|
|
|
185
185
|
|
|
186
186
|
Contract must be grounded in the investigation report. Every deliverable file must appear in the affected files list from the investigator. Adversarially validate the proposed fix against adjacent system layers before posting.
|
|
187
187
|
|
|
188
|
+
### B2.1: Post FORGE:CLAIM on coordination issue (conditional — when running under orchestration batch) <!-- Added: forge#1736 -->
|
|
189
|
+
|
|
190
|
+
**Skip if**: `FORGE_COORD_ISSUE` is not set (agent is not running under an orchestration batch). This step is a no-op outside of `/orchestrate` dispatch — no error, no output.
|
|
191
|
+
|
|
192
|
+
**When `FORGE_COORD_ISSUE` is set**: Post a `FORGE:CLAIM` annotation on the coordination issue to advertise this agent's active resource reservation to the orchestrator and peer agents. This enables the claims-board Layer-2/4 relaxation sweep (orchestrate Step 4B) to identify issue-pairs with disjoint file sets and downgrade unnecessary serialization edges.
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
if [ -n "${FORGE_COORD_ISSUE:-}" ]; then
|
|
196
|
+
COORD_NUM=$(echo "$FORGE_COORD_ISSUE" | grep -oE '[0-9]+$')
|
|
197
|
+
if [ -n "$COORD_NUM" ]; then
|
|
198
|
+
# Extract file paths from the just-posted FORGE:CONTRACT deliverables table
|
|
199
|
+
CLAIMED_FILES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
200
|
+
--jq '[.[] | select(.body | contains("FORGE:CONTRACT"))] | last | .body' 2>/dev/null \
|
|
201
|
+
| awk '/^### Deliverables/{p=1; next} /^### /{p=0} p' \
|
|
202
|
+
| grep -oP '`[^`]+\.(py|tsx?|jsx?|sql|json|ya?ml|md|mjs|sh)`' \
|
|
203
|
+
| tr -d '`' | sort -u | tr '\n' '\n' | head -20)
|
|
204
|
+
CLAIMED_FILES="${CLAIMED_FILES:-"(files listed in FORGE:CONTRACT deliverables table)"}"
|
|
205
|
+
|
|
206
|
+
# Extract preserved interfaces from the FORGE:ARCHITECT affected paths table (if present)
|
|
207
|
+
CLAIMED_INTERFACES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
208
|
+
--jq '[.[] | select(.body | contains("FORGE:ARCHITECT"))] | last | .body' 2>/dev/null \
|
|
209
|
+
| awk '/^### Affected Paths/{p=1; next} /^### /{p=0} p' \
|
|
210
|
+
| grep -oP 'Function/Class.*\|.*\|' | head -10 || echo "(see FORGE:ARCHITECT for interface details)")
|
|
211
|
+
CLAIMED_INTERFACES="${CLAIMED_INTERFACES:-"(see FORGE:ARCHITECT comment for interface details)"}"
|
|
212
|
+
|
|
213
|
+
CLAIM_HOLDER="#${NUMBER} / $(date -u +%Y%m%dT%H%M%S)"
|
|
214
|
+
CLAIM_TTL="terminal state of Holder issue #${NUMBER}"
|
|
215
|
+
|
|
216
|
+
gh issue comment "$COORD_NUM" -R {GH_REPO} --body "<!-- FORGE:CLAIM -->
|
|
217
|
+
## Resource Claim
|
|
218
|
+
|
|
219
|
+
**Holder**: ${CLAIM_HOLDER}
|
|
220
|
+
**Files**: ${CLAIMED_FILES}
|
|
221
|
+
**Interfaces**: ${CLAIMED_INTERFACES}
|
|
222
|
+
**TTL**: ${CLAIM_TTL}
|
|
223
|
+
|
|
224
|
+
<!-- CLAIM:COMPLETE -->" 2>/dev/null || true
|
|
225
|
+
echo "FORGE:CLAIM posted on coordination issue #${COORD_NUM} for #${NUMBER}"
|
|
226
|
+
fi
|
|
227
|
+
fi
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**After posting**: Continue to Phase B2.5. The claim is now visible to the orchestrator and peer agents. The orchestrator's claims-board relaxation sweep (orchestrate Step 4B) will read this claim when determining whether serialized peers can be unblocked.
|
|
231
|
+
|
|
188
232
|
---
|
|
189
233
|
|
|
190
234
|
## Phase B2.5: Extract FUNCTION_NAMES from Contract
|