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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgedock",
3
- "version": "1.15.0",
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 ]