forgedock 1.21.0 → 1.23.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 +15 -3
- package/bin/engine-cli.mjs +57 -16
- package/bin/forgedock.mjs +15 -3
- package/bin/lean/effects.mjs +18 -13
- package/bin/lean/lean.mjs +145 -213
- package/bin/lean/lean.test.mjs +360 -400
- package/bin/lean/orchestrate.mjs +69 -0
- package/bin/lean/prompts.mjs +37 -44
- package/bin/lean/render.mjs +66 -0
- package/commands/orchestrate/legacy.md +88 -0
- package/commands/orchestrate.md +74 -78
- package/docs/CONFIG.md +2 -1
- package/docs/RELEASE-NOTES.md +12 -3
- package/docs/site/command-reference.md +5 -8
- package/package.json +1 -1
- package/scripts/ci-local.test.sh +2 -2
- package/scripts/review-roster.sh +14 -1
- package/scripts/review-roster.test.sh +13 -0
- package/bin/lean/risk.mjs +0 -49
package/scripts/review-roster.sh
CHANGED
|
@@ -183,7 +183,20 @@ if [ "$CHURN" = "true" ] || [ "$DANGER" = "true" ]; then
|
|
|
183
183
|
for d in $(top_domains 1); do add_agent "$d"; trig churn-or-danger-zone; done
|
|
184
184
|
fi
|
|
185
185
|
hit() { printf '%s\n' "$FILES" | grep -qiE "$1"; }
|
|
186
|
-
|
|
186
|
+
# Auth path gate (forge#4071). Docs and prose are never auth by path (the content gate still applies). Strong names match
|
|
187
|
+
# any path segment; weak names (session, login, permission, token) only as a whole word in a code file outside design-system
|
|
188
|
+
# and asset dirs, so design-tokens/, SessionTimeline.tsx, LoginButton.tsx and cursor-token files do not spawn Auth. A CamelCase
|
|
189
|
+
# weak name, leading or after a CamelCase prefix (LoginController.java, RefreshTokenService.java), still counts outside UI
|
|
190
|
+
# component files and dirs.
|
|
191
|
+
AUTH_FILES=$(printf '%s\n' "$FILES" | grep -viE '(^|/)docs?/|\.(md|mdx|markdown|rst|adoc|txt)$' || true)
|
|
192
|
+
AUTH_CODE=$(printf '%s\n' "$AUTH_FILES" | grep -iE '\.(py|pyi|ts|tsx|js|jsx|mjs|cjs|go|rb|java|kt|kts|scala|rs|php|cs|ex|exs|swift|c|cc|cpp|h|hpp|sh|bash|lua|dart|groovy|pl|vue|svelte)$' \
|
|
193
|
+
| grep -viE '(^|/)(design[-_]?(system|tokens?)|assets?|static|images?|icons?|fonts?|styles?|themes?)/' || true)
|
|
194
|
+
if printf '%s\n' "$AUTH_FILES" | grep -qiE '(^|[/_.-])(auth([^o]|o[^r]|$)|authori[sz]|oauth|jwt|passw(or)?d|rbac|acl([/_.-]|$)|api[-_]?keys?|credential|trust[-_]?filter|trusted[-_a-z]*)|(^|/)deps\.py$' \
|
|
195
|
+
|| printf '%s\n' "$AUTH_CODE" | grep -qiE '(^|[/_])(sessions?|login|logout|permissions?|tokens?)([/_.-]|$)' \
|
|
196
|
+
|| printf '%s\n' "$AUTH_CODE" | grep -viE '\.(tsx|jsx|vue|svelte)$|(^|/)(components?|pages|views|ui|widgets)/' \
|
|
197
|
+
| grep -qE '((^|[/_])([Ss]essions?|[Ll]ogin|[Ll]ogout|[Pp]ermissions?|[Tt]okens?)|[a-z0-9](Sessions?|Login|Logout|Permissions?|Tokens?))([A-Z]|[/_.-]|$)'; then
|
|
198
|
+
add_agent Auth; trig auth-path
|
|
199
|
+
fi
|
|
187
200
|
hit '^\.github/|(^|/)Dockerfile|docker-compose|(^|/)(docker-)?compose[^/]*\.ya?ml$|(^|/)deploy[^/]*\.sh$|(^|/)docker-entrypoint[^/]*$|(^|/)(deploy|infra|traefik|nginx|k8s|helm|terraform)/|\.tf$' && { add_agent Infra; trig infra-path; }
|
|
188
201
|
hit 'migration|\.sql$' && { add_agent Database; trig migration-path; }
|
|
189
202
|
hit '^sdk/|openapi.*\.json$|openapi-versions/' && { add_agent API; trig sdk-path; }
|
|
@@ -130,6 +130,19 @@ run "docs/author.md" --scores "$SC_QUIET" --changed-lines 20 --changed-files 1
|
|
|
130
130
|
expect_not "no auth path docs/author.md" auth
|
|
131
131
|
run "svc/tokenizer.py" --scores "$SC_QUIET" --changed-lines 20 --changed-files 1
|
|
132
132
|
expect_not "no-auth-path-svc/tokenizer.py" auth
|
|
133
|
+
# forge#4071: weak names (session, token, permission, login) select Auth only as whole words in code files; docs never
|
|
134
|
+
for f in design-tokens/colors.json src/SessionTimeline.tsx docs/permissions.md src/components/LoginButton.tsx src/editor/cursor-token.ts docs/auth.md \
|
|
135
|
+
src/components/LoginForm.ts svc/tokenizer.py src/JsonTokenizer.java src/components/UserSessionBadge.ts; do
|
|
136
|
+
run "$f" --scores "$SC_QUIET" --changed-lines 20 --changed-files 1
|
|
137
|
+
expect_not "no-auth-path-$f" auth
|
|
138
|
+
done
|
|
139
|
+
for f in src/auth/session.py api/tokens.py middleware/permissions.ts lib/jwt.go src/session/store.ts svc/login_handler.go \
|
|
140
|
+
src/main/java/com/x/LoginController.java src/main/java/com/x/SessionManager.java src/TokenService.kt src/PermissionChecker.cs middleware/PermissionGuard.ts \
|
|
141
|
+
src/main/java/com/x/RefreshTokenService.java src/AccessTokenValidator.kt src/main/java/com/x/UserSessionManager.java \
|
|
142
|
+
src/CsrfTokenFilter.java src/AdminPermissionChecker.cs src/OtpLoginHandler.ts src/main/java/com/x/RefreshToken.java; do
|
|
143
|
+
run "$f" --scores "$SC_QUIET" --changed-lines 20 --changed-files 1
|
|
144
|
+
expect_has "auth-path-$f" auth
|
|
145
|
+
done
|
|
133
146
|
run "commands/review-pr.md" --scores "AUTH=3 INFRA=0 DATABASE=0 BILLING=0 CONCURRENCY=0" --changed-lines 20 --changed-files 1
|
|
134
147
|
expect_not "auth-from-prose-score-alone" auth
|
|
135
148
|
run "commands/review-pr.md" --scores "AUTH=3 INFRA=3 DATABASE=0 BILLING=0 CONCURRENCY=0" --changed-lines 120
|
package/bin/lean/risk.mjs
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
// SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
2
|
-
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
-
/** Lean path (#4081): deterministic path/keyword helpers. Pure, codebase-agnostic, no model calls. */
|
|
4
|
-
|
|
5
|
-
const RISK_PATH = /(^|[/._-])(auth|authn|authz|oauth|login|session|crypto|cipher|secret|secrets|credential|credentials|token|permission|permissions|acl|rbac|migration|migrations|payment|payments|billing|stripe)([/._-]|$)|^\.github\/workflows\//i;
|
|
6
|
-
const RISK_KEYWORD = /\b(auth\w*|crypto\w*|secret\w*|permission\w*|migration\w*|payment\w*)\b/i;
|
|
7
|
-
|
|
8
|
-
/** @returns {{tier:"high"|"low", reasons:string[]}} */
|
|
9
|
-
export function riskTier(files = [], text = "") {
|
|
10
|
-
const reasons = [];
|
|
11
|
-
for (const f of files) if (RISK_PATH.test(f)) reasons.push(`path:${f}`);
|
|
12
|
-
const kw = RISK_KEYWORD.exec(String(text));
|
|
13
|
-
if (kw) reasons.push(`keyword:${kw[1].toLowerCase()}`);
|
|
14
|
-
return { tier: reasons.length ? "high" : "low", reasons };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const DOC_FILE = /(\.(md|mdx|rst|txt|adoc)$)|(^|\/)(docs?|documentation)\//i;
|
|
18
|
-
export const isDocsOnly = (files) => files.length > 0 && files.every((f) => DOC_FILE.test(f));
|
|
19
|
-
|
|
20
|
-
const TEST_FILE = /(^|\/)(tests?|__tests__|spec|specs|e2e)\/|\.(test|spec)\.[a-z0-9]+$|_(test|spec)\.[a-z0-9]+$|(^|\/)test_[^/]+\.[a-z0-9]+$|Tests?\.(java|kt|cs|swift|scala)$/i;
|
|
21
|
-
export const isTestFile = (f) => TEST_FILE.test(f);
|
|
22
|
-
|
|
23
|
-
export const LARGE_DIFF = { files: 15, lines: 400 };
|
|
24
|
-
export const isLargeDiff = ({ files = 0, lines = 0 } = {}) => files >= LARGE_DIFF.files || lines >= LARGE_DIFF.lines;
|
|
25
|
-
|
|
26
|
-
/** `auto` goes full only when risk is high AND the diff is large; lean otherwise. */
|
|
27
|
-
export function choosePath(requested, risk, stats) {
|
|
28
|
-
if (requested === "lean" || requested === "full") return requested;
|
|
29
|
-
return risk?.tier === "high" && isLargeDiff(stats) ? "full" : "lean";
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Blocking = CRITICAL/HIGH, or CONFIRMED MEDIUM in introduced code. */
|
|
33
|
-
export function isBlocking(f) {
|
|
34
|
-
const sev = String(f?.severity ?? "").toUpperCase();
|
|
35
|
-
if (sev === "CRITICAL" || sev === "HIGH") return true;
|
|
36
|
-
return sev === "MEDIUM" && String(f?.confidence ?? "").toUpperCase() === "CONFIRMED"
|
|
37
|
-
&& String(f?.scope ?? "introduced").toLowerCase() === "introduced";
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Extract the findings array from a reviewer reply (last json fence, else first [...]). null when unparseable. */
|
|
41
|
-
export function parseFindings(text) {
|
|
42
|
-
const s = String(text ?? "");
|
|
43
|
-
const fences = [...s.matchAll(/```(?:json)?\s*([\s\S]*?)```/g)].map((m) => m[1]);
|
|
44
|
-
const cands = [...fences.reverse(), s.slice(s.indexOf("["), s.lastIndexOf("]") + 1)];
|
|
45
|
-
for (const c of cands) {
|
|
46
|
-
try { const v = JSON.parse(c.trim()); if (Array.isArray(v)) return v.filter((x) => x && typeof x === "object"); } catch { /* next */ }
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|