From 66a791bd72b6b4810b71c961c5191cdd4124f272 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 15 Sep 2026 17:47:33 -0700 Subject: [PATCH] Single-source the APS checkout under tmp/aps There were up to four APS copies (tmp/aps, tmp/aps-spec, tmp/aps-fresh, and a component-local client copy) and the briefing, wrappers, and runners disagreed on which to use. Add ensure-aps.sh (ensure/update/reclone) as the one canonical tmp/aps checkout, point the gherkin-parser wrapper, architect startup, and all three acceptance runners at it, and update the architect cheat-sheet. By specifier. --- docs/architect-startup.md | 7 +-- psf-memo-client/acceptance/acceptance.js | 16 ++++--- psf-memo-db/acceptance/acceptance.js | 14 ++++-- psf-memo-indexer/acceptance/acceptance.js | 14 ++++-- swarmforge/scripts/architect-startup.sh | 12 +++-- swarmforge/scripts/ensure-aps.sh | 53 +++++++++++++++++++++++ swarmforge/scripts/gherkin-parser | 4 +- 7 files changed, 99 insertions(+), 21 deletions(-) create mode 100755 swarmforge/scripts/ensure-aps.sh diff --git a/docs/architect-startup.md b/docs/architect-startup.md index 137a04b..683f998 100644 --- a/docs/architect-startup.md +++ b/docs/architect-startup.md @@ -6,7 +6,8 @@ Fast-start notes for the architect role in this monorepo. Captured from the ## Tool locations (already installed — do not reinstall or re-verify) - Language mutation/CRAP/DRY (JavaScript): `psf-memo-client/node_modules/.bin/` - `mutate4javascript`, `crap4javascript`, `dry4javascript` -- APS Babashka tools: `tmp/aps-spec/` (run via `bb ` from that dir) +- APS Babashka tools: `tmp/aps/` (single canonical checkout; refresh with + `swarmforge/scripts/ensure-aps.sh --update`; run via `bb ` from that dir) - `gherkin-parser`, `gherkin-mutator`, `gherkin-ir-dry-checker` - Runner adapters (for `gherkin-mutator`): - DB: `psf-memo-db/acceptance/lib/runner-worker.js` @@ -30,7 +31,7 @@ node ../psf-memo-client/node_modules/mutate4javascript/bin/mutate4javascript.js ``` ## Gherkin soft mutation — canonical invocation -Run from `tmp/aps-spec/`. Use `--json` and redirect stderr to `/dev/null` for a +Run from `tmp/aps/`. Use `--json` and redirect stderr to `/dev/null` for a clean parseable report (worker logs go to stderr; the report goes to stdout). ```bash bb gherkin-mutator \ @@ -70,7 +71,7 @@ re-process or re-verify. ```bash psf-memo-client/node_modules/.bin/mutate4javascript 2>&1 | head -1 # usage psf-memo-client/node_modules/.bin/dry4javascript --help 2>&1 | head -1 # usage (NOT bare) -cd tmp/aps-spec && bb gherkin-parser --help # usage +cd tmp/aps && bb gherkin-parser --help # usage ``` **IMPORTANT — never run bare `dry4javascript` as a smoke check.** With no diff --git a/psf-memo-client/acceptance/acceptance.js b/psf-memo-client/acceptance/acceptance.js index c2ae8ef..92caf0c 100644 --- a/psf-memo-client/acceptance/acceptance.js +++ b/psf-memo-client/acceptance/acceptance.js @@ -23,7 +23,9 @@ const specsDir = path.join(root, 'specs') const buildDir = path.join(root, 'build', 'acceptance') const irDir = path.join(buildDir, 'ir') const genDir = path.join(buildDir, 'generated') -const apsDir = path.join(root, 'tmp', 'aps-spec') +const repoRoot = path.resolve(root, '..') +const apsDir = path.join(repoRoot, 'tmp', 'aps') +const APS_URL = 'https://github.com/unclebob/Acceptance-Pipeline-Specification.git' function sh (cmd, args, opts = {}) { return execFileSync(cmd, args, { @@ -32,12 +34,16 @@ function sh (cmd, args, opts = {}) { }).toString() } -// Procure the latest APS tools if not already present in the worktree. +// Ensure the single canonical APS checkout (tmp/aps) is present. function ensureAps () { - if (fs.existsSync(apsDir)) return + if (fs.existsSync(path.join(apsDir, 'bb.edn'))) return + const shared = path.join(repoRoot, 'swarmforge', 'scripts', 'ensure-aps.sh') + if (fs.existsSync(shared)) { + sh('/bin/bash', [shared]) + return + } fs.mkdirSync(path.dirname(apsDir), { recursive: true }) - sh('git', ['clone', '--depth', '1', - 'https://github.com/unclebob/Acceptance-Pipeline-Specification.git', apsDir]) + sh('git', ['clone', '--depth', '1', APS_URL, apsDir]) } function main () { diff --git a/psf-memo-db/acceptance/acceptance.js b/psf-memo-db/acceptance/acceptance.js index cf4b13d..006bfb4 100644 --- a/psf-memo-db/acceptance/acceptance.js +++ b/psf-memo-db/acceptance/acceptance.js @@ -17,7 +17,9 @@ const specsDir = path.join(root, 'specs') const buildDir = path.join(root, 'build', 'acceptance') const irDir = path.join(buildDir, 'ir') const genDir = path.join(buildDir, 'generated') -const apsDir = path.join(root, '..', 'tmp', 'aps-spec') +const repoRoot = path.resolve(root, '..') +const apsDir = path.join(repoRoot, 'tmp', 'aps') +const APS_URL = 'https://github.com/unclebob/Acceptance-Pipeline-Specification.git' function sh (cmd, args, opts = {}) { return execFileSync(cmd, args, { @@ -27,10 +29,14 @@ function sh (cmd, args, opts = {}) { } function ensureAps () { - if (fs.existsSync(apsDir)) return + if (fs.existsSync(path.join(apsDir, 'bb.edn'))) return + const shared = path.join(repoRoot, 'swarmforge', 'scripts', 'ensure-aps.sh') + if (fs.existsSync(shared)) { + sh('/bin/bash', [shared]) + return + } fs.mkdirSync(path.dirname(apsDir), { recursive: true }) - sh('git', ['clone', '--depth', '1', - 'https://github.com/unclebob/Acceptance-Pipeline-Specification.git', apsDir]) + sh('git', ['clone', '--depth', '1', APS_URL, apsDir]) } // Each scenario opens an isolated LevelDB directory under tmp/acceptance. diff --git a/psf-memo-indexer/acceptance/acceptance.js b/psf-memo-indexer/acceptance/acceptance.js index 429536c..30d550e 100644 --- a/psf-memo-indexer/acceptance/acceptance.js +++ b/psf-memo-indexer/acceptance/acceptance.js @@ -13,7 +13,9 @@ const specsDir = path.join(root, 'specs') const buildDir = path.join(root, 'build', 'acceptance') const irDir = path.join(buildDir, 'ir') const genDir = path.join(buildDir, 'generated') -const apsDir = path.join(root, '..', 'tmp', 'aps-spec') +const repoRoot = path.resolve(root, '..') +const apsDir = path.join(repoRoot, 'tmp', 'aps') +const APS_URL = 'https://github.com/unclebob/Acceptance-Pipeline-Specification.git' function sh (cmd, args, opts = {}) { return execFileSync(cmd, args, { @@ -23,10 +25,14 @@ function sh (cmd, args, opts = {}) { } function ensureAps () { - if (fs.existsSync(apsDir)) return + if (fs.existsSync(path.join(apsDir, 'bb.edn'))) return + const shared = path.join(repoRoot, 'swarmforge', 'scripts', 'ensure-aps.sh') + if (fs.existsSync(shared)) { + sh('/bin/bash', [shared]) + return + } fs.mkdirSync(path.dirname(apsDir), { recursive: true }) - sh('git', ['clone', '--depth', '1', - 'https://github.com/unclebob/Acceptance-Pipeline-Specification.git', apsDir]) + sh('git', ['clone', '--depth', '1', APS_URL, apsDir]) } function main () { diff --git a/swarmforge/scripts/architect-startup.sh b/swarmforge/scripts/architect-startup.sh index fe5498d..20b3fd0 100755 --- a/swarmforge/scripts/architect-startup.sh +++ b/swarmforge/scripts/architect-startup.sh @@ -17,7 +17,13 @@ ok() { echo " [ok] $1"; pass=$((pass+1)); } bad() { echo " [FAIL] $1"; fail=$((fail+1)); } echo "== Tool repos at latest upstream ==" -for d in tmp/aps-spec tmp/crap4javascript tmp/dry4javascript tmp/mutate4javascript; do +# APS uses the single canonical checkout under tmp/aps; refresh it in place. +if "$ROOT/swarmforge/scripts/ensure-aps.sh" --update >/dev/null 2>&1; then + ok "tmp/aps present" +else + bad "tmp/aps ensure/update failed" +fi +for d in tmp/aps tmp/crap4javascript tmp/dry4javascript tmp/mutate4javascript; do if [ ! -d "$d/.git" ]; then bad "$d missing (reinstall per constitution)"; continue fi @@ -56,13 +62,13 @@ fi echo "== APS Babashka tools ==" if grep -q "usage: gherkin-parser" \ - <<< "$(cd tmp/aps-spec && bb gherkin-parser 2>&1)"; then + <<< "$(cd tmp/aps && bb gherkin-parser 2>&1)"; then ok "gherkin-parser" else bad "gherkin-parser" fi if grep -qF -- "--runner-worker is required" \ - <<< "$(cd tmp/aps-spec && bb gherkin-mutator 2>&1)"; then + <<< "$(cd tmp/aps && bb gherkin-mutator 2>&1)"; then ok "gherkin-mutator" else bad "gherkin-mutator" diff --git a/swarmforge/scripts/ensure-aps.sh b/swarmforge/scripts/ensure-aps.sh new file mode 100755 index 0000000..657c120 --- /dev/null +++ b/swarmforge/scripts/ensure-aps.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Ensure the canonical Acceptance Pipeline Specification checkout at +# /tmp/aps, and optionally update it to latest upstream. +# +# Usage: +# ensure-aps.sh # ensure present only (no network if present) +# ensure-aps.sh --update # fetch/reset to latest upstream default branch +# ensure-aps.sh --reclone # delete and clone fresh +# +# Prints the checkout path on stdout. All APS consumers should use this path +# (tmp/aps) so there is exactly one checkout. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +APS_URL="https://github.com/unclebob/Acceptance-Pipeline-Specification.git" + +mode="${1:-ensure}" +case "$mode" in + ensure | --update | --reclone) ;; + *) + echo "usage: ensure-aps.sh [--update|--reclone]" >&2 + exit 2 + ;; +esac + +common="$(git rev-parse --git-common-dir 2>/dev/null || true)" +if [ -z "$common" ]; then + echo "ensure-aps: not in a git repository" >&2 + exit 1 +fi +common_abs="$(cd "$common" 2>/dev/null && pwd || true)" +project_root="$(dirname "$common_abs")" +APS_DIR="$project_root/tmp/aps" + +if [ "$mode" = "--reclone" ]; then + rm -rf "$APS_DIR" +fi + +if [ ! -d "$APS_DIR/.git" ]; then + rm -rf "$APS_DIR" + mkdir -p "$(dirname "$APS_DIR")" + git clone --depth 1 "$APS_URL" "$APS_DIR" >&2 +elif [ "$mode" = "--update" ]; then + if git -C "$APS_DIR" fetch --depth 1 origin main:refs/remotes/origin/main >/dev/null 2>&1; then + git -C "$APS_DIR" reset --hard refs/remotes/origin/main >/dev/null 2>&1 + elif git -C "$APS_DIR" fetch --depth 1 origin master:refs/remotes/origin/master >/dev/null 2>&1; then + git -C "$APS_DIR" reset --hard refs/remotes/origin/master >/dev/null 2>&1 + else + echo "ensure-aps: update failed; using existing checkout" >&2 + fi +fi + +echo "$APS_DIR" diff --git a/swarmforge/scripts/gherkin-parser b/swarmforge/scripts/gherkin-parser index f45370d..c277d67 100755 --- a/swarmforge/scripts/gherkin-parser +++ b/swarmforge/scripts/gherkin-parser @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Wrapper for the Babashka APS gherkin-parser task. -# Delegates to the project-local APS checkout under ./tmp/aps-spec. +# Delegates to the single canonical APS checkout under /tmp/aps. set -euo pipefail if [ "$#" -ne 2 ]; then echo "usage: gherkin-parser " >&2 @@ -9,6 +9,6 @@ fi FEATURE_FILE="$(realpath -m "$1")" JSON_OUTPUT="$(realpath -m "$2")" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -APS_DIR="$(cd "$SCRIPT_DIR/../../tmp/aps-spec" && pwd)" +APS_DIR="$("$SCRIPT_DIR/ensure-aps.sh")" cd "$APS_DIR" exec bb gherkin-parser "$FEATURE_FILE" "$JSON_OUTPUT"