From 8a6a1cba687324bc861708f07d3795c975a8a15c Mon Sep 17 00:00:00 2001 From: Pierre Brisorgueil Date: Mon, 1 Jun 2026 14:12:36 +0200 Subject: [PATCH 1/3] feat(skills/update-stack): block on undeclared drift vs upstream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add gate 3ter to Phase 1: after /verify passes, diff each stack-module non-test file against devkit-node/master. Any file that diverges AND is not declared in DOWNSTREAM_PATCHES.md causes exit 1 with a clear fix message. Missing ledger = no declared divergences allowed. Prevents trawl-style silent drift (3 arch violations + 9 promote-up candidates found 2026-05-30 after weeks of unchecked accumulation). Closes #3759 — plan 2026-05-30-trawl-devkit-perfect-alignment.md (E.2) --- .claude/skills/update-stack/SKILL.md | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.claude/skills/update-stack/SKILL.md b/.claude/skills/update-stack/SKILL.md index bddb70833..f305f7d6e 100644 --- a/.claude/skills/update-stack/SKILL.md +++ b/.claude/skills/update-stack/SKILL.md @@ -88,6 +88,39 @@ BODY Proceed to Phase 2 and track the upstream fix separately — do not block downstream alignment on it. +### 3ter. Block on undeclared drift + +After `/verify` passes, run a final diff sweep before starting Phase 2. Any stack file that diverges from upstream **and** is not declared in `DOWNSTREAM_PATCHES.md` blocks the flow. + +```bash +git fetch devkit-node master --quiet + +drift_found=0 +while IFS= read -r f; do + upstream_blob=$(git ls-tree devkit-node/master -- "$f" 2>/dev/null | awk '{print $3}') + [ -z "$upstream_blob" ] && continue # downstream-only file — skip + local_hash=$(git hash-object "$f" 2>/dev/null) + if [ "$upstream_blob" != "$local_hash" ]; then + if ! grep -qF "$f" DOWNSTREAM_PATCHES.md 2>/dev/null; then + echo "BLOCK: undeclared drift on stack file: $f" + echo " Fix A — revert to upstream: git checkout devkit-node/master -- $f" + echo " Fix B — declare it: add '$f' + rationale to DOWNSTREAM_PATCHES.md" + drift_found=1 + fi + fi +done < <(git ls-files modules/home modules/auth modules/users modules/tasks modules/uploads modules/billing lib config/defaults 2>/dev/null \ + | grep -v "/tests/" | grep -vE "\.(test|spec)\.js$") + +[ "$drift_found" -eq 1 ] && exit 1 +echo "3ter: no undeclared drift — OK" +``` + +**Rules:** +- Missing `DOWNSTREAM_PATCHES.md` = no declared divergences allowed (treat as empty). +- `config/defaults/.config.js` (downstream-only config file) will be absent from `devkit-node/master` → `upstream_blob` empty → auto-skipped. +- This gate runs **after** `/verify` (never blocks on transient verify failures) and **before** Phase 2 (failure is recoverable — no merge commit yet). +- Ref: plan `2026-05-30-trawl-devkit-perfect-alignment.md` Tasks E.1 + E.2. + --- ## Phase 2 — Project alignment From 38295365c691a49f8dce4eab99c02f6a832987e6 Mon Sep 17 00:00:00 2001 From: Pierre Brisorgueil Date: Mon, 1 Jun 2026 14:18:33 +0200 Subject: [PATCH 2/3] fix(skills/update-stack): use HEAD blob + quoted path match in drift gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace `git hash-object "$f"` with `git rev-parse HEAD:"$f"` — compares committed blobs rather than working-tree bytes (CRLF-safe, no false drift from uncommitted changes post-merge) - Replace `grep -qF "$f"` with `grep -qF "'$f'"` — matches the single-quoted token in DOWNSTREAM_PATCHES.md to prevent substring false-positives - Reword rules: remove misleading note about config/defaults auto-skip (those paths are simply not in the scanned directories), clarify that downstream-only files are never scanned Per Copilot review on #3760 --- .claude/skills/update-stack/SKILL.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.claude/skills/update-stack/SKILL.md b/.claude/skills/update-stack/SKILL.md index f305f7d6e..92e782d84 100644 --- a/.claude/skills/update-stack/SKILL.md +++ b/.claude/skills/update-stack/SKILL.md @@ -99,9 +99,9 @@ drift_found=0 while IFS= read -r f; do upstream_blob=$(git ls-tree devkit-node/master -- "$f" 2>/dev/null | awk '{print $3}') [ -z "$upstream_blob" ] && continue # downstream-only file — skip - local_hash=$(git hash-object "$f" 2>/dev/null) - if [ "$upstream_blob" != "$local_hash" ]; then - if ! grep -qF "$f" DOWNSTREAM_PATCHES.md 2>/dev/null; then + local_blob=$(git rev-parse "HEAD:$f" 2>/dev/null) + if [ "$upstream_blob" != "$local_blob" ]; then + if ! grep -qF "'$f'" DOWNSTREAM_PATCHES.md 2>/dev/null; then echo "BLOCK: undeclared drift on stack file: $f" echo " Fix A — revert to upstream: git checkout devkit-node/master -- $f" echo " Fix B — declare it: add '$f' + rationale to DOWNSTREAM_PATCHES.md" @@ -117,7 +117,8 @@ echo "3ter: no undeclared drift — OK" **Rules:** - Missing `DOWNSTREAM_PATCHES.md` = no declared divergences allowed (treat as empty). -- `config/defaults/.config.js` (downstream-only config file) will be absent from `devkit-node/master` → `upstream_blob` empty → auto-skipped. +- Declare diverging paths in `DOWNSTREAM_PATCHES.md` as `'path/to/file'` (single-quoted) — the gate matches on the quoted token to avoid substring collisions. +- Downstream-only files (new modules, helpers, lib additions) are not scanned — the sweep only covers the stack directories listed above. - This gate runs **after** `/verify` (never blocks on transient verify failures) and **before** Phase 2 (failure is recoverable — no merge commit yet). - Ref: plan `2026-05-30-trawl-devkit-perfect-alignment.md` Tasks E.1 + E.2. From 9d9154672e9c3dc2c3b5c9fdba597421bcf9bf57 Mon Sep 17 00:00:00 2001 From: Pierre Brisorgueil Date: Mon, 1 Jun 2026 14:19:51 +0200 Subject: [PATCH 3/3] fix(skills/update-stack): add billing to stack module list + broaden test exclusion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `billing` to stack module declaration (it exists in devkit/Node: modules/billing/) — gate and header were inconsistent - Update conflict resolution table accordingly - Broaden test-file exclusion: add /__tests__/ directory pattern and extend extensions to (js|jsx|ts|tsx) for consistency Per CodeRabbit review on #3760 --- .claude/skills/update-stack/SKILL.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude/skills/update-stack/SKILL.md b/.claude/skills/update-stack/SKILL.md index 92e782d84..4cb155db2 100644 --- a/.claude/skills/update-stack/SKILL.md +++ b/.claude/skills/update-stack/SKILL.md @@ -17,7 +17,7 @@ Two-phase workflow. Phase 1 brings the stack down ISO. Phase 2 aligns the projec **Goal: stack modules and lib exit this phase identical to upstream. Zero downstream logic in them.** -Stack modules: `home`, `auth`, `users`, `tasks`, `uploads` — Stack core: `lib/` (existing files), `config/defaults/` (stack-owned files only) +Stack modules: `home`, `auth`, `users`, `tasks`, `uploads`, `billing` — Stack core: `lib/` (existing files), `config/defaults/` (stack-owned files only) ### 1. Setup remote + merge @@ -31,7 +31,7 @@ git merge devkit-node/master | File | Rule | |------|------| -| Stack module (`modules/home\|auth\|users\|tasks\|uploads`) | `git checkout --theirs ` | +| Stack module (`modules/home\|auth\|users\|tasks\|uploads\|billing`) | `git checkout --theirs ` | | `lib/` | `git checkout --theirs ` (existing stack framework files — always ISO) | | `config/defaults/development.js`, `production.js`, etc. | `git checkout --theirs ` (stack-owned defaults) | | `package-lock.json` | `git checkout --theirs package-lock.json` — regenerate after `package.json` is resolved | @@ -109,7 +109,7 @@ while IFS= read -r f; do fi fi done < <(git ls-files modules/home modules/auth modules/users modules/tasks modules/uploads modules/billing lib config/defaults 2>/dev/null \ - | grep -v "/tests/" | grep -vE "\.(test|spec)\.js$") + | grep -vE "/(tests|__tests__)/" | grep -vE "\.(test|spec)\.(js|jsx|ts|tsx)$") [ "$drift_found" -eq 1 ] && exit 1 echo "3ter: no undeclared drift — OK"