forgedock 1.27.0 → 1.29.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/README.md +4 -1
- package/bin/bootstrap.mjs +55 -0
- package/bin/engine/outcome-codes.mjs +39 -0
- package/bin/engine/telemetry-seam.mjs +37 -8
- package/bin/engine/telemetry-seam.test.mjs +31 -0
- package/bin/engine-cli.mjs +601 -42
- package/bin/engine.mjs +8 -4
- package/bin/forgedock.mjs +75 -9
- package/bin/gc.mjs +580 -0
- package/bin/lean/admission.mjs +33 -3
- package/bin/lean/batch.mjs +22 -4
- package/bin/lean/batch.test.mjs +5 -3
- package/bin/lean/checks.mjs +199 -0
- package/bin/lean/checks.test.mjs +183 -0
- package/bin/lean/conformance.mjs +234 -57
- package/bin/lean/conformance.test.mjs +168 -7
- package/bin/lean/context.mjs +333 -0
- package/bin/lean/context.test.mjs +279 -0
- package/bin/lean/credentials.mjs +29 -0
- package/bin/lean/depcache.mjs +312 -0
- package/bin/lean/depcache.test.mjs +298 -0
- package/bin/lean/drift.mjs +198 -0
- package/bin/lean/drift.test.mjs +223 -0
- package/bin/lean/effects.mjs +104 -9
- package/bin/lean/execution.e2e.test.mjs +81 -0
- package/bin/lean/execution.mjs +431 -0
- package/bin/lean/execution.test.mjs +583 -0
- package/bin/lean/heavy.mjs +289 -0
- package/bin/lean/heavy.test.mjs +207 -0
- package/bin/lean/lean.mjs +723 -154
- package/bin/lean/lean.test.mjs +1603 -35
- package/bin/lean/ledger.mjs +27 -2
- package/bin/lean/ledger.test.mjs +36 -1
- package/bin/lean/limits.mjs +58 -0
- package/bin/lean/limits.test.mjs +150 -0
- package/bin/lean/orchestrate.mjs +389 -19
- package/bin/lean/prefilter.mjs +2 -2
- package/bin/lean/prefilter.test.mjs +3 -0
- package/bin/lean/prompts.mjs +106 -17
- package/bin/lean/recovery.test.mjs +230 -0
- package/bin/lean/refactor.mjs +276 -0
- package/bin/lean/refactor.test.mjs +359 -0
- package/bin/lean/render.mjs +49 -10
- package/bin/lean/review-trigger.mjs +134 -0
- package/bin/lean/review-trigger.test.mjs +104 -0
- package/bin/lean/sandbox-provider.contract.test.mjs +257 -0
- package/bin/lean/sandbox-remote.mjs +142 -0
- package/bin/lean/sched.mjs +221 -0
- package/bin/lean/sched.test.mjs +310 -0
- package/bin/lean/schedd.mjs +247 -0
- package/bin/lean/schedd.test.mjs +180 -0
- package/bin/lean/testmap.mjs +84 -0
- package/bin/lean/testmap.test.mjs +58 -0
- package/bin/lean/toolchain.mjs +228 -0
- package/bin/lean/toolchain.test.mjs +154 -0
- package/bin/lean/verify.mjs +66 -10
- package/bin/lean/verify.test.mjs +104 -3
- package/bin/lean/worktree.mjs +69 -0
- package/bin/lean/worktree.test.mjs +141 -0
- package/bin/runner.mjs +53 -10
- package/bin/stats/github-source.mjs +1 -1
- package/bin/stats/index.mjs +24 -2
- package/bin/stats/local-source.mjs +13 -0
- package/bin/stats/query.mjs +99 -0
- package/bin/stats/stats.test.mjs +94 -1
- package/commands/audit.md +2 -0
- package/commands/autopilot.md +2 -0
- package/commands/bootstrap.md +35 -0
- package/commands/cleanup.md +7 -22
- package/commands/forge-stats.md +2 -1
- package/commands/forgedock-init.md +7 -0
- package/commands/issue.md +89 -830
- package/commands/milestone.md +55 -6
- package/commands/orchestrate.md +62 -8
- package/commands/upgrade-deps.md +3 -3
- package/commands/work-on/build/validate.md +4 -0
- package/commands/work-on/close.md +2 -0
- package/commands/work-on/decompose.md +24 -6
- package/docs/CI.md +3 -3
- package/docs/CONFIG.md +192 -2
- package/docs/OUTCOME-CODES.md +22 -0
- package/docs/RELEASE-NOTES.md +9 -0
- package/docs/TELEMETRY.md +1 -1
- package/docs/site/command-reference.md +22 -0
- package/docs/site/getting-started.md +2 -0
- package/docs/site/start-a-new-project.md +327 -0
- package/package.json +1 -1
- package/scripts/affected-files-check.sh +91 -0
- package/scripts/affected-files-check.test.sh +80 -0
- package/scripts/bootstrap-create.mjs +393 -0
- package/scripts/bootstrap-validate.mjs +36 -2
- package/scripts/calibration.mjs +116 -0
- package/scripts/check-devdocs-freshness.sh +9 -4
- package/scripts/check-devdocs-freshness.test.sh +11 -1
- package/scripts/check-spec-markers.sh +4 -0
- package/scripts/check-spec-size.sh +29 -11
- package/scripts/check-spec-size.test.sh +27 -4
- package/scripts/ci-local/lib.mjs +8 -1
- package/scripts/ci-local/parallelism.test.mjs +27 -0
- package/scripts/ci-local/run.mjs +3 -3
- package/scripts/ci-local/scoping.mjs +82 -0
- package/scripts/ci-local.test.sh +1 -1
- package/scripts/fixtures/symbol-index/other/sample.rb +3 -0
- package/scripts/fixtures/symbol-index/py/pkg/__init__.py +1 -0
- package/scripts/fixtures/symbol-index/py/pkg/a.py +5 -0
- package/scripts/fixtures/symbol-index/py/pkg/b.py +14 -0
- package/scripts/fixtures/symbol-index/py/pkg/c.py +1 -0
- package/scripts/fixtures/symbol-index/py/sample.py +41 -0
- package/scripts/fixtures/symbol-index/ts/sample.ts +13 -0
- package/scripts/fixtures/symbol-move/gen_big.py +201 -0
- package/scripts/fixtures/symbol-move-ts/gen_big.mjs +56 -0
- package/scripts/fixtures/test-map/go/.github/workflows/ci.yml +23 -0
- package/scripts/fixtures/test-map/go/Makefile +5 -0
- package/scripts/fixtures/test-map/go/README.md +1 -0
- package/scripts/fixtures/test-map/go/go.mod +3 -0
- package/scripts/fixtures/test-map/go/internal/util/util.go +3 -0
- package/scripts/fixtures/test-map/go/main.go +3 -0
- package/scripts/fixtures/test-map/node/package.json +10 -0
- package/scripts/fixtures/test-map/node/packages/ui/package.json +4 -0
- package/scripts/fixtures/test-map/node/packages/ui/src/button.tsx +1 -0
- package/scripts/fixtures/test-map/node/pnpm-lock.yaml +1 -0
- package/scripts/fixtures/test-map/node/src/a.ts +1 -0
- package/scripts/fixtures/test-map/node/vitest.config.ts +1 -0
- package/scripts/fixtures/test-map/python/pkg/__init__.py +0 -0
- package/scripts/fixtures/test-map/python/pkg/mod.py +2 -0
- package/scripts/fixtures/test-map/python/pyproject.toml +9 -0
- package/scripts/fixtures/test-map/python/tests/test_mod.py +5 -0
- package/scripts/fixtures/test-map/python/uv.lock +1 -0
- package/scripts/forge-root.test.sh +1 -1
- package/scripts/hotspots.sh +100 -0
- package/scripts/hotspots.test.sh +101 -0
- package/scripts/issue-create.sh +197 -0
- package/scripts/issue-create.test.sh +166 -0
- package/scripts/maintenance-scan.sh +185 -0
- package/scripts/maintenance-scan.test.sh +89 -0
- package/scripts/milestone-drift.sh +206 -0
- package/scripts/milestone-drift.test.sh +120 -0
- package/scripts/refactor-verify.sh +368 -0
- package/scripts/refactor-verify.test.sh +202 -0
- package/scripts/repo-facts.sh +146 -0
- package/scripts/repo-facts.test.sh +106 -0
- package/scripts/structure-delta.sh +10 -5
- package/scripts/structure-delta.test.sh +3 -2
- package/scripts/symbol-index.mjs +253 -0
- package/scripts/symbol-index.py +354 -0
- package/scripts/symbol-index.test.sh +119 -0
- package/scripts/symbol-move-ts.test.sh +161 -0
- package/scripts/symbol-move.mjs +379 -0
- package/scripts/symbol-move.py +383 -0
- package/scripts/symbol-move.test.sh +168 -0
- package/scripts/test-map.sh +211 -0
- package/scripts/test-map.test.sh +165 -0
- package/scripts/wait-ci-green.sh +10 -4
- package/scripts/wait-ci-green.test.sh +9 -0
- package/templates/bootstrap/interview.md +34 -0
- package/templates/bootstrap/issue-body.md +42 -0
- package/templates/bootstrap/needs-human-issue.md +45 -0
- package/templates/bootstrap/stack-profiles.md +34 -0
- package/templates/bootstrap/technical-spike.md +75 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
#
|
|
5
|
+
# test-map.sh — deterministic `file glob → check command` map for a repo (forge#4272). No LLM, no network, runs none of
|
|
6
|
+
# the repo's commands: git + jq + awk only (no yq). Codebase-agnostic: node, python, go, make and CI workflow steps.
|
|
7
|
+
#
|
|
8
|
+
# Usage: test-map.sh [--repo-path DIR] [--output FILE | --stdout]
|
|
9
|
+
# default output <repo>/.forge/test-map.json, rewritten on every run (a regenerable cache; deleting it restores the
|
|
10
|
+
# pre-PR step's `learned.test_commands` behaviour).
|
|
11
|
+
# Output: {"version":1,"entries":[{"globs":[...],"command","cwd","source"}]}, sorted by source then command; entries
|
|
12
|
+
# with the same (command, cwd) are merged (globs unioned, first source kept).
|
|
13
|
+
# Sources:
|
|
14
|
+
# package.json (root + every tracked one outside node_modules): scripts test/lint/typecheck/type-check/check →
|
|
15
|
+
# `<pm> run <script>` in the package dir (pm from the root lockfile), JS/TS globs under that dir
|
|
16
|
+
# pytest (pyproject [tool.pytest*], pytest.ini, setup.cfg [tool:pytest], conftest.py) and ruff config → `pytest`,
|
|
17
|
+
# `ruff check .` (through `uv run` / `poetry run` when that lockfile exists), `**/*.py`
|
|
18
|
+
# go.mod (dir D) → `go test ./...`, `go vet ./...` in D, `D/**/*.go`, `D/go.mod`, `D/go.sum`
|
|
19
|
+
# Makefile / GNUmakefile targets test/lint/check/typecheck/vet → `make <target>`, `**`
|
|
20
|
+
# .github/workflows/*.yml|*.yaml `run:` steps (one command per line of a `|` block, a `>` block is one command;
|
|
21
|
+
# install/setup lines skipped): globs
|
|
22
|
+
# from the tool (python/go/shell/js), else the workflow's `paths:` filter, else `**`. A step whose job id, job
|
|
23
|
+
# name, step name or command matches deploy/publish/release/push/secret is never mapped (forge#4297): the engine
|
|
24
|
+
# runs these commands before a PR, so only check steps belong here.
|
|
25
|
+
# Exit: 0 ok | 2 usage or bad repo path. bash 3.2 portable.
|
|
26
|
+
set -uo pipefail
|
|
27
|
+
|
|
28
|
+
die() { echo "test-map: $*" >&2; exit 2; }
|
|
29
|
+
command -v jq >/dev/null 2>&1 || die "jq is required"
|
|
30
|
+
REPO="."; OUT=""; STDOUT=0
|
|
31
|
+
while [ $# -gt 0 ]; do
|
|
32
|
+
case "$1" in
|
|
33
|
+
--repo-path) [ $# -ge 2 ] && [ -n "$2" ] || die "usage: test-map.sh [--repo-path DIR] [--output FILE | --stdout]"; REPO="$2"; shift 2 ;;
|
|
34
|
+
--output) [ $# -ge 2 ] && [ -n "$2" ] || die "usage: test-map.sh [--repo-path DIR] [--output FILE | --stdout]"; OUT="$2"; shift 2 ;;
|
|
35
|
+
--stdout) STDOUT=1; shift ;;
|
|
36
|
+
*) die "unknown option: $1" ;;
|
|
37
|
+
esac
|
|
38
|
+
done
|
|
39
|
+
[ -d "$REPO" ] || die "repo path not found: $REPO"
|
|
40
|
+
case "$OUT" in ""|/*) ;; *) OUT="$PWD/$OUT" ;; esac
|
|
41
|
+
cd "$REPO" || die "cannot enter $REPO"
|
|
42
|
+
|
|
43
|
+
T=$(mktemp -d "${TMPDIR:-/tmp}/test-map.XXXXXX") || die "mktemp failed"
|
|
44
|
+
trap 'rm -rf "$T"' EXIT
|
|
45
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
46
|
+
# -z and quotepath=off (#4298): non-ASCII and spaced paths come out verbatim, never C-quoted
|
|
47
|
+
git -c core.quotepath=off ls-files -z --cached --others --exclude-standard | tr '\0' '\n' > "$T/files"
|
|
48
|
+
else
|
|
49
|
+
find . -type f -not -path './.git/*' | sed 's|^\./||' | sort > "$T/files"
|
|
50
|
+
fi
|
|
51
|
+
: > "$T/rows"
|
|
52
|
+
# row: source TAB cwd TAB command TAB globs separated by US (\037, #4298: a glob may contain spaces)
|
|
53
|
+
US="$(printf '\037')"
|
|
54
|
+
row() { printf '%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$4" >> "$T/rows"; }
|
|
55
|
+
JS_EXT="js jsx ts tsx mjs cjs"
|
|
56
|
+
# globs for the extensions in $2 under dir $1 ("." = repo root)
|
|
57
|
+
ext_globs() {
|
|
58
|
+
g=""; pre=""; [ "$1" = "." ] || pre="$1/"
|
|
59
|
+
for e in $2; do g="$g$US${pre}**/*.$e"; done
|
|
60
|
+
printf '%s' "${g#"$US"}"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# ---- package.json scripts ----
|
|
64
|
+
PM=npm
|
|
65
|
+
[ -f yarn.lock ] && PM=yarn
|
|
66
|
+
[ -f pnpm-lock.yaml ] && PM=pnpm
|
|
67
|
+
grep -E '(^|/)package\.json$' "$T/files" | grep -v -E '(^|/)node_modules/' | sort | while IFS= read -r pj; do
|
|
68
|
+
[ -f "$pj" ] || continue
|
|
69
|
+
d=$(dirname "$pj")
|
|
70
|
+
globs="$(ext_globs "$d" "$JS_EXT")$US$pj"
|
|
71
|
+
tag=""
|
|
72
|
+
if [ -n "$(ls "$d"/vitest.config.* "$d"/jest.config.* 2>/dev/null)" ] || jq -e 'has("jest")' "$pj" >/dev/null 2>&1; then tag=" (vitest/jest config)"; fi
|
|
73
|
+
jq -r '(.scripts // {}) | to_entries[] | select(.value | type == "string") | .key' "$pj" 2>/dev/null | while IFS= read -r s; do
|
|
74
|
+
case "$s" in
|
|
75
|
+
test) row "$pj#scripts.$s$tag" "$d" "$PM run $s" "$globs" ;;
|
|
76
|
+
lint|typecheck|type-check|check) row "$pj#scripts.$s" "$d" "$PM run $s" "$globs" ;;
|
|
77
|
+
esac
|
|
78
|
+
done
|
|
79
|
+
done
|
|
80
|
+
|
|
81
|
+
# ---- python ----
|
|
82
|
+
RUN=""
|
|
83
|
+
[ -f poetry.lock ] && RUN="poetry run "
|
|
84
|
+
[ -f uv.lock ] && RUN="uv run "
|
|
85
|
+
PY_SRC=""
|
|
86
|
+
if [ -f pyproject.toml ] && grep -qE '^\[tool\.pytest' pyproject.toml; then PY_SRC="pyproject.toml#tool.pytest"
|
|
87
|
+
elif [ -f pytest.ini ]; then PY_SRC="pytest.ini"
|
|
88
|
+
elif [ -f setup.cfg ] && grep -qE '^\[tool:pytest\]' setup.cfg; then PY_SRC="setup.cfg#tool:pytest"
|
|
89
|
+
elif grep -qE '(^|/)conftest\.py$' "$T/files"; then PY_SRC="conftest.py"; fi
|
|
90
|
+
[ -n "$PY_SRC" ] && row "$PY_SRC" "." "${RUN}pytest" "**/*.py$US**/conftest.py"
|
|
91
|
+
RUFF_SRC=""
|
|
92
|
+
if [ -f pyproject.toml ] && grep -qE '^\[tool\.ruff' pyproject.toml; then RUFF_SRC="pyproject.toml#tool.ruff"
|
|
93
|
+
elif [ -f ruff.toml ]; then RUFF_SRC="ruff.toml"
|
|
94
|
+
elif [ -f .ruff.toml ]; then RUFF_SRC=".ruff.toml"; fi
|
|
95
|
+
[ -n "$RUFF_SRC" ] && row "$RUFF_SRC" "." "${RUN}ruff check ." "**/*.py"
|
|
96
|
+
|
|
97
|
+
# ---- go ----
|
|
98
|
+
grep -E '(^|/)go\.mod$' "$T/files" | grep -v -E '(^|/)vendor/' | sort | while IFS= read -r gm; do
|
|
99
|
+
d=$(dirname "$gm"); pre=""; [ "$d" = "." ] || pre="$d/"
|
|
100
|
+
globs="${pre}**/*.go$US${pre}go.mod$US${pre}go.sum"
|
|
101
|
+
row "$gm" "$d" "go test ./..." "$globs"
|
|
102
|
+
row "$gm" "$d" "go vet ./..." "$globs"
|
|
103
|
+
done
|
|
104
|
+
|
|
105
|
+
# ---- Makefile ----
|
|
106
|
+
for mf in Makefile GNUmakefile; do
|
|
107
|
+
[ -f "$mf" ] || continue
|
|
108
|
+
grep -oE '^(test|lint|check|typecheck|vet)[[:space:]]*:' "$mf" | sed -E 's/[[:space:]]*:$//' | sort -u | while IFS= read -r t; do
|
|
109
|
+
row "$mf#$t" "." "make $t" "**"
|
|
110
|
+
done
|
|
111
|
+
done
|
|
112
|
+
|
|
113
|
+
# ---- CI workflow run: steps ----
|
|
114
|
+
# Emits: job US jobname US stepname US command (US = \037, so empty names survive `read`), one per command line; and P TAB path for each `paths:` filter item
|
|
115
|
+
# before `jobs:`. A block ends at the first non-blank line not indented past the `run` key (sibling keys such as env:).
|
|
116
|
+
wf_extract() {
|
|
117
|
+
awk '
|
|
118
|
+
function indent(s) { match(s, /^ */); return RLENGTH }
|
|
119
|
+
function unq(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); if (s ~ /^".*"$/ || s ~ /^\x27.*\x27$/) s = substr(s, 2, length(s) - 2); return s }
|
|
120
|
+
function emit(c) { c = unq(c); if (c != "") print job "\037" jname "\037" sname "\037" c }
|
|
121
|
+
BEGIN { injobs = 0; jind = -1; block = 0; inpaths = 0; job = ""; jname = ""; sname = "" }
|
|
122
|
+
{
|
|
123
|
+
line = $0; sub(/\r$/, "", line)
|
|
124
|
+
if (block) {
|
|
125
|
+
if (line ~ /^[ \t]*$/) next
|
|
126
|
+
if (indent(line) > bind) {
|
|
127
|
+
l = line; sub(/^ +/, "", l)
|
|
128
|
+
if (fold) { pend = (pend == "" ? l : pend " " l); next }
|
|
129
|
+
if (pend != "") { l = pend " " l; pend = "" }
|
|
130
|
+
if (l ~ /\\$/) { pend = substr(l, 1, length(l) - 1); sub(/[ \t]+$/, "", pend); next }
|
|
131
|
+
emit(l); next
|
|
132
|
+
}
|
|
133
|
+
if (pend != "") { emit(pend); pend = "" }
|
|
134
|
+
block = 0
|
|
135
|
+
}
|
|
136
|
+
if (line ~ /^jobs:[ \t]*$/) { injobs = 1; inpaths = 0; next }
|
|
137
|
+
if (!injobs) {
|
|
138
|
+
if (line ~ /^[ \t]*paths:[ \t]*$/) { inpaths = 1; pind = indent(line); next }
|
|
139
|
+
if (inpaths) {
|
|
140
|
+
if (line ~ /^[ \t]*-[ \t]/ && indent(line) >= pind) { p = line; sub(/^[ \t]*-[ \t]*/, "", p); sub(/[ \t]+#.*$/, "", p); print "P\t" unq(p); next }
|
|
141
|
+
if (line !~ /^[ \t]*$/ && line !~ /^[ \t]*#/) inpaths = 0
|
|
142
|
+
}
|
|
143
|
+
next
|
|
144
|
+
}
|
|
145
|
+
if (line ~ /^[^ \t#]/) { injobs = 0; next }
|
|
146
|
+
if (line ~ /^ +[A-Za-z0-9_-]+:[ \t]*$/ && (jind < 0 || indent(line) == jind)) { jind = indent(line); job = line; sub(/^ +/, "", job); sub(/:.*$/, "", job); jname = ""; sname = ""; next }
|
|
147
|
+
if (line ~ /^[ \t]*-[ \t]/) sname = ""
|
|
148
|
+
if (line ~ /^[ \t]*(-[ \t]+)?name:/) {
|
|
149
|
+
nm = line; sub(/^[ \t]*(-[ \t]+)?name:[ \t]*/, "", nm); sub(/[ \t]+#.*$/, "", nm)
|
|
150
|
+
if (line !~ /^[ \t]*-[ \t]/ && indent(line) == jind + 2) jname = unq(nm); else sname = unq(nm)
|
|
151
|
+
next
|
|
152
|
+
}
|
|
153
|
+
if (line ~ /^[ \t]*(-[ \t]+)?run:/) {
|
|
154
|
+
rest = line; sub(/^[ \t]*(-[ \t]+)?run:[ \t]*/, "", rest); sub(/[ \t]+#.*$/, "", rest)
|
|
155
|
+
# bind = the column of the `run` key: step keys after it (env:, shell:) sit there and end the block
|
|
156
|
+
if (rest ~ /^[|>][-+0-9]*$/) { block = 1; pend = ""; fold = (rest ~ /^>/); bind = match(line, /run:/) - 1; next }
|
|
157
|
+
emit(rest)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
END { if (block && pend != "") emit(pend) }
|
|
161
|
+
' "$1"
|
|
162
|
+
}
|
|
163
|
+
skip_cmd() {
|
|
164
|
+
case "$1" in
|
|
165
|
+
\#*|echo\ *|echo|cd\ *|export\ *|set\ *|if\ *|then|fi|else|for\ *|do|done|\}*|\{*|*'$GITHUB_'*|*'>> $GITHUB'*) return 0 ;;
|
|
166
|
+
npm\ ci*|npm\ install*|npm\ i|npm\ i\ *|pnpm\ install*|pnpm\ i|yarn|yarn\ install*|pip\ install*|pip3\ install*|python*\ -m\ pip\ install*|uv\ sync*|uv\ pip\ install*|poetry\ install*|go\ mod\ download*|apt-get*|sudo\ *|corepack\ *|brew\ *) return 0 ;;
|
|
167
|
+
esac
|
|
168
|
+
return 1
|
|
169
|
+
}
|
|
170
|
+
# forge#4297: deploy, publish, release, push and secret-using steps are never check commands
|
|
171
|
+
deploy_step() {
|
|
172
|
+
grep -qiE 'deploy|publish|release|push|secret' <<<"$*"
|
|
173
|
+
}
|
|
174
|
+
tool_globs() {
|
|
175
|
+
c=" $1 "
|
|
176
|
+
case "$c" in
|
|
177
|
+
*[\ /]ruff\ *|*[\ /]flake8\ *|*[\ /]pylint\ *|*[\ /]mypy\ *|*[\ /]black\ *|*[\ /]pytest\ *) printf '**/*.py'; return ;;
|
|
178
|
+
*[\ /]golangci-lint\ *|*[\ /]gofmt\ *|*[\ /]staticcheck\ *|*\ go\ *) printf '**/*.go'; return ;;
|
|
179
|
+
*[\ /]shellcheck\ *|*\ bash\ *.sh\ *|*\ sh\ *.sh\ *) printf '**/*.sh'; return ;;
|
|
180
|
+
*[\ /]eslint\ *|*[\ /]tsc\ *|*[\ /]prettier\ *|*[\ /]vitest\ *|*[\ /]jest\ *|*\ node\ --test*|*\ npm\ *|*\ pnpm\ *|*\ yarn\ *|*\ npx\ *) ext_globs . "$JS_EXT"; return ;;
|
|
181
|
+
esac
|
|
182
|
+
printf ''
|
|
183
|
+
}
|
|
184
|
+
grep -E '^\.github/workflows/[^/]+\.(yml|yaml)$' "$T/files" | sort | while IFS= read -r wf; do
|
|
185
|
+
[ -f "$wf" ] || continue
|
|
186
|
+
wf_extract "$wf" > "$T/wf"
|
|
187
|
+
paths=$(awk -F'\t' '$1 == "P" { printf "%s\037", $2 }' "$T/wf"); paths=${paths%"$US"}
|
|
188
|
+
awk -F'\t' '$1 != "P"' "$T/wf" | while IFS="$(printf '\037')" read -r job jname sname cmd; do
|
|
189
|
+
skip_cmd "$cmd" && continue
|
|
190
|
+
deploy_step "$job" "$jname" "$sname" "$cmd" && continue
|
|
191
|
+
g=$(tool_globs "$cmd")
|
|
192
|
+
[ -n "$g" ] || g="$paths"
|
|
193
|
+
[ -n "$g" ] || g="**"
|
|
194
|
+
row "$wf#jobs.$job" "." "$cmd" "$g"
|
|
195
|
+
done
|
|
196
|
+
done
|
|
197
|
+
|
|
198
|
+
MAP=$(jq -R -s -c '
|
|
199
|
+
[split("\n")[] | select(length > 0) | split("\t") | {source: .[0], cwd: .[1], command: .[2], globs: (.[3] | split("\u001f") | map(select(length > 0)))}]
|
|
200
|
+
| sort_by(.source, .command)
|
|
201
|
+
| reduce .[] as $e ({order: [], by: {}};
|
|
202
|
+
([$e.command, $e.cwd] | tojson) as $k
|
|
203
|
+
| if .by[$k] then .by[$k].globs += ($e.globs - .by[$k].globs)
|
|
204
|
+
else .order += [$k] | .by[$k] = {globs: $e.globs, command: $e.command, cwd: $e.cwd, source: $e.source} end)
|
|
205
|
+
| {version: 1, entries: [.by[.order[]]]}' < "$T/rows") || die "jq failed"
|
|
206
|
+
|
|
207
|
+
if [ "$STDOUT" = 1 ]; then printf '%s\n' "$MAP"; exit 0; fi
|
|
208
|
+
[ -n "$OUT" ] || OUT=".forge/test-map.json"
|
|
209
|
+
mkdir -p "$(dirname "$OUT")" || die "cannot create $(dirname "$OUT")"
|
|
210
|
+
printf '%s\n' "$MAP" > "$OUT.tmp.$$" && mv "$OUT.tmp.$$" "$OUT" || die "cannot write $OUT"
|
|
211
|
+
echo "$OUT"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test-map.test.sh — cases for scripts/test-map.sh over the node, python and go fixtures (forge#4272).
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
set -uo pipefail
|
|
6
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
S="$HERE/test-map.sh"
|
|
8
|
+
SEL="$HERE/../bin/lean/testmap.mjs"
|
|
9
|
+
FIX="$HERE/fixtures/test-map"
|
|
10
|
+
PASS=0; FAILN=0
|
|
11
|
+
TMP="$(mktemp -d "${TMPDIR:-/tmp}/test-map-test.XXXXXX")"; trap 'rm -rf "$TMP"' EXIT
|
|
12
|
+
ok() { PASS=$((PASS+1)); }
|
|
13
|
+
bad() { FAILN=$((FAILN+1)); echo "FAIL: $1"; }
|
|
14
|
+
eq() { if [ "$2" = "$3" ]; then ok; else bad "$1 (got: $2 want: $3)"; fi }
|
|
15
|
+
|
|
16
|
+
# copy a fixture into a fresh git repo and build its map
|
|
17
|
+
setup() {
|
|
18
|
+
rm -rf "$TMP/$1"; cp -R "$FIX/$1" "$TMP/$1"; cd "$TMP/$1" || exit 1
|
|
19
|
+
git init -q -b main; git config user.email t@example.com; git config user.name t; git config commit.gpgsign false
|
|
20
|
+
git add -A; git commit -qm init
|
|
21
|
+
OUTP="$(bash "$S")"; RC=$?
|
|
22
|
+
}
|
|
23
|
+
# selected commands for changed files, sorted, joined by |
|
|
24
|
+
sel() { node "$SEL" --map .forge/test-map.json --select "$@" | LC_ALL=C sort | paste -sd'|' -; }
|
|
25
|
+
|
|
26
|
+
# ---- 1. node: root scripts for root files, workspace scripts for package files ----
|
|
27
|
+
setup node
|
|
28
|
+
eq "node rc" "$RC" 0
|
|
29
|
+
eq "node output path" "$OUTP" ".forge/test-map.json"
|
|
30
|
+
eq "schema" "$(jq -c '[.version, (.entries | length > 0), (.entries | all(has("globs") and has("command") and has("cwd") and has("source")))]' .forge/test-map.json)" '[1,true,true]'
|
|
31
|
+
eq "non-check script ignored" "$(jq '[.entries[] | select(.command | test("build"))] | length' .forge/test-map.json)" 0
|
|
32
|
+
eq "root file" "$(sel src/a.ts)" "pnpm run lint|pnpm run test"
|
|
33
|
+
eq "package file" "$(sel packages/ui/src/button.tsx)" "cd packages/ui && pnpm run test|cd packages/ui && pnpm run typecheck|pnpm run lint|pnpm run test"
|
|
34
|
+
eq "vitest config noted" "$(jq -r '.entries[] | select(.cwd == "." and .command == "pnpm run test") | .source' .forge/test-map.json)" "package.json#scripts.test (vitest/jest config)"
|
|
35
|
+
eq "unrelated file" "$(sel README.md)" ""
|
|
36
|
+
|
|
37
|
+
# ---- 2. python: pytest + ruff through uv ----
|
|
38
|
+
setup python
|
|
39
|
+
eq "python rc" "$RC" 0
|
|
40
|
+
eq "python file" "$(sel pkg/mod.py)" "uv run pytest|uv run ruff check ."
|
|
41
|
+
eq "python test file" "$(sel tests/test_mod.py)" "uv run pytest|uv run ruff check ."
|
|
42
|
+
eq "python non-py" "$(sel uv.lock)" ""
|
|
43
|
+
|
|
44
|
+
# ---- 3. go: go.mod, Makefile and CI workflow steps; CI-only lint step; install lines skipped ----
|
|
45
|
+
setup go
|
|
46
|
+
eq "go rc" "$RC" 0
|
|
47
|
+
eq "go file" "$(sel main.go)" "./scripts/check-docs|go test ./...|go vet ./...|golangci-lint run ./...|make test"
|
|
48
|
+
eq "nested go file" "$(sel internal/util/util.go)" "./scripts/check-docs|go test ./...|go vet ./...|golangci-lint run ./...|make test"
|
|
49
|
+
eq "ci-only lint source" "$(jq -r '.entries[] | select(.command == "golangci-lint run ./...") | .source' .forge/test-map.json)" ".github/workflows/ci.yml#jobs.lint"
|
|
50
|
+
eq "install line skipped" "$(jq '[.entries[] | select(.command | test("mod download"))] | length' .forge/test-map.json)" 0
|
|
51
|
+
eq "paths filter fallback" "$(jq -c '.entries[] | select(.command == "./scripts/check-docs") | .globs' .forge/test-map.json)" '["**.go","go.mod"]'
|
|
52
|
+
eq "docs file" "$(sel README.md)" "make test"
|
|
53
|
+
eq "go.sum" "$(sel go.sum)" "go test ./...|go vet ./...|make test"
|
|
54
|
+
|
|
55
|
+
# ---- 4. regenerable: a rerun is byte-identical; --stdout writes nothing; --output ----
|
|
56
|
+
cp .forge/test-map.json "$TMP/first.json"
|
|
57
|
+
bash "$S" >/dev/null
|
|
58
|
+
if cmp -s .forge/test-map.json "$TMP/first.json"; then ok; else bad "rerun not byte-identical"; fi
|
|
59
|
+
rm -rf .forge; OUTS="$(bash "$S" --stdout)"
|
|
60
|
+
eq "stdout same map" "$OUTS" "$(cat "$TMP/first.json")"
|
|
61
|
+
eq "stdout writes nothing" "$([ -e .forge ] && echo yes || echo no)" no
|
|
62
|
+
bash "$S" --repo-path "$TMP/go" --output "$TMP/out/map.json" >/dev/null
|
|
63
|
+
if cmp -s "$TMP/out/map.json" "$TMP/first.json"; then ok; else bad "--output differs"; fi
|
|
64
|
+
|
|
65
|
+
# ---- 5. block runs, continuation lines, setup lines, workflow without paths ----
|
|
66
|
+
mkdir -p "$TMP/wf/.github/workflows"; cd "$TMP/wf" || exit 1
|
|
67
|
+
git init -q -b main
|
|
68
|
+
cat > .github/workflows/lint.yaml <<'EOF'
|
|
69
|
+
on: push
|
|
70
|
+
jobs:
|
|
71
|
+
checks:
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- run: npm ci
|
|
75
|
+
- run: "shellcheck scripts/*.sh"
|
|
76
|
+
- run: >-
|
|
77
|
+
ruff check
|
|
78
|
+
--fix
|
|
79
|
+
- run: |
|
|
80
|
+
echo hi
|
|
81
|
+
bash scripts/verify.sh \
|
|
82
|
+
--strict
|
|
83
|
+
./custom-check
|
|
84
|
+
EOF
|
|
85
|
+
git add -A
|
|
86
|
+
OUTS="$(bash "$S" --stdout)"
|
|
87
|
+
eq "wf commands" "$(printf '%s' "$OUTS" | jq -r '[.entries[].command] | sort | join("|")')" "./custom-check|bash scripts/verify.sh --strict|ruff check --fix|shellcheck scripts/*.sh"
|
|
88
|
+
eq "wf globs" "$(printf '%s' "$OUTS" | jq -c '[.entries[] | {(.command): .globs}] | add')" '{"./custom-check":["**"],"bash scripts/verify.sh --strict":["**/*.sh"],"ruff check --fix":["**/*.py"],"shellcheck scripts/*.sh":["**/*.sh"]}'
|
|
89
|
+
|
|
90
|
+
# ---- 5b. forge#4297: `- run: |` then env:/shell: ends the block; deploy/publish/release/push/secret steps dropped ----
|
|
91
|
+
mkdir -p "$TMP/wf2/.github/workflows"; cd "$TMP/wf2" || exit 1
|
|
92
|
+
git init -q -b main
|
|
93
|
+
cat > .github/workflows/ci.yml <<'EOF'
|
|
94
|
+
on:
|
|
95
|
+
push:
|
|
96
|
+
branches: [main]
|
|
97
|
+
jobs:
|
|
98
|
+
test:
|
|
99
|
+
runs-on: ubuntu-latest
|
|
100
|
+
steps:
|
|
101
|
+
- run: |
|
|
102
|
+
npm test
|
|
103
|
+
env:
|
|
104
|
+
CI: true
|
|
105
|
+
shell: bash
|
|
106
|
+
- name: Lint
|
|
107
|
+
run: |
|
|
108
|
+
npm run lint
|
|
109
|
+
working-directory: .
|
|
110
|
+
- name: Publish to npm
|
|
111
|
+
run: npm run ship
|
|
112
|
+
- run: ./scripts/release.sh
|
|
113
|
+
- run: git push origin HEAD
|
|
114
|
+
- run: curl -H "token ${{ secrets.TOKEN }}" https://x
|
|
115
|
+
- run: >-
|
|
116
|
+
tsc
|
|
117
|
+
--noEmit
|
|
118
|
+
shell: bash
|
|
119
|
+
deploy:
|
|
120
|
+
name: Ship it
|
|
121
|
+
runs-on: ubuntu-latest
|
|
122
|
+
steps:
|
|
123
|
+
- run: ./ship.sh
|
|
124
|
+
site:
|
|
125
|
+
name: Deploy site
|
|
126
|
+
runs-on: ubuntu-latest
|
|
127
|
+
steps:
|
|
128
|
+
- run: ./build-site.sh
|
|
129
|
+
EOF
|
|
130
|
+
git add -A
|
|
131
|
+
OUTS="$(bash "$S" --stdout)"
|
|
132
|
+
eq "wf2 commands" "$(printf '%s' "$OUTS" | jq -r '[.entries[].command] | sort | join("|")')" "npm run lint|npm test|tsc --noEmit"
|
|
133
|
+
|
|
134
|
+
# ---- 5c. forge#4298: a package dir with a space and a non-ASCII name keeps whole globs; workflow paths with spaces ----
|
|
135
|
+
mkdir -p "$TMP/sp/web app" "$TMP/sp/pkg/ü/src" "$TMP/sp/.github/workflows"; cd "$TMP/sp" || exit 1
|
|
136
|
+
git init -q -b main
|
|
137
|
+
echo '{"name":"root","private":true}' > package.json
|
|
138
|
+
echo '{"name":"web","scripts":{"test":"vitest run"}}' > "web app/package.json"
|
|
139
|
+
echo '{"name":"u","scripts":{"lint":"eslint ."}}' > "pkg/ü/package.json"
|
|
140
|
+
echo 'x' > "pkg/ü/src/a.ts"
|
|
141
|
+
cat > .github/workflows/ci.yml <<'EOF'
|
|
142
|
+
on:
|
|
143
|
+
push:
|
|
144
|
+
paths:
|
|
145
|
+
- "docs site/**"
|
|
146
|
+
jobs:
|
|
147
|
+
docs:
|
|
148
|
+
runs-on: ubuntu-latest
|
|
149
|
+
steps:
|
|
150
|
+
- run: ./build-docs.sh
|
|
151
|
+
EOF
|
|
152
|
+
git add -A
|
|
153
|
+
OUTS="$(bash "$S" --stdout)"
|
|
154
|
+
eq "spaced package globs" "$(printf '%s' "$OUTS" | jq -c '[.entries[] | select(.cwd == "web app") | .globs[]] | map(select(endswith("package.json") or endswith(".js")))')" '["web app/**/*.js","web app/package.json"]'
|
|
155
|
+
eq "non-ASCII package dir" "$(printf '%s' "$OUTS" | jq -r '[.entries[] | select(.command == "npm run lint") | .cwd] | join("|")')" "pkg/ü"
|
|
156
|
+
eq "spaced workflow path filter" "$(printf '%s' "$OUTS" | jq -c '[.entries[] | select(.command == "./build-docs.sh") | .globs]')" '[["docs site/**"]]'
|
|
157
|
+
printf '%s' "$OUTS" > "$TMP/sp-map.json"
|
|
158
|
+
eq "spaced changed file selects its package test" "$(node "$SEL" --map "$TMP/sp-map.json" --select "web app/src/x.js")" "cd 'web app' && npm run test"
|
|
159
|
+
|
|
160
|
+
# ---- 6. usage ----
|
|
161
|
+
bash "$S" --repo-path "$TMP/missing" >/dev/null 2>&1; eq "bad repo path" "$?" 2
|
|
162
|
+
bash "$S" --nope >/dev/null 2>&1; eq "unknown option" "$?" 2
|
|
163
|
+
|
|
164
|
+
echo "test-map.test.sh: $PASS passed, $FAILN failed"
|
|
165
|
+
[ "$FAILN" -eq 0 ]
|
package/scripts/wait-ci-green.sh
CHANGED
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
#
|
|
5
5
|
# wait-ci-green.sh — CI gate every autonomous merge must pass first.
|
|
6
6
|
#
|
|
7
|
-
# Usage: wait-ci-green.sh <pr-number> -R <owner/repo> [--timeout SECONDS] [--interval SECONDS]
|
|
7
|
+
# Usage: wait-ci-green.sh <pr-number> -R <owner/repo> [--timeout SECONDS] [--interval SECONDS] [--ignore-check NAME]...
|
|
8
|
+
#
|
|
9
|
+
# --ignore-check NAME (repeatable): drop checks with exactly that name before deciding. `forgedock/verify` (forge#4270,
|
|
10
|
+
# the lean engine's report-only gate summary) is always ignored, so every caller of this gate treats it the same
|
|
11
|
+
# (forge#4298); requiring it is the repository's branch-protection choice.
|
|
8
12
|
#
|
|
9
13
|
# Waits until every check on the PR has finished, then decides. Fails closed: a merge may
|
|
10
14
|
# proceed ONLY on exit 0. Field test: pipeline agents merged PRs to staging while checks were
|
|
@@ -40,17 +44,18 @@ set -uo pipefail
|
|
|
40
44
|
_TEL="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/telemetry.sh"; [ -f "$_TEL" ] && . "$_TEL" 2>/dev/null
|
|
41
45
|
type forge_emit >/dev/null 2>&1 || forge_emit() { :; }
|
|
42
46
|
|
|
43
|
-
PR=""; REPO=""
|
|
47
|
+
PR=""; REPO=""; IGNORE='["forgedock/verify"]'
|
|
44
48
|
TIMEOUT="${FORGE_CI_TIMEOUT:-540}"; INTERVAL="${FORGE_CI_INTERVAL:-20}"; SETTLE="${FORGE_CI_SETTLE:-45}"
|
|
45
49
|
GRACE="${FORGE_CI_NO_CHECKS_GRACE:-120}"; REQUIRE="${FORGE_CI_REQUIRE_CHECKS:-0}"
|
|
46
|
-
usage() { echo "CI_GATE: ERROR"; echo "CI_GATE_HEAD: unknown"; echo "usage: wait-ci-green.sh <pr> -R <owner/repo> [--timeout S] [--interval S]" >&2; exit 2; }
|
|
50
|
+
usage() { echo "CI_GATE: ERROR"; echo "CI_GATE_HEAD: unknown"; echo "usage: wait-ci-green.sh <pr> -R <owner/repo> [--timeout S] [--interval S] [--ignore-check NAME]..." >&2; exit 2; }
|
|
47
51
|
while [ $# -gt 0 ]; do
|
|
48
52
|
case "$1" in
|
|
49
53
|
-R) [ $# -ge 2 ] || usage; REPO="$2"; shift 2 ;;
|
|
50
54
|
-R*) REPO="${1#-R}"; REPO="${REPO# }"; shift ;;
|
|
51
55
|
--timeout) [ $# -ge 2 ] || usage; TIMEOUT="$2"; shift 2 ;;
|
|
52
56
|
--interval) [ $# -ge 2 ] || usage; INTERVAL="$2"; shift 2 ;;
|
|
53
|
-
-
|
|
57
|
+
--ignore-check) [ $# -ge 2 ] || usage; IGNORE="$(jq -c --arg n "$2" '. + [$n]' <<<"$IGNORE")"; shift 2 ;;
|
|
58
|
+
-h|--help) sed -n '5,37p' "$0"; exit 0 ;;
|
|
54
59
|
*) [ -z "$PR" ] || usage; PR="$1"; shift ;;
|
|
55
60
|
esac
|
|
56
61
|
done
|
|
@@ -132,6 +137,7 @@ while :; do
|
|
|
132
137
|
fi
|
|
133
138
|
fi
|
|
134
139
|
errors=0; limited=0; transient=0
|
|
140
|
+
[ "$IGNORE" = '[]' ] || JSON="$(printf '%s' "$JSON" | jq -c --argjson ig "$IGNORE" '[.[] | select(.name as $n | $ig | index($n) | not)]')"
|
|
135
141
|
cur="$(head_sha)"
|
|
136
142
|
if [ -n "$cur" ] && [ "$cur" != "$START_SHA" ]; then emit ERROR; echo "PR head moved during the wait ($START_SHA -> $cur); re-review the new head"; exit 2; fi
|
|
137
143
|
total=$(printf '%s' "$JSON" | jq 'length')
|
|
@@ -55,6 +55,15 @@ expect "pending then pass" "0|CI_GATE: PASS" "$(run "$(mk pend '[{"name":"a","bu
|
|
|
55
55
|
expect "pending then fail" "1|CI_GATE: FAIL" "$(run "$(mk pendfail '[{"name":"a","bucket":"pending"}]' '[{"name":"a","bucket":"fail"}]')")"
|
|
56
56
|
expect "pending past timeout" "3|CI_GATE: TIMEOUT" "$(run "$(mk slow '[{"name":"a","bucket":"pending"}]')" --timeout 2)"
|
|
57
57
|
expect "fail wins over pending" "1|CI_GATE: FAIL" "$(run "$(mk mixed '[{"name":"a","bucket":"pending"},{"name":"b","bucket":"fail"}]')")"
|
|
58
|
+
# forge#4270: --ignore-check drops the named check (the lane's own report-only forgedock/verify) before deciding.
|
|
59
|
+
OWN='[{"name":"a","bucket":"pass"},{"name":"forgedock/verify","bucket":"fail"}]'
|
|
60
|
+
# forge#4298: forgedock/verify is ignored by default, so every caller (legacy engine, review-finalize, spec gates) agrees.
|
|
61
|
+
expect "own verify check failing is ignored by default" "0|CI_GATE: PASS" "$(run "$(mk ownfail "$OWN")")"
|
|
62
|
+
expect "default ignore keeps other failures" "1|CI_GATE: FAIL" "$(run "$(mk owndef '[{"name":"b","bucket":"fail"},{"name":"forgedock/verify","bucket":"fail"}]')")"
|
|
63
|
+
expect "usage text lists --ignore-check" "1" "$(bash "$GATE" 2>&1 >/dev/null | grep -c -- '--ignore-check NAME')"
|
|
64
|
+
expect "--ignore-check drops the named failing check" "0|CI_GATE: PASS" "$(run "$(mk ownign "$OWN")" --ignore-check forgedock/verify)"
|
|
65
|
+
expect "--ignore-check keeps other failures" "1|CI_GATE: FAIL" "$(run "$(mk ownother '[{"name":"b","bucket":"fail"},{"name":"forgedock/verify","bucket":"pass"}]')" --ignore-check forgedock/verify)"
|
|
66
|
+
expect "--ignore-check without a name is a usage error" "2|CI_GATE: ERROR" "$(run "$(mk ownusage "$P")" --ignore-check)"
|
|
58
67
|
expect "transient outage then pass" "0|CI_GATE: PASS" "$(run "$(mk blip ERR "$P")")"
|
|
59
68
|
expect "persistent unclassified failure is an ERROR" "2|CI_GATE: ERROR" "$(run "$(mk down ERRX)" --timeout 60)"
|
|
60
69
|
# Rate-limit / 5xx classification: retried, never an ERROR; a window that only saw them ends RATE_LIMITED (rc 4).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
2
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
3
|
+
# /bootstrap interview question bank
|
|
4
|
+
|
|
5
|
+
Ask conversationally and keep it short: skip any aspect the user's free text or docs already answer. Every aspect left
|
|
6
|
+
unanswered gets the **Default** and its rationale, tied to what the user did say, in the "Here's what I assumed" summary.
|
|
7
|
+
Aspects marked **Doubt** change architecture or cost. When the answer is unclear, ask them as multiple-choice questions
|
|
8
|
+
(at most 4 per batch, each with a recommended option). All other aspects default silently.
|
|
9
|
+
|
|
10
|
+
| # | Aspect | Question | Default (rationale pattern) | Doubt? |
|
|
11
|
+
|---|--------|----------|-----------------------------|--------|
|
|
12
|
+
| 1 | Users and segments | Who uses it, and which segments differ in what they need? | One primary segment from the pitch; admins as a second role (most products need an operator view) | no |
|
|
13
|
+
| 2 | Core jobs and features | What must a user be able to do on day one? | The jobs named in the pitch, each as a feature with its happy path | no |
|
|
14
|
+
| 3 | Scope | MVP first, or the full product? | MVP, full product as later milestones (cheapest path to feedback) | yes, when the pitch lists many features |
|
|
15
|
+
| 4 | Platforms | Web, mobile, desktop? | Responsive web (one codebase covers phones) | yes: mobile native vs PWA |
|
|
16
|
+
| 5 | Stack | Any language, framework or hosting preferences? | The closest preset in `stack-profiles.md` | no, unless the user names conflicting stacks |
|
|
17
|
+
| 6 | Auth | How do users sign in? | Email magic link + Google sign-in (lowest friction, no password storage) | no |
|
|
18
|
+
| 7 | Data and tenancy | Is data shared between organisations? | Postgres + row-level security for multi-tenant SaaS; single-tenant otherwise | yes: self-host vs managed DB |
|
|
19
|
+
| 8 | Integrations | Which external services? | A mock provider per credentialed integration so dev and CI stay green | no |
|
|
20
|
+
| 9 | Payments | Do you charge, and how? | Stripe subscriptions behind a mock in dev (most common, test mode is free) | yes, when pricing is unclear |
|
|
21
|
+
| 10 | i18n | Which languages and locales? | English with i18n-ready strings (cheap now, expensive later) | no |
|
|
22
|
+
| 11 | Compliance and residency | Any regulation (GDPR, HIPAA, DPDP) or data-residency rule? | Privacy by default (minimal PII, encrypted at rest); region of the primary market | yes: data residency |
|
|
23
|
+
| 12 | Design ambition and UI libraries | Polished brand or functional? Any UI library? | Accessible component library (e.g. shadcn/ui) plus a design-token theme; hero surfaces go to `/design` | no |
|
|
24
|
+
| 13 | Deployment target | VPS, Cloudflare or a cloud provider? | The stack preset's target with a staging-branch flow (staging -> main) | yes: when the cost profile differs |
|
|
25
|
+
| 14 | Scale | Users and data in year one? | Small (hundreds of users): one region, one DB, a scale path in the spike | no |
|
|
26
|
+
| 15 | Budget | Monthly infra budget? | Free or hobby tiers until launch | no |
|
|
27
|
+
| 16 | Team | Who builds and reviews? | A solo builder with ForgeDock agents | no |
|
|
28
|
+
| 17 | Timeline | Any hard date? | None: milestones in build order | no |
|
|
29
|
+
|
|
30
|
+
Always a doubt: the **product name** and the **repo owner** (user or org; the owner must be covered by the ForgeDock
|
|
31
|
+
GitHub App installation).
|
|
32
|
+
|
|
33
|
+
Guardrails and domain rules (e.g. "no auto-send", "India-only data residency") go into the plan's `guardrails` and
|
|
34
|
+
`domainRules`. bootstrap-create.mjs copies them into every issue's Context.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
2
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
3
|
+
<!-- /bootstrap build-issue body (Pipeline Issue Template). Copy from "## Problem" down; drop the UI block for
|
|
4
|
+
non-UI issues and the hero line for non-hero issues. Plan flags: "ui": true needs the full design spec, "hero": true
|
|
5
|
+
needs the /design line (scripts/bootstrap-validate.mjs). Reference other plan issues as #{{KEY}}. -->
|
|
6
|
+
## Problem
|
|
7
|
+
|
|
8
|
+
{what this issue delivers and for whom, one paragraph}
|
|
9
|
+
|
|
10
|
+
## Root Cause (if known)
|
|
11
|
+
|
|
12
|
+
Greenfield: not built yet. {spike section this implements}
|
|
13
|
+
|
|
14
|
+
## Affected Files
|
|
15
|
+
|
|
16
|
+
1. `{planned/path/file.ext}` — new
|
|
17
|
+
2. `{planned/path/other.ext}` — new
|
|
18
|
+
|
|
19
|
+
## Acceptance Criteria
|
|
20
|
+
|
|
21
|
+
- [ ] {observable behaviour} [type:unit]
|
|
22
|
+
- [ ] {API or integration behaviour} [type:api]
|
|
23
|
+
- [ ] {user-visible flow} [type:e2e]
|
|
24
|
+
|
|
25
|
+
### Design spec (UI issues)
|
|
26
|
+
|
|
27
|
+
- **Breakpoints**: {mobile 360, tablet 768, desktop 1280: layout at each}
|
|
28
|
+
- **States**: empty state {copy and action}; loading state {skeleton}; error state {message and retry}; offline state {cached view or banner}
|
|
29
|
+
- **Motion**: {transitions and durations; respects prefers-reduced-motion}
|
|
30
|
+
- **Accessibility**: {WCAG 2.2 AA: focus order, labels, contrast, keyboard paths}
|
|
31
|
+
- **Dark mode**: {token mapping; verified in both themes}
|
|
32
|
+
- **Hero surface**: route the visual design to `/design` before building this page.
|
|
33
|
+
|
|
34
|
+
## Context
|
|
35
|
+
|
|
36
|
+
**Parent**: #{{SPIKE}}
|
|
37
|
+
Spike: `docs/engineering/technical-spike.md` § {section}
|
|
38
|
+
{real credentials: see #{{NEEDS_HUMAN_KEY}}; until then the mock provider is used, so this issue never depends on it}
|
|
39
|
+
|
|
40
|
+
## Dependencies
|
|
41
|
+
|
|
42
|
+
Depends on #{{DEP_KEY}} — {why}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
2
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
3
|
+
<!-- /bootstrap needs-human issue: one per credential, account or external input (OAuth client, cloud or DNS account,
|
|
4
|
+
payment keys, API keys, legal review). Label it needs-human and set "needsHuman": true in the plan. Code issues
|
|
5
|
+
reference it (#{{KEY}}) but never list it under Dependencies; their mocks keep dev and CI green. Copy from "## Problem" down. -->
|
|
6
|
+
## Problem
|
|
7
|
+
|
|
8
|
+
{which credential, account or external input is needed, and which issues use it}
|
|
9
|
+
|
|
10
|
+
### Setup steps
|
|
11
|
+
|
|
12
|
+
1. {exact console URL and the clicks to create it}
|
|
13
|
+
2. {settings to choose: redirect URIs, scopes, region}
|
|
14
|
+
3. {store the values as deploy secrets (repository or environment secrets)}
|
|
15
|
+
|
|
16
|
+
### Environment variables
|
|
17
|
+
|
|
18
|
+
- `{SERVICE_CLIENT_ID}` — {what it is}
|
|
19
|
+
- `{SERVICE_CLIENT_SECRET}` — {what it is}
|
|
20
|
+
|
|
21
|
+
### Secret handling
|
|
22
|
+
|
|
23
|
+
Never commit these values or paste them into issues. Store them only as deploy/CI secrets and a local untracked
|
|
24
|
+
`.env`. Rotate them if exposed. Until they exist, dev and CI use the mock provider.
|
|
25
|
+
|
|
26
|
+
## Root Cause (if known)
|
|
27
|
+
|
|
28
|
+
Greenfield: an external input agents cannot create.
|
|
29
|
+
|
|
30
|
+
## Affected Files
|
|
31
|
+
|
|
32
|
+
1. `docs/setup/{service}.md` — new
|
|
33
|
+
|
|
34
|
+
## Acceptance Criteria
|
|
35
|
+
|
|
36
|
+
- [ ] Credentials created and stored as deploy secrets [type:manual]
|
|
37
|
+
- [ ] The real provider replaces the mock in staging [type:manual]
|
|
38
|
+
|
|
39
|
+
## Context
|
|
40
|
+
|
|
41
|
+
**Parent**: #{{SPIKE}}
|
|
42
|
+
|
|
43
|
+
## Dependencies
|
|
44
|
+
|
|
45
|
+
None.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
2
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
3
|
+
# /bootstrap stack-profile presets
|
|
4
|
+
|
|
5
|
+
Pick the preset closest to the interview and record the choice and its rationale in the spike's Stack section. A
|
|
6
|
+
preset only fills blanks: anything the user said wins. Domain labels go into the plan's `labels`.
|
|
7
|
+
|
|
8
|
+
## Next.js + shadcn SaaS
|
|
9
|
+
- **Stack**: TypeScript, Next.js (App Router), shadcn/ui + Tailwind, Postgres (Prisma or Drizzle) with row-level security, Auth.js.
|
|
10
|
+
- **Repo layout**: `apps/web/`, `packages/db/`, `packages/ui/`, `packages/config/`, `docs/engineering/`.
|
|
11
|
+
- **Deployment**: Vercel or a VPS with Docker; managed Postgres; preview deployments per branch.
|
|
12
|
+
- **Quality gates**: `tsc --noEmit`, ESLint, Vitest, Playwright smoke test, Lighthouse budget on hero pages.
|
|
13
|
+
- **Domain labels**: `frontend`, `backend`, `infra`, `design`.
|
|
14
|
+
|
|
15
|
+
## Python API
|
|
16
|
+
- **Stack**: Python 3.12, FastAPI, SQLAlchemy + Alembic, Postgres, Pydantic settings, uv.
|
|
17
|
+
- **Repo layout**: `src/<app>/api/`, `src/<app>/models/`, `src/<app>/services/`, `migrations/`, `tests/`, `docs/engineering/`.
|
|
18
|
+
- **Deployment**: a container on a VPS or Cloud Run; managed Postgres.
|
|
19
|
+
- **Quality gates**: ruff, mypy, pytest with coverage, an OpenAPI schema diff check.
|
|
20
|
+
- **Domain labels**: `backend`, `infra`, `api`.
|
|
21
|
+
|
|
22
|
+
## Mobile (Expo / React Native)
|
|
23
|
+
- **Stack**: TypeScript, Expo + React Native, Expo Router, a backend from one of the presets above (or Supabase).
|
|
24
|
+
- **Repo layout**: `apps/mobile/`, `apps/api/` (when a custom backend exists), `packages/shared/`, `docs/engineering/`.
|
|
25
|
+
- **Deployment**: EAS Build and Submit; OTA updates for JS-only changes.
|
|
26
|
+
- **Quality gates**: `tsc --noEmit`, ESLint, Jest, a Maestro or Detox smoke flow.
|
|
27
|
+
- **Domain labels**: `mobile`, `backend`, `infra`, `design`.
|
|
28
|
+
|
|
29
|
+
## Go service
|
|
30
|
+
- **Stack**: Go, chi or net/http, sqlc + Postgres, Docker.
|
|
31
|
+
- **Repo layout**: `cmd/<app>/`, `internal/`, `migrations/`, `docs/engineering/`.
|
|
32
|
+
- **Deployment**: a single static binary in a container on a VPS or Fly.io.
|
|
33
|
+
- **Quality gates**: `go vet`, golangci-lint, `go test -race`.
|
|
34
|
+
- **Domain labels**: `backend`, `infra`.
|