forgedock 1.16.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.16.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"
@@ -5,17 +5,21 @@
5
5
  #
6
6
  # Usage: check-spec-size.sh [base_ref] (default: $BASE_REF, else origin/staging)
7
7
  # Env: PR_LABELS comma-separated PR label names (untrusted; grep-matched only)
8
- # PR_BODY PR body text (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)
9
12
  # GITHUB_STEP_SUMMARY summary file (stdout when unset)
10
13
  # Exit: 0 pass/exempt, 1 budget violation, 2 usage/base-ref error (fail closed)
11
14
  #
12
- # Rules (against the PR base, compared in bytes):
15
+ # Rules (against the merge-base of HEAD and the PR base, compared in bytes):
13
16
  # 1. Budgeted specs may not grow by more than 2% or more than 4096 bytes.
14
- # 2. No commands/**/*.md file may gain a canonical block copy (per-file marker
15
- # count may not rise). Count-based until the shared helper lands (#3493).
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).
16
20
  # Shrinking or equal size always passes.
17
- # Exemption: `spec-size-exempt` label, or a `<!-- spec-size-exempt: <reason> -->`
18
- # line with a non-empty reason in the PR body. Failures are reported, exit 0.
21
+ # Exemption: the `spec-size-exempt` label only (needs triage rights; a PR-body
22
+ # marker is NOT honoured). Failures are reported, exit 0.
19
23
  # Bash 3.2 portable: no assoc arrays, mapfile, grep -P.
20
24
  set -uo pipefail
21
25
 
@@ -32,15 +36,27 @@ if ! git rev-parse --verify -q "$BASE^{commit}" >/dev/null; then
32
36
  exit 2
33
37
  fi
34
38
 
35
- # --- exemption -------------------------------------------------------------
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 ---------------------------------------------------
36
57
  EXEMPT_REASON=""
37
58
  if printf '%s' "${PR_LABELS:-}" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -qxF 'spec-size-exempt'; then
38
59
  EXEMPT_REASON="label spec-size-exempt"
39
- else
40
- R=$(printf '%s\n' "${PR_BODY:-}" | tr -d '\r' \
41
- | sed -n 's/^[[:space:]]*<!--[[:space:]]*spec-size-exempt:[[:space:]]*\(.*\)-->[[:space:]]*$/\1/p' \
42
- | head -n 1 | sed 's/[[:space:]]*$//')
43
- [ -n "$R" ] && EXEMPT_REASON="PR body marker: $R"
44
60
  fi
45
61
 
46
62
  FAILURES=0
@@ -66,25 +82,29 @@ for f in $BUDGETED; do
66
82
  out "| \`$f\` | $b | $h | $g | $p | $status |"
67
83
  done
68
84
 
69
- DUP_ROWS=""
70
- # Every commands/**/*.md present at head (new files have base count 0).
71
- for f in $(git ls-files 'commands/*.md' 'commands/**/*.md' | sort -u); do
72
- [ -f "$f" ] || continue
73
- hc=$(grep -cE "$MARKERS" "$f" 2>/dev/null || true); hc=${hc:-0}
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}
74
93
  bc=$(git show "$BASE:$f" 2>/dev/null | grep -cE "$MARKERS" 2>/dev/null || true); bc=${bc:-0}
75
- if [ "$hc" -gt "$bc" ]; then
76
- DUP_ROWS="$DUP_ROWS| \`$f\` | $bc | $hc | NEW CANONICAL BLOCK COPY |
94
+ HEAD_TOTAL=$((HEAD_TOTAL + hc)); BASE_TOTAL=$((BASE_TOTAL + bc))
95
+ [ "$hc" -ne "$bc" ] && MARKER_ROWS="$MARKER_ROWS| \`$f\` | $bc | $hc |
77
96
  "
78
- FAILURES=$((FAILURES + 1))
79
- fi
80
97
  done
81
- if [ -n "$DUP_ROWS" ]; then
98
+ if [ "$HEAD_TOTAL" -gt "$BASE_TOTAL" ]; then
99
+ FAILURES=$((FAILURES + 1))
82
100
  out ""
83
- out "### Canonical block copies added (source the shared helper instead)"
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
84
104
  out ""
85
- out "| File | Base markers | Head markers | Status |"
86
- out "|------|-----:|-----:|--------|"
87
- printf '%s' "$DUP_ROWS" >> "$SUMMARY"
105
+ out "| File | Base markers | Head markers |"
106
+ out "|------|-----:|-----:|"
107
+ printf '%s' "$MARKER_ROWS" >> "$SUMMARY"
88
108
  fi
89
109
 
90
110
  out ""
@@ -97,6 +117,6 @@ if [ -n "$EXEMPT_REASON" ]; then
97
117
  echo "spec-size: $FAILURES violation(s) waived: $EXEMPT_REASON"
98
118
  exit 0
99
119
  fi
100
- out "Result: FAIL ($FAILURES violation(s)). Shrink the spec, or add the spec-size-exempt label / a \`<!-- spec-size-exempt: <reason> -->\` line to the PR body."
120
+ out "Result: FAIL ($FAILURES violation(s)). Shrink the spec, or ask a maintainer to add the spec-size-exempt label."
101
121
  echo "spec-size: FAIL ($FAILURES violation(s)); see job summary" >&2
102
122
  exit 1
@@ -18,6 +18,7 @@ gen() { awk -v n="$1" 'BEGIN{for(i=0;i<n;i++) print "line of spec text number "
18
18
  gen 4000 > commands/review-pr.md # ~110 KB: 2% ~ 2.2 KB < 4 KB cap
19
19
  gen 20000 > commands/work-on.md # ~560 KB: 2% ~ 11 KB, so the 4 KB cap binds
20
20
  gen 100 > commands/other.md
21
+ printf 'HELPER_SCRIPT=""\n' > commands/donor.md
21
22
  gen 100 > commands/work-on/remediate.md
22
23
  git add -A; git commit -qm base; git branch -M staging; git push -q origin staging 2>/dev/null
23
24
  git checkout -q -b feat
@@ -26,7 +27,7 @@ reset() { git reset -q --hard origin/staging; git clean -qfd; }
26
27
  run() { # name want_rc [env...]
27
28
  local name="$1" want="$2"; shift 2
28
29
  local sum="$TMP/summary"; : > "$sum"
29
- env GITHUB_STEP_SUMMARY="$sum" PR_LABELS="" PR_BODY="" "$@" bash "$S" origin/staging >"$TMP/out" 2>"$TMP/err"
30
+ env GITHUB_STEP_SUMMARY="$sum" PR_LABELS="" PR_HEAD_REF="" PR_BASE_REF="" "$@" bash "$S" origin/staging >"$TMP/out" 2>"$TMP/err"
30
31
  local rc=$?
31
32
  if [ "$rc" = "$want" ]; then ok; else bad "$name (rc=$rc want $want)"; cat "$TMP/err"; fi
32
33
  }
@@ -72,10 +73,52 @@ run "reduced count passes" 0
72
73
  # Exemption
73
74
  reset; gen 100 >> commands/review-pr.md
74
75
  run "exempt by label" 0 PR_LABELS="bug,spec-size-exempt"; has "label reported" "EXEMPT"
75
- run "exempt by body marker" 0 PR_BODY=$'text\n<!-- spec-size-exempt: security fix -->\nmore'; has "reason reported" "security fix"
76
- run "empty-reason marker not exempt" 1 PR_BODY='<!-- spec-size-exempt: -->'
76
+ run "body marker does not exempt" 1 PR_BODY=$'text\n<!-- spec-size-exempt: security fix -->\nmore'
77
77
  run "label substring not exempt" 1 PR_LABELS="spec-size-exempt-not"
78
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
+
79
122
  # Fail closed on unresolvable base
80
123
  : > "$TMP/summary"
81
124
  GITHUB_STEP_SUMMARY="$TMP/summary" bash "$S" origin/does-not-exist >/dev/null 2>&1; rc=$?