forgedock 1.15.0 → 1.16.1
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 +66 -15
- package/bin/engine/runlog.mjs +29 -2
- package/bin/engine.mjs +35 -4
- package/bin/runner.mjs +21 -2
- package/commands/forge-stats.md +10 -0
- package/commands/orchestrate/phase-1-resolve.md +6 -40
- package/commands/orchestrate/phase-4-execution.md +26 -162
- package/commands/quality-gate.md +12 -80
- package/commands/review-pr-staging.md +79 -303
- package/commands/review-pr.md +150 -752
- package/commands/work-on/build/validate.md +6 -40
- package/commands/work-on/build.md +34 -24
- package/commands/work-on/close.md +6 -21
- package/commands/work-on/decompose.md +5 -22
- package/commands/work-on/investigate.md +10 -24
- package/commands/work-on/remediate.md +25 -144
- package/commands/work-on/review.md +7 -41
- package/commands/work-on.md +10 -61
- package/docs/CONFIG.md +6 -5
- package/docs/RELEASE-NOTES.md +6 -0
- package/package.json +1 -1
- package/scripts/check-spec-size.sh +122 -0
- package/scripts/check-spec-size.test.sh +128 -0
- package/scripts/forge-root.test.sh +210 -91
- package/scripts/lib/fence-bootstrap.sh +16 -0
- package/scripts/lib/forge-resolve.sh +87 -0
- package/scripts/lib/review-delta-mode.sh +41 -0
- package/scripts/lint-dispatch-prompt.test.sh +3 -3
- package/scripts/review-pr-delta-wiring.test.sh +24 -7
- package/scripts/review-pr-staging-scope.test.sh +41 -6
- package/scripts/spec-no-worktree-script.test.sh +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgedock",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.1",
|
|
4
4
|
"description": "Autonomous development pipeline for coding agents. GitHub as institutional memory, with native Claude Code and OpenCode entrypoints.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"forgedock": "bin/forgedock.mjs"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# check-spec-size.sh — PR spec-size budget (forge#3762).
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
#
|
|
6
|
+
# Usage: check-spec-size.sh [base_ref] (default: $BASE_REF, else origin/staging)
|
|
7
|
+
# Env: PR_LABELS comma-separated PR label names (untrusted; grep-matched only)
|
|
8
|
+
# PR_HEAD_REF / PR_BASE_REF PR head and base branch names; staging -> main
|
|
9
|
+
# (a release PR) is skipped: cumulative growth is not judged here
|
|
10
|
+
# PR_SAME_REPO must be "true" for the release skip (a fork branch named
|
|
11
|
+
# staging must not bypass the budget)
|
|
12
|
+
# GITHUB_STEP_SUMMARY summary file (stdout when unset)
|
|
13
|
+
# Exit: 0 pass/exempt, 1 budget violation, 2 usage/base-ref error (fail closed)
|
|
14
|
+
#
|
|
15
|
+
# Rules (against the merge-base of HEAD and the PR base, compared in bytes):
|
|
16
|
+
# 1. Budgeted specs may not grow by more than 2% or more than 4096 bytes.
|
|
17
|
+
# 2. The NET count of canonical block markers across all of commands/**/*.md
|
|
18
|
+
# may not rise (moving a block between files, or replacing one with a stub,
|
|
19
|
+
# is allowed). Count-based until the shared helper lands (#3493).
|
|
20
|
+
# Shrinking or equal size always passes.
|
|
21
|
+
# Exemption: the `spec-size-exempt` label only (needs triage rights; a PR-body
|
|
22
|
+
# marker is NOT honoured). Failures are reported, exit 0.
|
|
23
|
+
# Bash 3.2 portable: no assoc arrays, mapfile, grep -P.
|
|
24
|
+
set -uo pipefail
|
|
25
|
+
|
|
26
|
+
BASE="${1:-${BASE_REF:-origin/staging}}"
|
|
27
|
+
PCT_NUM=2 # 2%
|
|
28
|
+
ABS_MAX=4096 # bytes
|
|
29
|
+
BUDGETED="commands/review-pr.md commands/review-pr-staging.md commands/work-on/remediate.md commands/work-on/review.md commands/work-on.md"
|
|
30
|
+
|
|
31
|
+
SUMMARY="${GITHUB_STEP_SUMMARY:-/dev/stdout}"
|
|
32
|
+
out() { printf '%s\n' "$*" >> "$SUMMARY"; }
|
|
33
|
+
|
|
34
|
+
if ! git rev-parse --verify -q "$BASE^{commit}" >/dev/null; then
|
|
35
|
+
echo "ERROR: base ref '$BASE' cannot be resolved (shallow checkout? need fetch-depth: 0)" >&2
|
|
36
|
+
exit 2
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# --- release PR skip (staging -> main) ---------------------------------------
|
|
40
|
+
if [ "${PR_HEAD_REF:-}" = "staging" ] && [ "${PR_BASE_REF:-}" = "main" ] && [ "${PR_SAME_REPO:-}" = "true" ]; then
|
|
41
|
+
out "## Spec-size budget"
|
|
42
|
+
out ""
|
|
43
|
+
out "Result: SKIPPED (release PR staging -> main; growth was checked per PR)"
|
|
44
|
+
echo "spec-size: skipped for release PR staging -> main"
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Compare against the merge-base, not the base tip (base moves after branching).
|
|
49
|
+
MB=$(git merge-base HEAD "$BASE" 2>/dev/null) || MB=""
|
|
50
|
+
if [ -z "$MB" ]; then
|
|
51
|
+
echo "ERROR: no merge-base between HEAD and '$BASE' (need fetch-depth: 0)" >&2
|
|
52
|
+
exit 2
|
|
53
|
+
fi
|
|
54
|
+
BASE="$MB"
|
|
55
|
+
|
|
56
|
+
# --- exemption: label only ---------------------------------------------------
|
|
57
|
+
EXEMPT_REASON=""
|
|
58
|
+
if printf '%s' "${PR_LABELS:-}" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -qxF 'spec-size-exempt'; then
|
|
59
|
+
EXEMPT_REASON="label spec-size-exempt"
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
FAILURES=0
|
|
63
|
+
base_size() { git cat-file -s "$BASE:$1" 2>/dev/null || echo 0; }
|
|
64
|
+
MARKERS='FORGE_ROOT bootstrap \(canonical|TRUSTED_SCRIPT=""|HELPER_SCRIPT=""|mkdir -p "\$_SD".*# FENCE_BOOTSTRAP'
|
|
65
|
+
|
|
66
|
+
out "## Spec-size budget (base: \`$BASE\`)"
|
|
67
|
+
out ""
|
|
68
|
+
out "| File | Base | Head | Delta | Delta % | Status |"
|
|
69
|
+
out "|------|-----:|-----:|------:|--------:|--------|"
|
|
70
|
+
for f in $BUDGETED; do
|
|
71
|
+
[ -f "$f" ] || [ "$(base_size "$f")" != 0 ] || continue
|
|
72
|
+
b=$(base_size "$f")
|
|
73
|
+
h=0; [ -f "$f" ] && h=$(wc -c < "$f" | tr -d ' ')
|
|
74
|
+
g=$((h - b))
|
|
75
|
+
status="ok"
|
|
76
|
+
if [ "$g" -gt 0 ] && { [ $((g * 100)) -gt $((b * PCT_NUM)) ] || [ "$g" -gt "$ABS_MAX" ]; }; then
|
|
77
|
+
status="OVER BUDGET"; FAILURES=$((FAILURES + 1))
|
|
78
|
+
fi
|
|
79
|
+
if [ "$b" -gt 0 ]; then
|
|
80
|
+
p=$(awk -v g="$g" -v b="$b" 'BEGIN{printf "%+.2f%%", g*100/b}')
|
|
81
|
+
else p="n/a"; fi
|
|
82
|
+
out "| \`$f\` | $b | $h | $g | $p | $status |"
|
|
83
|
+
done
|
|
84
|
+
|
|
85
|
+
# Net marker total across every commands/**/*.md: head (working tree, incl. new
|
|
86
|
+
# files) vs base (incl. files deleted at head). Fail only when the total rises.
|
|
87
|
+
HEAD_TOTAL=0; BASE_TOTAL=0; MARKER_ROWS=""
|
|
88
|
+
HEAD_FILES=$(git ls-files 'commands/*.md' | sort -u)
|
|
89
|
+
BASE_FILES=$(git ls-tree -r --name-only "$BASE" -- commands | grep -E '\.md$' | sort -u)
|
|
90
|
+
for f in $(printf '%s\n%s\n' "$HEAD_FILES" "$BASE_FILES" | sort -u); do
|
|
91
|
+
[ -n "$f" ] || continue
|
|
92
|
+
hc=0; [ -f "$f" ] && hc=$(grep -cE "$MARKERS" "$f" 2>/dev/null || true); hc=${hc:-0}
|
|
93
|
+
bc=$(git show "$BASE:$f" 2>/dev/null | grep -cE "$MARKERS" 2>/dev/null || true); bc=${bc:-0}
|
|
94
|
+
HEAD_TOTAL=$((HEAD_TOTAL + hc)); BASE_TOTAL=$((BASE_TOTAL + bc))
|
|
95
|
+
[ "$hc" -ne "$bc" ] && MARKER_ROWS="$MARKER_ROWS| \`$f\` | $bc | $hc |
|
|
96
|
+
"
|
|
97
|
+
done
|
|
98
|
+
if [ "$HEAD_TOTAL" -gt "$BASE_TOTAL" ]; then
|
|
99
|
+
FAILURES=$((FAILURES + 1))
|
|
100
|
+
out ""
|
|
101
|
+
out "### NEW CANONICAL BLOCK COPY: net marker total rose $BASE_TOTAL -> $HEAD_TOTAL (source the shared helper instead)"
|
|
102
|
+
fi
|
|
103
|
+
if [ -n "$MARKER_ROWS" ]; then
|
|
104
|
+
out ""
|
|
105
|
+
out "| File | Base markers | Head markers |"
|
|
106
|
+
out "|------|-----:|-----:|"
|
|
107
|
+
printf '%s' "$MARKER_ROWS" >> "$SUMMARY"
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
out ""
|
|
111
|
+
if [ "$FAILURES" -eq 0 ]; then
|
|
112
|
+
out "Result: PASS"
|
|
113
|
+
exit 0
|
|
114
|
+
fi
|
|
115
|
+
if [ -n "$EXEMPT_REASON" ]; then
|
|
116
|
+
out "Result: EXEMPT ($FAILURES violation(s) waived: $EXEMPT_REASON)"
|
|
117
|
+
echo "spec-size: $FAILURES violation(s) waived: $EXEMPT_REASON"
|
|
118
|
+
exit 0
|
|
119
|
+
fi
|
|
120
|
+
out "Result: FAIL ($FAILURES violation(s)). Shrink the spec, or ask a maintainer to add the spec-size-exempt label."
|
|
121
|
+
echo "spec-size: FAIL ($FAILURES violation(s)); see job summary" >&2
|
|
122
|
+
exit 1
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# check-spec-size.test.sh — cases for scripts/check-spec-size.sh (forge#3762).
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
set -uo pipefail
|
|
6
|
+
S="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/check-spec-size.sh"
|
|
7
|
+
PASS=0; FAILN=0
|
|
8
|
+
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
|
9
|
+
ok() { PASS=$((PASS+1)); }
|
|
10
|
+
bad() { FAILN=$((FAILN+1)); echo "FAIL: $1"; }
|
|
11
|
+
|
|
12
|
+
git init -q --bare "$TMP/origin.git"
|
|
13
|
+
git clone -q "$TMP/origin.git" "$TMP/w" 2>/dev/null
|
|
14
|
+
cd "$TMP/w" || exit 1
|
|
15
|
+
git config user.email t@example.com; git config user.name t
|
|
16
|
+
mkdir -p commands/work-on
|
|
17
|
+
gen() { awk -v n="$1" 'BEGIN{for(i=0;i<n;i++) print "line of spec text number " i}'; }
|
|
18
|
+
gen 4000 > commands/review-pr.md # ~110 KB: 2% ~ 2.2 KB < 4 KB cap
|
|
19
|
+
gen 20000 > commands/work-on.md # ~560 KB: 2% ~ 11 KB, so the 4 KB cap binds
|
|
20
|
+
gen 100 > commands/other.md
|
|
21
|
+
printf 'HELPER_SCRIPT=""\n' > commands/donor.md
|
|
22
|
+
gen 100 > commands/work-on/remediate.md
|
|
23
|
+
git add -A; git commit -qm base; git branch -M staging; git push -q origin staging 2>/dev/null
|
|
24
|
+
git checkout -q -b feat
|
|
25
|
+
|
|
26
|
+
reset() { git reset -q --hard origin/staging; git clean -qfd; }
|
|
27
|
+
run() { # name want_rc [env...]
|
|
28
|
+
local name="$1" want="$2"; shift 2
|
|
29
|
+
local sum="$TMP/summary"; : > "$sum"
|
|
30
|
+
env GITHUB_STEP_SUMMARY="$sum" PR_LABELS="" PR_HEAD_REF="" PR_BASE_REF="" "$@" bash "$S" origin/staging >"$TMP/out" 2>"$TMP/err"
|
|
31
|
+
local rc=$?
|
|
32
|
+
if [ "$rc" = "$want" ]; then ok; else bad "$name (rc=$rc want $want)"; cat "$TMP/err"; fi
|
|
33
|
+
}
|
|
34
|
+
has() { grep -qF "$2" "$TMP/summary" && ok || bad "$1 (summary lacks: $2)"; }
|
|
35
|
+
|
|
36
|
+
reset; run "no change passes" 0
|
|
37
|
+
has "summary shows delta table" "| File | Base | Head | Delta |"
|
|
38
|
+
|
|
39
|
+
reset; gen 100 >> commands/review-pr.md # +~2.8 KB on ~110 KB = ~2.5% (>2%, <4 KB)
|
|
40
|
+
run "over 2% fails" 1; has "over-budget flagged" "OVER BUDGET"
|
|
41
|
+
|
|
42
|
+
reset; gen 200 >> commands/work-on.md # +~5.6 KB (<2% of 560 KB, >4 KB)
|
|
43
|
+
run "over 4 KB fails" 1
|
|
44
|
+
|
|
45
|
+
reset; gen 5 >> commands/review-pr.md # small growth under both limits
|
|
46
|
+
run "small growth passes" 0
|
|
47
|
+
|
|
48
|
+
reset; gen 5000 >> commands/other.md # not a budgeted spec
|
|
49
|
+
run "non-budgeted growth passes" 0
|
|
50
|
+
|
|
51
|
+
reset; gen 100 > commands/review-pr.md # shrink
|
|
52
|
+
run "shrink passes" 0
|
|
53
|
+
|
|
54
|
+
reset; printf 'TRUSTED_SCRIPT=""\n' >> commands/other.md
|
|
55
|
+
run "new TRUSTED_SCRIPT copy fails" 1; has "dup flagged" "NEW CANONICAL BLOCK COPY"
|
|
56
|
+
|
|
57
|
+
reset; printf '_SD=x; mkdir -p "$_SD" # FENCE_BOOTSTRAP\n' >> commands/other.md
|
|
58
|
+
run "new FENCE_BOOTSTRAP anchor fails" 1
|
|
59
|
+
|
|
60
|
+
reset; printf 'echo hi # FENCE_BOOTSTRAP\n' >> commands/other.md
|
|
61
|
+
run "non-anchor FENCE_BOOTSTRAP tag passes" 0
|
|
62
|
+
|
|
63
|
+
reset; printf '# FORGE_ROOT bootstrap (canonical)\n' > commands/new-spec.md; git add -A
|
|
64
|
+
run "new spec file with block fails" 1
|
|
65
|
+
|
|
66
|
+
# Existing block, count unchanged or reduced, passes
|
|
67
|
+
reset; printf 'HELPER_SCRIPT=""\n' >> commands/other.md; git commit -qam x; git push -q origin HEAD:staging 2>/dev/null
|
|
68
|
+
git fetch -q origin staging
|
|
69
|
+
run "unchanged count passes" 0
|
|
70
|
+
: > commands/other.md; git add -A
|
|
71
|
+
run "reduced count passes" 0
|
|
72
|
+
|
|
73
|
+
# Exemption
|
|
74
|
+
reset; gen 100 >> commands/review-pr.md
|
|
75
|
+
run "exempt by label" 0 PR_LABELS="bug,spec-size-exempt"; has "label reported" "EXEMPT"
|
|
76
|
+
run "body marker does not exempt" 1 PR_BODY=$'text\n<!-- spec-size-exempt: security fix -->\nmore'
|
|
77
|
+
run "label substring not exempt" 1 PR_LABELS="spec-size-exempt-not"
|
|
78
|
+
|
|
79
|
+
# Net counting: moves and stubs pass (the #3493 / #3590 shape)
|
|
80
|
+
reset; : > commands/donor.md; printf 'HELPER_SCRIPT=""\n' >> commands/other.md
|
|
81
|
+
run "net move between files passes" 0
|
|
82
|
+
reset; : > commands/donor.md; printf 'HELPER_SCRIPT=""\n' > commands/new-spec.md; git add -A
|
|
83
|
+
run "move to a new file passes" 0
|
|
84
|
+
reset; git rm -q commands/donor.md; gen 5 > commands/review-pr.md; printf 'HELPER_SCRIPT=""\n' > commands/helper-stub.md; git add -A
|
|
85
|
+
run "stub replaces deleted block passes" 0
|
|
86
|
+
reset; printf 'HELPER_SCRIPT=""\n' >> commands/other.md
|
|
87
|
+
run "copy without removal still fails" 1
|
|
88
|
+
|
|
89
|
+
# Shared-helper stubs (forge#3778): the sourced-helper stub shape carries none of the markers, so converting an inlined block to a stub
|
|
90
|
+
# is a net reduction, while re-pasting the full block still fails.
|
|
91
|
+
STUB_LOC='type forge_resolve >/dev/null 2>&1 || for _r in "${FORGEDOCK_HOME:-}"; do . "$_r/scripts/lib/forge-resolve.sh"; done'
|
|
92
|
+
reset; printf '%s\n' "$STUB_LOC" '. "$(forge_resolve lib/fence-bootstrap.sh)" || exit 1 # FENCE_BOOTSTRAP' 'HELPER_SCRIPT=$(forge_resolve x.sh)' >> commands/other.md
|
|
93
|
+
run "one-tag-per-stub design passes" 0
|
|
94
|
+
reset; : > commands/donor.md; printf '%s\n' "$STUB_LOC" 'HELPER_SCRIPT=$(forge_resolve x.sh)' >> commands/other.md
|
|
95
|
+
run "block converted to a stub passes" 0
|
|
96
|
+
reset; printf '%s\n' "$STUB_LOC" 'HELPER_SCRIPT=""' >> commands/other.md
|
|
97
|
+
run "stub plus a re-pasted resolver block fails" 1
|
|
98
|
+
|
|
99
|
+
# Release PR skip (staging -> main only)
|
|
100
|
+
reset; gen 100 >> commands/review-pr.md
|
|
101
|
+
run "release PR skipped" 0 PR_HEAD_REF=staging PR_BASE_REF=main PR_SAME_REPO=true; has "skip reported" "SKIPPED"
|
|
102
|
+
run "fork branch named staging still checked" 1 PR_HEAD_REF=staging PR_BASE_REF=main PR_SAME_REPO=false
|
|
103
|
+
run "release PR without same-repo flag still checked" 1 PR_HEAD_REF=staging PR_BASE_REF=main
|
|
104
|
+
run "hotfix to main still checked" 1 PR_HEAD_REF=hotfix PR_BASE_REF=main
|
|
105
|
+
run "staging to other base still checked" 1 PR_HEAD_REF=staging PR_BASE_REF=dev
|
|
106
|
+
|
|
107
|
+
# Merge-base: base advancing after branching must not count against the PR
|
|
108
|
+
reset; git checkout -q -b old-branch; git checkout -q staging 2>/dev/null || git checkout -q -B staging origin/staging
|
|
109
|
+
gen 300 >> commands/review-pr.md; git commit -qam "base grows"; git push -q origin HEAD:staging 2>/dev/null
|
|
110
|
+
git checkout -q old-branch; git fetch -q origin staging
|
|
111
|
+
run "base tip growth ignored (merge-base)" 0
|
|
112
|
+
git checkout -q feat; git branch -qD old-branch; git branch -qf staging origin/staging; reset
|
|
113
|
+
|
|
114
|
+
# Script extracted from the base ref behaves the same
|
|
115
|
+
git show origin/staging:scripts/check-spec-size.sh > "$TMP/base-script.sh" 2>/dev/null || cp "$S" "$TMP/base-script.sh"
|
|
116
|
+
gen 100 >> commands/review-pr.md
|
|
117
|
+
: > "$TMP/summary"
|
|
118
|
+
GITHUB_STEP_SUMMARY="$TMP/summary" PR_LABELS="" bash "$TMP/base-script.sh" origin/staging >/dev/null 2>&1; rc=$?
|
|
119
|
+
[ "$rc" = 1 ] && ok || bad "extracted base-ref script fails over-budget change (rc=$rc)"
|
|
120
|
+
reset
|
|
121
|
+
|
|
122
|
+
# Fail closed on unresolvable base
|
|
123
|
+
: > "$TMP/summary"
|
|
124
|
+
GITHUB_STEP_SUMMARY="$TMP/summary" bash "$S" origin/does-not-exist >/dev/null 2>&1; rc=$?
|
|
125
|
+
[ "$rc" = 2 ] && ok || bad "unresolved base fails closed (rc=$rc)"
|
|
126
|
+
|
|
127
|
+
echo "check-spec-size.test: pass=$PASS fail=$FAILN"
|
|
128
|
+
[ "$FAILN" -eq 0 ]
|