forgedock 1.13.1 → 1.14.0
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/phases.mjs +160 -18
- package/bin/engine-cli.mjs +18 -5
- package/bin/engine.mjs +109 -6
- package/bin/forgedock.mjs +4 -1
- package/commands/orchestrate/phase-3-dependency.md +5 -2
- package/commands/orchestrate/phase-4-execution.md +144 -24
- package/commands/pipeline-health/phase-2-metrics.md +5 -3
- package/commands/review-pr-staging.md +289 -39
- package/commands/review-pr.md +604 -42
- package/commands/validate.md +11 -3
- package/commands/work-on/build/architect.md +11 -8
- package/commands/work-on/build.md +27 -6
- package/commands/work-on/close.md +5 -4
- package/commands/work-on/decompose.md +7 -2
- package/commands/work-on/investigate.md +11 -0
- package/commands/work-on/remediate.md +135 -74
- package/commands/work-on/review.md +3 -3
- package/docs/CONFIG.md +14 -1
- package/docs/RELEASE-NOTES.md +8 -0
- package/package.json +1 -1
- package/scripts/architect-resume-match.test.sh +53 -0
- package/scripts/check-spec-bash.sh +73 -3
- package/scripts/check-spec-bash.test.sh +50 -0
- package/scripts/checkbox-sections.test.sh +19 -0
- package/scripts/classify-finding.sh +96 -1
- package/scripts/classify-finding.test.sh +47 -0
- package/scripts/forge-root.test.sh +48 -1
- package/scripts/orchestrate-soft-edge.test.sh +58 -0
- package/scripts/remediate-basesync.test.sh +62 -3
- package/scripts/review-delta.sh +3 -2
- package/scripts/review-delta.test.sh +1 -1
- package/scripts/review-pr-delta-wiring.test.sh +266 -0
- package/scripts/review-pr-staging-scope.test.sh +60 -0
- package/scripts/trusted-comments.test.sh +11 -0
package/commands/validate.md
CHANGED
|
@@ -184,9 +184,17 @@ if [ -n "$INPUT_ISSUE" ] && [ -n "$GH_FLAG" ]; then
|
|
|
184
184
|
esac
|
|
185
185
|
|
|
186
186
|
# Resolve and call transition-label.sh --validate
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
# Trusted-install resolution only; never the working tree / REPO_PATH (#3400/#3483, #3596)
|
|
188
|
+
_l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
|
|
189
|
+
_tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
|
|
190
|
+
for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
|
|
191
|
+
[ -n "$_cfg" ] && _tc="$_tc"$'\n'"$(find -L "$_cfg/plugins/cache/forgedock/forgedock" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | awk -F/ '$NF ~ /^[0-9]+\.[0-9]+\.[0-9]+$/{split($NF,a,".");printf "%d %d %d %s\n",a[1],a[2],a[3],$(0)}' | sort -k1,1nr -k2,2nr -k3,3nr | cut -d' ' -f4- || true)"
|
|
192
|
+
done
|
|
193
|
+
SCRIPT=""
|
|
194
|
+
while IFS= read -r _c; do
|
|
195
|
+
case "$_c" in /*) [ -z "$SCRIPT" ] && [ -f "$_c/scripts/transition-label.sh" ] && SCRIPT="$_c/scripts/transition-label.sh" ;; esac
|
|
196
|
+
done <<< "$_tc"
|
|
197
|
+
if [ -n "$SCRIPT" ]; then
|
|
190
198
|
bash "$SCRIPT" --validate "$FORGE_VERDICT" "$INPUT_ISSUE" "$GH_FLAG" || true
|
|
191
199
|
else
|
|
192
200
|
# Prose fallback: apply directly via gh
|
|
@@ -80,19 +80,22 @@ gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
|
80
80
|
|
|
81
81
|
## Resume Check
|
|
82
82
|
|
|
83
|
-
Before doing any work:
|
|
83
|
+
Before doing any work, select the architect comments with ONE anchored, trusted selector (body must start with the marker; author must be a Bot or OWNER/MEMBER/COLLABORATOR; mirrors `scripts/trusted-comments.sh`). A comment that merely mentions the marker (an INVESTIGATOR report, a CONTEXT briefing) never matches. <!-- Added: forge#3648 -->
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
gh api repos/{GH_REPO}/issues/{NUMBER}/comments
|
|
87
|
-
--jq '.[] | select(.body | contains("FORGE:ARCHITECT")) | .body'
|
|
86
|
+
ARCH=$(gh api --paginate repos/{GH_REPO}/issues/{NUMBER}/comments | jq -s '[.[][] | select((.body // "") | startswith("<!-- FORGE:ARCHITECT -->")) | select((.user.type // "") == "Bot" or ((.author_association // "") | IN("OWNER","MEMBER","COLLABORATOR"))) | {id: .id, complete: (.body | contains("<!-- FORGE:ARCHITECT:COMPLETE -->")), other: (.body | test("<!-- FORGE:(INVESTIGATOR|CONTEXT|CONTRACT|BUILDER)"))}]') || ARCH=""
|
|
88
87
|
```
|
|
89
88
|
|
|
90
|
-
- If
|
|
91
|
-
- If
|
|
89
|
+
- If `ARCH` is empty (unparsable) → fail closed: delete nothing and treat as no plan.
|
|
90
|
+
- If any element of `ARCH` has `complete == true` → plan already complete: print `ARCHITECT_RESULT:` with `status: COMPLETE` and the existing comment's `comment_url`, EXIT.
|
|
91
|
+
- If anchored trusted comments exist but none is complete → plan was interrupted. Delete the partial comment ONLY when exactly one such comment exists and it carries no other FORGE phase marker (`other == false`); zero or multiple matches delete nothing (log and continue):
|
|
92
92
|
```bash
|
|
93
|
-
COMMENT_ID=$(
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
COMMENT_ID=$(printf '%s' "$ARCH" | jq -r '[.[] | select(.complete | not)] | if length == 1 and (.[0].other | not) then .[0].id else empty end')
|
|
94
|
+
if [ -n "$COMMENT_ID" ]; then
|
|
95
|
+
gh api repos/{GH_REPO}/issues/comments/$COMMENT_ID -X DELETE
|
|
96
|
+
else
|
|
97
|
+
echo "architect resume: no single safe partial plan comment — deleting nothing"
|
|
98
|
+
fi
|
|
96
99
|
```
|
|
97
100
|
|
|
98
101
|
---
|
|
@@ -384,15 +384,18 @@ Every item in the contract's `### Out of Scope` section must carry exactly one d
|
|
|
384
384
|
```bash
|
|
385
385
|
# <Script resolution block from work-on/build.md, verbatim>
|
|
386
386
|
run() { if [ -n "${DRY_RUN:-}" ]; then echo "DRY_RUN: $*"; else "$@"; fi; }
|
|
387
|
+
# Trusted-author filter (OWNER/MEMBER/COLLABORATOR or Bot, same predicate as trusted-comments.sh) is applied at
|
|
388
|
+
# the source in the LOOKUP --jq, before the path match: an untrusted match is never reused. The REST search API is used because it exposes
|
|
389
|
+
# author_association and user.type (`gh issue list --json` has no authorAssociation field).
|
|
387
390
|
# For each deferred item (DEFERRED_PATH = the backticked path, DEFERRED_WHY = one line), repeat:
|
|
388
391
|
DEFERRED_MARKER="<!-- FORGE:DEFERRED_FROM: #{NUMBER} -->"
|
|
389
|
-
LOOKUP=$(gh
|
|
390
|
-
|
|
392
|
+
LOOKUP=$(gh api -X GET search/issues -f per_page=50 \
|
|
393
|
+
-f q="\"FORGE:DEFERRED_FROM: #{NUMBER}\" in:body repo:{GH_REPO} is:issue is:open" --jq '[.items[] | select(((.author_association // "") | IN("OWNER","MEMBER","COLLABORATOR")) or ((.user.type // "") == "Bot"))]' 2>/dev/null) || LOOKUP="FAILED"
|
|
391
394
|
if [ "$LOOKUP" = "FAILED" ]; then
|
|
392
395
|
echo "DEFERRED_LOOKUP=FAILED" # a failed lookup is not "none found": Blocked exit, never re-file blind
|
|
393
396
|
else
|
|
394
397
|
DEFERRED_NUM=$(printf '%s' "$LOOKUP" | jq -r --arg p "$DEFERRED_PATH" \
|
|
395
|
-
'[.[] | select(.body | contains($p))] | first | .number // empty' 2>/dev/null)
|
|
398
|
+
'[.[] | select((.body // "") | contains($p))] | first | .number // empty' 2>/dev/null)
|
|
396
399
|
if [ -z "$DEFERRED_NUM" ]; then
|
|
397
400
|
DEFERRED_URL=$(run gh issue create {GH_FLAG} --title "follow-up: ${DEFERRED_PATH} (deferred from #{NUMBER})" \
|
|
398
401
|
--body "## Problem
|
|
@@ -417,6 +420,7 @@ ${DEFERRED_MARKER}")
|
|
|
417
420
|
fi
|
|
418
421
|
```
|
|
419
422
|
|
|
423
|
+
- Only follow-up issues authored by a trusted author (OWNER/MEMBER/COLLABORATOR or Bot) are reused; an untrusted match carrying the marker is ignored and a fresh follow-up is filed.
|
|
420
424
|
- `DEFERRED_LOOKUP=FAILED`, or an empty `DEFERRED_NUM` after a real (non-`DRY_RUN`) create → Blocked exit with blocker `github-unavailable: could not file or look up deferred follow-up for <path>` (a transient failure follows the retry rule above; no `needs-human` for `github-unavailable:`).
|
|
421
425
|
- On resume the lookup reuses the open issue that carries the marker and the same path, so a retried build never files a duplicate.
|
|
422
426
|
- Prefer filing through the `/issue` create-hook's programmatic contract (`Skill(skill="{FORGE_SKILL_PREFIX}issue", ...)`) when it is available; the raw create above is the fallback and must keep the `FORGE:DEFERRED_FROM` marker in the body.
|
|
@@ -688,9 +692,26 @@ else run gh issue comment {NUMBER} {GH_FLAG} --body "$BODY"; fi
|
|
|
688
692
|
| `over=true`, no override, issue body has a `**Parent**: #N` line OR a trusted `FORGE:DECOMPOSED` comment exists | `OVER` | Decompose-loop guard: do NOT route to decompose. Post/refresh `FORGE:DIFF_SIZE`, then Blocked exit with blocker `diff-size-over-threshold: {diff_lines} > {threshold}; post a FORGE:SIZE_OVERRIDE with a justification to proceed` |
|
|
689
693
|
| `over=true`, no override, otherwise | `OVER` | Put a `### Split Proposal` in the `FORGE:DIFF_SIZE` comment, then print `BUILD_RESULT: status: NEEDS_DECOMPOSE` (see below) |
|
|
690
694
|
|
|
691
|
-
**`### Split Proposal`** (written into the `FORGE:DIFF_SIZE` comment): list the top files by changed lines from the `top=` output, then propose 2-4 sub-issues split along directory or phase seams, each independently buildable and no more than about half the threshold, in dependency order, one `- **{title}** — {files}` line each (this is the plan `work-on:decompose` consumes in D0/D2).
|
|
695
|
+
**`### Split Proposal`** (written into the `FORGE:DIFF_SIZE` comment): list the top files by changed lines from the `top=` output, then propose 2-4 sub-issues split along directory or phase seams, each independently buildable and no more than about half the threshold, in dependency order, one `- **{title}** — {files} — wip: {WIP_REF}@{WIP_SHA} (git checkout {WIP_SHA} -- {files})` line each (this is the plan `work-on:decompose` consumes in D0/D2). The `wip:` part comes from the persist step below; when persistence fell back to a patch file write `patch: {PATCH_PATH}` instead, and when both failed write `wip: unavailable`. Lines without the suffix stay valid.
|
|
692
696
|
|
|
693
|
-
|
|
697
|
+
**Persist the staged hunks** (before the `NEEDS_DECOMPOSE` exit; runs once the `FORGE:DIFF_SIZE` result is `OVER` and the decompose-loop guard did not fire) <!-- Added: forge#3573 -->: commit the staged index to a standalone commit object WITHOUT moving the build branch or the index, and push it to a dedicated ref named only from the issue number (never interpolate a title or file name into a ref). Re-running overwrites the same ref, so resume is idempotent. Never block `NEEDS_DECOMPOSE` on a persistence failure.
|
|
698
|
+
|
|
699
|
+
```bash
|
|
700
|
+
WIP_REF="forge/wip/{NUMBER}"
|
|
701
|
+
WIP_SHA=""; PATCH_PATH=""
|
|
702
|
+
TREE=$(git -C "{WORKTREE_PATH}" write-tree) \
|
|
703
|
+
&& WIP_SHA=$(git -C "{WORKTREE_PATH}" commit-tree "$TREE" -p HEAD -m "wip: staged hunks for #{NUMBER} (diff-size OVER)") || WIP_SHA=""
|
|
704
|
+
if [ -n "$WIP_SHA" ] && git -C "{WORKTREE_PATH}" push --force origin "${WIP_SHA}:refs/heads/${WIP_REF}" 2>/dev/null; then
|
|
705
|
+
echo "WIP_REF=${WIP_REF}@${WIP_SHA}"
|
|
706
|
+
else
|
|
707
|
+
PATCH_PATH="$(git -C "{WORKTREE_PATH}" rev-parse --git-common-dir)/forge-wip-{NUMBER}.patch"
|
|
708
|
+
git -C "{WORKTREE_PATH}" diff --cached --binary > "$PATCH_PATH" 2>/dev/null && echo "PATCH_PATH=${PATCH_PATH}" || echo "WIP=unavailable"
|
|
709
|
+
fi
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
The checkout command in each line restores that seam's files from the persisted commit (`git fetch origin {WIP_REF}` first in a fresh clone); `git diff {WIP_SHA}^ {WIP_SHA} -- {files}` shows them. Post `FORGE:DIFF_SIZE` (Step 3) after this step so the proposal carries the references.
|
|
713
|
+
|
|
714
|
+
**`NEEDS_DECOMPOSE` exit**: leave the staged worktree and branch as they are (the persisted WIP ref or patch is an additional durable copy, not a replacement); do not add `needs-human`; do not post `FORGE:BUILDER:COMPLETE`; do NOT call `work-on:decompose` here (the router is the sole dispatcher). The labels stay `workflow:building`; decompose owns the `workflow:decomposed` transition. Print:
|
|
694
715
|
|
|
695
716
|
```
|
|
696
717
|
BUILD_RESULT:
|
|
@@ -700,7 +721,7 @@ BUILD_RESULT:
|
|
|
700
721
|
blocker: diff-size-over-threshold: {diff_lines} > {threshold}; split proposal posted in FORGE:DIFF_SIZE
|
|
701
722
|
```
|
|
702
723
|
|
|
703
|
-
**Resume**: a re-invoked build re-enters here via the B0 size-gate resume branch (partial `FORGE:BUILDER` plus latest trusted `FORGE:DIFF_SIZE` `result: OVER` plus a staged diff in the kept worktree), so it does not re-run implement; this covers both exits that leave BUILDER incomplete, `NEEDS_DECOMPOSE` and the decompose-loop-guard Blocked exit. B5.5 re-measures: if still over with no override it re-emits `NEEDS_DECOMPOSE` without a duplicate comment (the loop-guard case re-emits the same Blocked exit); when a valid `FORGE:SIZE_OVERRIDE` has appeared since, it continues via `OVERRIDDEN`.
|
|
724
|
+
**Resume**: a re-invoked build re-enters here via the B0 size-gate resume branch (partial `FORGE:BUILDER` plus latest trusted `FORGE:DIFF_SIZE` `result: OVER` plus a staged diff in the kept worktree), so it does not re-run implement; this covers both exits that leave BUILDER incomplete, `NEEDS_DECOMPOSE` and the decompose-loop-guard Blocked exit. B5.5 re-measures: if still over with no override it re-pushes the same `forge/wip/{NUMBER}` ref (idempotent) and re-emits `NEEDS_DECOMPOSE` without a duplicate comment (the loop-guard case re-emits the same Blocked exit); when a valid `FORGE:SIZE_OVERRIDE` has appeared since, it continues via `OVERRIDDEN`.
|
|
704
725
|
# MUST CONTINUE to Phase B6 — the size gate result is intermediate, NOT terminal (validation still required), except the NEEDS_DECOMPOSE and Blocked exits above.
|
|
705
726
|
|
|
706
727
|
---
|
|
@@ -355,7 +355,9 @@ The `INVARIANT_ANOMALIES` variable is read in Phase C5 (Step 1) and rendered in
|
|
|
355
355
|
BODY=$(gh issue view {NUMBER} {GH_FLAG} --json body --jq '.body')
|
|
356
356
|
|
|
357
357
|
# Count remaining unchecked items BEFORE any edit
|
|
358
|
-
|
|
358
|
+
# The investigate-appended placeholder box ("Fix confirmed during investigation.") is
|
|
359
|
+
# pipeline scaffolding, not a phase item: never count it (forge#3545).
|
|
360
|
+
REMAINING_BEFORE=$(printf '%s\n' "$BODY" | grep -vE '^[-*+] \[ \] Fix confirmed during investigation\.?[[:space:]]*$' | grep -cE '^[-*+] \[ \]' || true)
|
|
359
361
|
```
|
|
360
362
|
|
|
361
363
|
**If `REMAINING_BEFORE == 0`** (no unchecked items): skip body edit — all items already checked, proceed to add PR reference only:
|
|
@@ -390,6 +392,7 @@ fi
|
|
|
390
392
|
CHECKBOX_SECTIONS=$(printf '%s\n' "$BODY_STRIPPED" | awk '
|
|
391
393
|
/^#+ / { if (in_section && has) n++; in_section=1; has=0; previous=""; next }
|
|
392
394
|
/^(=+|-+)$/ && previous != "" { if (in_section && has) n++; in_section=1; has=0; previous=""; next }
|
|
395
|
+
/^[-*+] \[[ xX]\] Fix confirmed during investigation\.?[ \t]*$/ { previous=$(0); next }
|
|
393
396
|
{ if (in_section && /^[-*+] \[[ xX]\]/) has=1; previous=$(0) }
|
|
394
397
|
END { if (in_section && has) n++; print n+0 }
|
|
395
398
|
')
|
|
@@ -1231,7 +1234,6 @@ Where:
|
|
|
1231
1234
|
# Resolve from the ForgeDock install root (FORGE_ROOT, from the script-resolution block) — never from ${0}, which is meaningless inside a skill
|
|
1232
1235
|
INDEXER_PATH=""
|
|
1233
1236
|
[ -n "${FORGE_ROOT:-}" ] && INDEXER_PATH="$FORGE_ROOT/scripts/build-knowledge-index.mjs"
|
|
1234
|
-
[ -f "$INDEXER_PATH" ] || INDEXER_PATH="$REPO_PATH/scripts/build-knowledge-index.mjs"
|
|
1235
1237
|
if node --version >/dev/null 2>&1 && [ -f "$INDEXER_PATH" ]; then
|
|
1236
1238
|
echo "[cost-prior] Re-indexing issue #${NUMBER} and regenerating cost priors..."
|
|
1237
1239
|
node "$INDEXER_PATH" --issue {NUMBER} --no-mirror 2>&1 | tail -5 \
|
|
@@ -1391,12 +1393,11 @@ LEDGER_INDEXED=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
|
1391
1393
|
|
|
1392
1394
|
### Step 2: Run incremental indexer
|
|
1393
1395
|
|
|
1394
|
-
Resolve the indexer script path from the ForgeDock install root (`FORGE_ROOT`),
|
|
1396
|
+
Resolve the indexer script path from the ForgeDock install root (`FORGE_ROOT`), never from the repository root (`REPO_PATH` holds the PR checkout):
|
|
1395
1397
|
|
|
1396
1398
|
```bash
|
|
1397
1399
|
INDEXER_PATH=""
|
|
1398
1400
|
[ -n "${FORGE_ROOT:-}" ] && INDEXER_PATH="$FORGE_ROOT/scripts/build-knowledge-index.mjs"
|
|
1399
|
-
[ -f "$INDEXER_PATH" ] || INDEXER_PATH="$REPO_PATH/scripts/build-knowledge-index.mjs"
|
|
1400
1401
|
|
|
1401
1402
|
if [ ! -f "$INDEXER_PATH" ]; then
|
|
1402
1403
|
echo "[LEDGER] scripts/build-knowledge-index.mjs not found — skipping Phase C5.3"
|
|
@@ -190,7 +190,7 @@ if [ -n "$TRUSTED_SCRIPT" ] && [ -f "$TRUSTED_SCRIPT" ]; then
|
|
|
190
190
|
else echo "SIZE_PLAN_READ=FAILED"; fi
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
-
A `FAILED` read or a comment without `result: OVER` means there is no size-gate plan (the assessment rules below then apply unchanged). Its `### Split Proposal` lines (`- **{title}** — {files}`) are the proposed sub-issues, in dependency order, for Phase D2.
|
|
193
|
+
A `FAILED` read or a comment without `result: OVER` means there is no size-gate plan (the assessment rules below then apply unchanged). Its `### Split Proposal` lines (`- **{title}** — {files}`) are the proposed sub-issues, in dependency order, for Phase D2. <!-- Added: forge#3573 --> A line may end with `— wip: {REF}@{SHA} (git checkout {SHA} -- {files})` (the durable staged hunks build B5.5 pushed to `forge/wip/{NUMBER}`), or `— patch: {PATH}` when the push failed and a patch file was written. Parse that per-seam reference as `WIP_REF_{N}` for Phase D2/D3. A line without one (or `wip: unavailable`) means no hunks were persisted; behaviour is unchanged.
|
|
194
194
|
- Milestone (from issue metadata)
|
|
195
195
|
- Priority label (P0/P1/P2) from issue labels
|
|
196
196
|
|
|
@@ -267,7 +267,7 @@ From the Decomposition Assessment in the investigation report (adjusted for any
|
|
|
267
267
|
|
|
268
268
|
For each sub-issue, prepare:
|
|
269
269
|
- **Title**: from investigation report's proposed sub-issue title (re-derived from the owner override when one was detected), including the final `{fix|feat|refactor}: ` prefix used in Phase D3
|
|
270
|
-
- **Body**: brief description of scope + `**Parent**: #{NUMBER}` + dependency note if applicable
|
|
270
|
+
- **Body**: brief description of scope + `**Parent**: #{NUMBER}` + dependency note if applicable + a `## Staged Hunks` section when the Split Proposal line carries a wip ref or patch (see D3)
|
|
271
271
|
- **Labels**: inherit priority label (P0/P1/P2) from parent; do NOT copy workflow labels
|
|
272
272
|
- **Milestone**: same milestone title as parent (if parent has one)
|
|
273
273
|
|
|
@@ -355,6 +355,11 @@ Files that need changes:
|
|
|
355
355
|
|
|
356
356
|
**Parent**: #{NUMBER}
|
|
357
357
|
{If depends on another sub-issue: "**Depends on**: #{SUB_ISSUE_N} — {reason}"}
|
|
358
|
+
|
|
359
|
+
{Only when this seam's Split Proposal line carries a wip ref or patch (D0); otherwise omit this section entirely:}
|
|
360
|
+
## Staged Hunks
|
|
361
|
+
|
|
362
|
+
The parent build's staged hunks for this seam are preserved outside the worktree: `{REF}@{SHA}`. Reuse them with `git fetch origin {REF} && git checkout {SHA} -- {files}` (wip ref), or `git apply {PATCH_PATH}` (patch file). They survive worktree cleanup.
|
|
358
363
|
SUB_BODY_EOF
|
|
359
364
|
```
|
|
360
365
|
|
|
@@ -601,6 +601,12 @@ bash {REPO_PATH}/scripts/code-index.sh query --domain {DOMAIN_LABEL} --repo-path
|
|
|
601
601
|
- **Scope cap**: if more than 25 `fix` rows result, do not grow one PR to fit them. Record the cited instance plus the rows in the same subsystem as `fix`, mark the rest `fix-deferred`, and recommend decomposition in the report (set `decompose: YES` if the remaining scope is too large for one PR).
|
|
602
602
|
- **Degrade, never block**: if any query fails (`RC` other than 0 or 1), write `Pattern sweep skipped: {reason}` in the `### Pattern Sweep` section, fall back to step 7.5, and continue. A failed query must never be recorded as 0 hits. The sweep is advisory evidence, not a gate.
|
|
603
603
|
- Every `fix` row MUST appear in `### Affected Files`, and the acceptance spec MUST include a class-wide coverage check plus a check that the added test exercises more than the reviewer's single repro (see Phase 1C).
|
|
604
|
+
7.7. **Size estimate** *(always, after the affected-file list is settled)*: estimate the changed lines the build will produce BEFORE anything is built, so an oversized issue is decomposed up front instead of discarded at the post-build size gate (build B5.5). <!-- Added: forge#3573 -->
|
|
605
|
+
- Per affected file: a new file counts about its full size; an edit counts about its hunks (added plus deleted lines). Add roughly 1:1 test lines for changed behavior. Skip lockfiles, generated files and fixtures (the gate excludes them).
|
|
606
|
+
- `INDEPENDENT_SEAMS`: the number of independently buildable pieces. For a batch issue (several review findings or checklist members) this is the member count; otherwise count distinct directory/phase seams. Minimum 1.
|
|
607
|
+
- Resolve config (defaults when absent): `THRESHOLD=$(yq '.build.diff_size.threshold // 1000' forge.yaml 2>/dev/null || echo 1000)`, `FACTOR=$(yq '.build.diff_size.estimate_factor // 1.2' forge.yaml 2>/dev/null || echo 1.2)`, `SEAM_MIN=$(yq '.build.diff_size.batch_seam_min // 3' forge.yaml 2>/dev/null || echo 3)`.
|
|
608
|
+
- Rule: when `build.diff_size.threshold` is `0` the rule is skipped (emit `GATE_THRESHOLD: 0`). Otherwise the Decomposition Assessment MUST be `**YES**` when `ESTIMATED_DIFF_LINES` > threshold x 1.2 (`estimate_factor`), OR when `INDEPENDENT_SEAMS` >= `batch_seam_min` and `ESTIMATED_DIFF_LINES` > threshold / 2 (not well under the gate). Propose one sub-issue per seam. A batch that is well under the gate (estimate at or under half the threshold) may stay `**NO**`.
|
|
609
|
+
- Emit the numbers in the comment as the three own-line fields in Phase 1C. A pure estimate is a heuristic; state it in the reason, do not pad it.
|
|
604
610
|
8. **Fix-approach validation** — if the issue proposes a fix, don't adopt it as spec. Trace through the target system's middleware, auth, routing, config. Cross-domain: if fix in domain A interacts with domain B, read domain B's files too.
|
|
605
611
|
|
|
606
612
|
---
|
|
@@ -703,6 +709,9 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:INVESTIGATOR -->
|
|
|
703
709
|
### Decomposition Assessment
|
|
704
710
|
**{YES|NO}** — {reason}
|
|
705
711
|
{if YES: proposed sub-issues with titles and dependencies}
|
|
712
|
+
ESTIMATED_DIFF_LINES: {N}
|
|
713
|
+
INDEPENDENT_SEAMS: {N}
|
|
714
|
+
GATE_THRESHOLD: {N — build.diff_size.threshold; 0 when the gate is disabled}
|
|
706
715
|
<!-- DECOMPOSE:{YES|NO} -->
|
|
707
716
|
|
|
708
717
|
### Acceptance Spec <!-- Added: forge#1829 -->
|
|
@@ -739,6 +748,8 @@ ${INVESTIGATION_SENTINEL}"
|
|
|
739
748
|
|
|
740
749
|
**Decomposition marker (MANDATORY, both output paths — template and codec)**: emit exactly one `<!-- DECOMPOSE:YES -->` or `<!-- DECOMPOSE:NO -->` line per investigator comment, matching the `**YES**`/`**NO**` verdict, on its own line (nothing else on that line) after the verdict line and any sub-issue list (the `**YES**`/`**NO**` line must stay directly under the heading — the resume parser reads it). The headless engine routes to `work-on/decompose` on this marker (or on the `**YES**` heading in older comments). Never quote or mention the opposite marker anywhere in the comment. Never emit it when Verdict=INVALID. <!-- Added: forge#3543 -->
|
|
741
750
|
|
|
751
|
+
**Size-estimate fields (MANDATORY when Verdict is not INVALID)** <!-- Added: forge#3573 -->: `ESTIMATED_DIFF_LINES: N`, `INDEPENDENT_SEAMS: N` and `GATE_THRESHOLD: N` are each alone on their own line (whole numbers, no prose or backticks on the line), INSIDE `### Decomposition Assessment` after the verdict line and sub-issue list and before the `<!-- DECOMPOSE:.. -->` marker. The engine reads them with full-line anchors as a safety net: an estimate over `GATE_THRESHOLD` x 1.2 routes to decompose even when the verdict says NO, so the verdict and the numbers must agree. Never quote these field lines elsewhere in the comment.
|
|
752
|
+
|
|
742
753
|
**Decomposition scopes the Acceptance Spec**: when the assessment is YES, the Acceptance Spec MUST cover only the first, in-scope item — or tag each check with the sub-item it belongs to (append `# item-N` to the description). Never emit checks for work the assessment assigns to a separate sub-issue: build's acceptance gate would otherwise fail on them and its repair loop would pull that work into this branch. <!-- Added: forge#3543 -->
|
|
743
754
|
|
|
744
755
|
**Do not hardcode `<!-- INVESTIGATION:COMPLETE -->` as the closing line.** The closing line MUST be the `${INVESTIGATION_SENTINEL}` variable computed above — it resolves to `<!-- INVESTIGATION:INVALID -->` for an INVALID verdict and `<!-- INVESTIGATION:COMPLETE -->` otherwise. `INVESTIGATION:COMPLETE` and `INVESTIGATION:INVALID` are mutually exclusive within a single posted comment — never emit both.
|