baldart 4.45.0 → 4.46.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/CHANGELOG.md +16 -0
- package/VERSION +1 -1
- package/framework/.claude/skills/new/references/setup.md +3 -1
- package/framework/.claude/skills/worktree-manager/SKILL.md +87 -17
- package/framework/.claude/workflows/new2.js +5 -1
- package/framework/docs/PROJECT-CONFIGURATION.md +20 -0
- package/framework/templates/baldart.config.template.yml +18 -0
- package/package.json +1 -1
- package/src/commands/configure.js +28 -0
- package/src/commands/update.js +9 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to BALDART will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [4.46.0] - 2026-06-15
|
|
9
|
+
|
|
10
|
+
**Worktree env-file copy is unified onto one stack-agnostic config key, `stack.env_files` — closing the v4.42.0-deferred divergence (`/nw` copied `.env.local`+`.env`; new2's pre-flight copied `env/.env.local/.env.example/supabase/.temp`) WITHOUT the superset the adversarial pass had refuted.** A worktree is a fresh checkout, so the gitignored env artifacts a build needs must be copied from main — but the SET was hard-coded and divergent across paths. This release makes the set a single SSOT list (`stack.env_files`, default `['.env.local', '.env']`) read identically by `worktree-manager` (`/nw`), `/new`, and `new2`. A 3-skeptic **adversarial review before implementation** shaped the design: it (1) confirmed `framework/agents/runbook.md:36` (`cp .env.example .env`) is a documentation-template onboarding idiom — a DIFFERENT context — and **excluded** it; (2) refuted folding `supabase/.temp` into the copy set (it carries the remote project-ref → every copying worktree auto-links to the shared remote, the exact footgun `stack.schema_deploy_from_trunk_only` exists to prevent, and worse unattended in `/new` than in manual `/nw`; it is not even a build input); and (3) found the bash bug class fixed below. Crucially, the layer is **stack-agnostic**: the generic skill/installer/template never name Supabase or any stack — copying a tool's local-state directory is a per-project opt-in the user adds to their own `stack.env_files` / overlay, never a framework default. **MINOR** (additive: a new `baldart.config.yml` key, propagated end-to-end per the schema-change rule; no removed surface).
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`stack.env_files` config key** (`framework/templates/baldart.config.template.yml`) — list of gitignored env artifacts (files OR dirs) copied into each worktree, default `['.env.local', '.env']`. Files are copied with `cp` (dereferences symlinks — never `cp -P`, which would dangle a relative env symlink inside `.worktrees/`), directories with `cp -r` (mirrored: stale files removed on a `/nw` resume). A missing FILE is WARNed at copy time (never `exit 1` — that would strand `/new`'s programmatic path; the build gate is the real enforcement); nothing-copied escalates to a loud WARN. Replaces the old `cp … 2>/dev/null || true` that silently swallowed a missing critical env → cryptic later build failure.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **`framework/.claude/skills/worktree-manager/SKILL.md` — `/nw` step 4 copy loop reads `stack.env_files`** (file/dir-aware, WARN-not-fatal). The dev-server PORT is now written to the first FILE actually copied (not `ENV_FILES[0]`, which could be a directory — `echo >> dir` errors), falling back to `.env.local`. The env-sync staleness check (`/mw` step 2) compares the NEWEST mtime among all `stack.env_files` (portable `stat -f %m || stat -c %Y`, recursive for dirs) instead of only `.env.local`; the `/lw` status line, the port-reuse rule, the docs-mode "forbidden" list, and the Project-Context header are reconciled to the list. The skill stays **stack-agnostic** — it iterates the list and never names a stack.
|
|
19
|
+
- **`framework/.claude/workflows/new2.js` + `framework/.claude/skills/new/references/setup.md`** — the pre-flight worktree briefing stops hard-coding `env/.env.local/.env.example/supabase/.temp` and copies `stack.env_files` instead (new2 interpolates the list resolved from `args.config`). `.env.example` is dropped (tracked → already in the checkout).
|
|
20
|
+
- **`src/commands/configure.js`** — autodetects `stack.env_files` from the universal gitignored env conventions (`.env.local`/`.env`) only (stack-agnostic — no per-database special-casing), a comma-separated interactive prompt, and a summary-box line.
|
|
21
|
+
- **`src/commands/update.js`** — the `missingStack` detector now also catches top-level ARRAY stack keys (`stack.env_files` is `typeof 'object'`, so it needs `Array.isArray`); sub-object keys (`charting`/`animation`/`testing`) stay excluded (`Array.isArray({}) === false`). Without this the new key would silently never be asked on update.
|
|
22
|
+
- **`framework/docs/PROJECT-CONFIGURATION.md`** — documents `stack.env_files` (§4.4) as a stack-agnostic list.
|
|
23
|
+
|
|
8
24
|
## [4.45.0] - 2026-06-15
|
|
9
25
|
|
|
10
26
|
**The main-repo-root (`$MAIN`) resolution across `worktree-manager` + `/prd` + `/new` is unified onto one correct, `separate-git-dir`-safe canonical — fixing two latent bugs while *refuting* the obvious "unify on `--git-common-dir`" fix the deferred plan proposed.** v4.42.0 deferred the `$MAIN` cleanup after an adversarial pass flagged it as a likely trap; this release does it properly. The root resolution lived in ~5 forms across three genuine execution contexts (main-checkout cwd, worktree cwd, the `allocate-id.sh` startup), and the deferred plan wanted to collapse them onto `git rev-parse --git-common-dir` + `/..` (the recipe already in `allocate-id.sh` `resolve_main()`). A 3-skeptic adversarial review **before implementation** demolished that plan with git experiments: (1) `--git-common-dir` + parent returns the *parent of the git dir*, which is the WRONG directory under `git init --separate-git-dir` (the git dir lives outside the working tree) — so the "canonical" recipe was itself fragile, and `resolve_main()` only worked because BALDART repos use in-tree `.git`; (2) the alleged `prd/SKILL.md` Step-1 bug ("`--show-toplevel` from inside a worktree") does **not** exist — Step 1 runs only at fresh kickoff on the main checkout, and resume reads the persisted `main_path`, so `--show-toplevel` is correct there *and* is the only form that survives `separate-git-dir`; (3) `git worktree list` head is also not `separate-git-dir`-safe (returns the git dir). The corrected design keeps the task's **context classification** but fixes the **primitive**: every site resolves the root through `--show-toplevel` (the true working-tree root in all cases), using `git -C .. rev-parse --show-toplevel` from a worktree (its parent `.worktrees/` lives inside the main repo). `resolve_main()` is rewritten as the canonical reference (detects a linked worktree via `git-dir != git-common-dir`, walks one level up) — **byte-identical output to the old form for in-tree repos** (verified), correct for `separate-git-dir`, and fails cleanly (`return 1`) instead of emitting a garbage path. Two real fragilities fixed along the way: the `/mw` `$MAIN` fallback (`--git-common-dir` + `/..` under `git -C`, relative-base hazard + `separate-git-dir` break) and the `3b` card-sync (`--show-superproject-working-tree || pwd`, which returned the SUPERPROJECT for a real submodule and only worked otherwise by a cwd accident). All recipes dogfooded across normal + `separate-git-dir` repos in every cwd context. **MINOR** (hardening + bug fixes across skills/agents; no removed surface, **no new `baldart.config.yml` key** ⇒ schema-change propagation rule N/A).
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.
|
|
1
|
+
4.46.0
|
|
@@ -287,7 +287,9 @@
|
|
|
287
287
|
{ cards: [<all card IDs>], groupParent: <PARENT-ID|null>, slug: "<slug>" }
|
|
288
288
|
Let it: group cards, derive the branch from git_strategy.branch (fallback
|
|
289
289
|
feat/<PARENT-ID>-<slug>), create the worktree in .worktrees/, install deps,
|
|
290
|
-
copy env
|
|
290
|
+
copy the gitignored env artifacts in stack.env_files (files via cp, dirs via
|
|
291
|
+
cp -r; missing file → WARN, never abort), assign a free port, update
|
|
292
|
+
.worktrees/registry.json (all card
|
|
291
293
|
IDs in the `cards` field), and run the baseline (tsc + lint + build)
|
|
292
294
|
UNDER A HARD TIMEOUT — wrap the build step in `timeout 600 <build-cmd>`
|
|
293
295
|
(10 min) so a hung or interactive build cannot stall the pre-flight barrier.
|
|
@@ -20,6 +20,7 @@ description: >
|
|
|
20
20
|
- `git.merge_strategy` — `pr` (default, GitHub PR via `gh`) or `local-push` (direct fast-forward to `origin/<git.trunk_branch>`). Drives `/mw` step 4c.
|
|
21
21
|
- `paths.backlog_dir` — used for syncing untracked backlog cards (`/nw` step 3b).
|
|
22
22
|
- `paths.metrics` — JSONL telemetry dir (default `docs/metrics`) used by the rebase conflict-resolution table.
|
|
23
|
+
- `stack.env_files` — list of gitignored env artifacts (files OR dirs) copied into each worktree (`/nw` step 4). Default `['.env.local', '.env']`. The SAME list drives /nw, /new, and new2 — no per-path divergence. NEVER list a tracked file (already in the checkout). The skill is stack-agnostic — it just iterates the list; any project-specific entry (e.g. a tool's local-state dir) is the project's own opt-in in its config/overlay.
|
|
23
24
|
- `features.has_toolchain` + `toolchain.commands.{typecheck,lint,build,test}` — when the flag is `true`, the mechanical gates below run the consumer's configured commands verbatim instead of the hardcoded `npx tsc`/`npx eslint`/`npm run build` defaults (see "## Toolchain-aware gates"). Absent/`false` → defaults, identical to pre-toolchain behavior.
|
|
24
25
|
- Protocol reference: `framework/agents/project-context.md`. Skills must ASK when a needed key is missing — never assume.
|
|
25
26
|
|
|
@@ -108,7 +109,7 @@ backlog cards, design references — and never touch source code.
|
|
|
108
109
|
What docs mode SKIPS vs the standard flow:
|
|
109
110
|
- No `npm install` (no `node_modules/` inside the worktree)
|
|
110
111
|
- No port allocation (no dev server)
|
|
111
|
-
- No
|
|
112
|
+
- No env-artifact (`stack.env_files`) copy
|
|
112
113
|
- No `npm run build` baseline verification
|
|
113
114
|
- No lint / tsc baseline checks
|
|
114
115
|
- No `--dev` server bootstrap
|
|
@@ -240,7 +241,7 @@ git -C "$MAIN_ROOT" worktree add "$WORKTREE_REL" -b "$BRANCH" "origin/$TRUNK"
|
|
|
240
241
|
}
|
|
241
242
|
```
|
|
242
243
|
|
|
243
|
-
**Forbidden in docs mode**: `npm install`, port scan, `.env
|
|
244
|
+
**Forbidden in docs mode**: `npm install`, port scan, `.env*`/`stack.env_files` copy, `npm run build`,
|
|
244
245
|
`npx tsc`, `npx eslint`, dev server start, `git checkout`/`switch`/`branch` on main.
|
|
245
246
|
|
|
246
247
|
### mw-docs programmatic
|
|
@@ -633,8 +634,19 @@ npm install
|
|
|
633
634
|
# 2. Own .next cache — already isolated since each worktree has its own
|
|
634
635
|
# directory tree. The default .next inside the worktree is sufficient.
|
|
635
636
|
|
|
636
|
-
# 3. Copy environment
|
|
637
|
-
#
|
|
637
|
+
# 3. Copy environment artifacts from main repo root.
|
|
638
|
+
# The build typically requires gitignored env secrets, so this copy is
|
|
639
|
+
# critical — but the SET is project-specific and lives in `stack.env_files`
|
|
640
|
+
# (baldart.config.yml). Resolve that list and emit it LITERALLY below as a bash
|
|
641
|
+
# array (default ('.env.local' '.env') when the key is absent). List ONLY
|
|
642
|
+
# gitignored artifacts: a tracked file (e.g. an example env committed to git)
|
|
643
|
+
# is already in the worktree checkout — copying it is redundant. Entries may be
|
|
644
|
+
# FILES (cp) or DIRECTORIES (cp -r). The SAME list drives /nw, /new, and new2 —
|
|
645
|
+
# no per-path divergence. (A directory entry that carries remote-link/credential
|
|
646
|
+
# state makes the worktree act on the same remote as main; that is a per-project
|
|
647
|
+
# opt-in the user adds to stack.env_files / their overlay — the skill itself is
|
|
648
|
+
# stack-agnostic and just iterates the list.)
|
|
649
|
+
#
|
|
638
650
|
# cwd is the worktree; its parent (`.worktrees/`) lives inside the main repo,
|
|
639
651
|
# so `git -C ..` resolves the MAIN repo's toplevel — correct under a
|
|
640
652
|
# `--separate-git-dir` repo too (unlike `--git-common-dir`+parent). The old
|
|
@@ -642,8 +654,42 @@ npm install
|
|
|
642
654
|
# guess; fail loud instead. See § "Resolving the main repo root".
|
|
643
655
|
MAIN_ROOT="$(git -C .. rev-parse --show-toplevel 2>/dev/null)"
|
|
644
656
|
[ -n "$MAIN_ROOT" ] || { echo "ERROR: cannot resolve main repo root from worktree $(pwd)" >&2; exit 1; }
|
|
645
|
-
|
|
646
|
-
|
|
657
|
+
|
|
658
|
+
# <resolve stack.env_files and emit each entry quoted; default if the key is absent>
|
|
659
|
+
ENV_FILES=( ".env.local" ".env" )
|
|
660
|
+
copied_any=0
|
|
661
|
+
primary_env="" # first FILE actually copied — PORT target (step 4)
|
|
662
|
+
for ef in "${ENV_FILES[@]}"; do
|
|
663
|
+
src="$MAIN_ROOT/$ef"
|
|
664
|
+
dest_dir="$(dirname "$ef")"
|
|
665
|
+
if [ -d "$src" ]; then
|
|
666
|
+
# Directory artifact. cp -r MERGES into an existing
|
|
667
|
+
# dest, so mirror it: remove first to avoid stale files lingering on a /nw
|
|
668
|
+
# resume. Judge success by dest existence, NOT cp's exit code (BSD `cp -r`
|
|
669
|
+
# returns non-zero on a broken inner symlink while still copying the rest).
|
|
670
|
+
mkdir -p "$dest_dir"
|
|
671
|
+
rm -rf "$ef"
|
|
672
|
+
cp -R "$src" "$dest_dir"/ 2>/dev/null || true
|
|
673
|
+
[ -e "$ef" ] && copied_any=1 || echo "WARN: env dir '$ef' (stack.env_files) present in main but copy failed — worktree may be incomplete." >&2
|
|
674
|
+
elif [ -f "$src" ]; then
|
|
675
|
+
# File artifact. PLAIN cp (NEVER cp -P): it dereferences a symlinked source,
|
|
676
|
+
# so an `.env.local -> ../shared/.env` symlink yields real content here — a
|
|
677
|
+
# preserved relative symlink would dangle inside `.worktrees/feat-X/`.
|
|
678
|
+
mkdir -p "$dest_dir"
|
|
679
|
+
if cp "$src" "$ef"; then copied_any=1; [ -z "$primary_env" ] && primary_env="$ef"; fi
|
|
680
|
+
else
|
|
681
|
+
# Absent in main. A missing FILE is the critical case (build likely needs it):
|
|
682
|
+
# WARN loudly so a later cryptic build failure is diagnosable — but NEVER exit 1
|
|
683
|
+
# (this runs on /new's programmatic path; aborting would strand the batch — the
|
|
684
|
+
# build gate in step 5 is the real enforcement). A missing DIR is best-effort
|
|
685
|
+
# (the worktree just isn't linked, which is safe) — we cannot stat a nonexistent
|
|
686
|
+
# path to know it was a dir, so a single neutral WARN per entry covers both.
|
|
687
|
+
echo "WARN: env artifact '$ef' (stack.env_files) not found in $MAIN_ROOT — the worktree build may fail if it is required." >&2
|
|
688
|
+
fi
|
|
689
|
+
done
|
|
690
|
+
if [ "$copied_any" = 0 ] && [ "${#ENV_FILES[@]}" -gt 0 ]; then
|
|
691
|
+
echo "WARN: NO env artifacts copied into the worktree (none of: ${ENV_FILES[*]} exist in $MAIN_ROOT). If the build needs env vars it WILL fail — add the file(s) to the main repo or fix stack.env_files." >&2
|
|
692
|
+
fi
|
|
647
693
|
|
|
648
694
|
# 4. Pick a free dev server port (avoid 3000 used by main)
|
|
649
695
|
PORT=3001
|
|
@@ -651,11 +697,15 @@ while lsof -i :$PORT -sTCP:LISTEN >/dev/null 2>&1; do
|
|
|
651
697
|
PORT=$((PORT + 1))
|
|
652
698
|
if [ $PORT -gt 3099 ]; then echo "ERROR: No free port in 3001-3099" >&2; exit 1; fi
|
|
653
699
|
done
|
|
654
|
-
# Write PORT to
|
|
655
|
-
|
|
656
|
-
|
|
700
|
+
# Write PORT to the project's PRIMARY env file — the first FILE that was actually
|
|
701
|
+
# copied (NOT ENV_FILES[0], which could be a directory entry, where
|
|
702
|
+
# `echo >> dir` / `grep dir` error out). Fall back to .env.local when nothing was
|
|
703
|
+
# copied so a port is still persisted somewhere the dev server can read.
|
|
704
|
+
PORT_ENV_FILE="${primary_env:-.env.local}"
|
|
705
|
+
if grep -q "^PORT=" "$PORT_ENV_FILE" 2>/dev/null; then
|
|
706
|
+
sed -i '' "s/^PORT=.*/PORT=$PORT/" "$PORT_ENV_FILE"
|
|
657
707
|
else
|
|
658
|
-
echo "PORT=$PORT" >>
|
|
708
|
+
echo "PORT=$PORT" >> "$PORT_ENV_FILE"
|
|
659
709
|
fi
|
|
660
710
|
|
|
661
711
|
# 5. ASSERT git hooks are ACTIVE — not just readable.
|
|
@@ -776,12 +826,32 @@ fi
|
|
|
776
826
|
|
|
777
827
|
### 2. Env sync check
|
|
778
828
|
|
|
779
|
-
Compare
|
|
780
|
-
|
|
829
|
+
Compare the NEWEST modification time among the `stack.env_files` artifacts in the
|
|
830
|
+
main repo against the registry `envSyncedAt`. If any is newer, WARN. Use a
|
|
831
|
+
portable mtime read (`stat -f %m` is BSD/macOS, `stat -c %Y` is GNU/Linux — the
|
|
832
|
+
snippet runs on both) and scan directory entries recursively (a directory's own
|
|
833
|
+
mtime does NOT bump when a nested file is edited in place):
|
|
781
834
|
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
|
|
835
|
+
```bash
|
|
836
|
+
# Resolve stack.env_files the same way step 4 does; emit it literally here.
|
|
837
|
+
ENV_FILES=( ".env.local" ".env" )
|
|
838
|
+
mtime() { stat -f %m "$1" 2>/dev/null || stat -c %Y "$1" 2>/dev/null; }
|
|
839
|
+
newest=0
|
|
840
|
+
for ef in "${ENV_FILES[@]}"; do
|
|
841
|
+
src="$MAIN/$ef"
|
|
842
|
+
if [ -d "$src" ]; then
|
|
843
|
+
# newest mtime of any file inside the dir (recursive)
|
|
844
|
+
while IFS= read -r f; do m=$(mtime "$f"); [ "${m:-0}" -gt "$newest" ] && newest=$m; done \
|
|
845
|
+
< <(find "$src" -type f 2>/dev/null)
|
|
846
|
+
elif [ -f "$src" ]; then
|
|
847
|
+
m=$(mtime "$src"); [ "${m:-0}" -gt "$newest" ] && newest=$m
|
|
848
|
+
fi
|
|
849
|
+
done
|
|
850
|
+
SYNCED=$(mtime "$WORKTREE_PATH/.env-synced-marker" 2>/dev/null) # or parse registry envSyncedAt → epoch
|
|
851
|
+
if [ "${newest:-0}" -gt "${SYNCED:-0}" ]; then
|
|
852
|
+
echo "Warning: a main-repo env artifact (stack.env_files) changed since this worktree was created."
|
|
853
|
+
echo "Consider re-copying the env files into the worktree before merging."
|
|
854
|
+
fi
|
|
785
855
|
```
|
|
786
856
|
|
|
787
857
|
### 3. Pre-merge safety commit + checks inside the worktree
|
|
@@ -1314,7 +1384,7 @@ Active worktrees:
|
|
|
1314
1384
|
Age: 2 days
|
|
1315
1385
|
Status: 3 uncommitted changes
|
|
1316
1386
|
Build: verified ✓
|
|
1317
|
-
Env: in sync | STALE (main
|
|
1387
|
+
Env: in sync | STALE (a main env artifact changed)
|
|
1318
1388
|
|
|
1319
1389
|
2. BUG-0500 fix-auth
|
|
1320
1390
|
Branch: feat/BUG-0500-fix-auth
|
|
@@ -1418,5 +1488,5 @@ Cleanup complete:
|
|
|
1418
1488
|
- If merge conflicts during /mw, STOP and ask the user. Do not auto-resolve.
|
|
1419
1489
|
- Always verify `.worktrees/` is in `.gitignore` before creating it (the nw-docs pre-flight in step 0 enforces this; add it before running if absent).
|
|
1420
1490
|
- Commit lock protocol: Each worktree has its own `COMMIT_LOCK` in its git dir — no cross-worktree interference.
|
|
1421
|
-
- Port persistence: On dev server restart, reuse the port from registry (grep .env.local for PORT
|
|
1491
|
+
- Port persistence: On dev server restart, reuse the port from registry (or grep the worktree's primary env file — the first FILE entry of `stack.env_files` that was copied, default `.env.local` — for `PORT=`) instead of picking a new one.
|
|
1422
1492
|
- **NEVER use `git stash` in worktrees.** Stashes are globally shared across all worktrees (`refs/stash` via `git.commondir`). A stash created in one worktree or in the main repo can be popped in another, causing conflicts, data loss, and cascading merge failures (see FEAT-0522 incident). Use explicit file staging only — the file ownership map ensures no overlap between cards. The stash pattern in AGENTS.md/CLAUDE.md applies ONLY to the main repo working tree.
|
|
@@ -62,6 +62,10 @@ const paths = cfg.paths || {}
|
|
|
62
62
|
const gitCfg = cfg.git || {}
|
|
63
63
|
const stack = cfg.stack || {}
|
|
64
64
|
const highRisk = paths.high_risk_modules || []
|
|
65
|
+
// Gitignored env artifacts copied into the worktree (v4.46.0). SAME contract as
|
|
66
|
+
// /nw + /new (worktree-manager step 4): the SSOT is stack.env_files; never list a
|
|
67
|
+
// tracked file (.env.example is already in the checkout). Default minimal set.
|
|
68
|
+
const ENV_FILES = (Array.isArray(stack.env_files) && stack.env_files.length) ? stack.env_files : ['.env.local', '.env']
|
|
65
69
|
const mergeStrategy = gitCfg.merge_strategy || 'pr'
|
|
66
70
|
// Stack-agnostic schema-deploy safety invariant (v4.33.0). When true, a remote
|
|
67
71
|
// schema/RLS/index/migration deploy is auto-executed ONLY from the trunk branch;
|
|
@@ -260,7 +264,7 @@ try {
|
|
|
260
264
|
`ROLE BOUNDARY (specialization integrity): you are the OPS/GIT agent. You NEVER edit source or doc files — any needed content change belongs to the coder specialist; report it instead.\n\n` +
|
|
261
265
|
`DETERMINISTIC GATE POLICIES (NO user prompts):\n` +
|
|
262
266
|
`• G1 dirty-tree (main repo ${MAIN}): partition framework-managed noise exactly as setup.md step 3 ($METRICS=${METRICS}, .baldart/generated|state.json|skill-conflicts.json — NOT overlays/). Genuine user work → auto-stash 'baldart-new2-${firstCard}' (main checkout) and record the label. Never commit/abort/prompt.\n` +
|
|
263
|
-
`• Worktree (setup.md step 4): create ONE code worktree off ${TRUNK}; install deps; assign a port; run the baseline (tsc+lint+build). Copy
|
|
267
|
+
`• Worktree (setup.md step 4): create ONE code worktree off ${TRUNK}; install deps; assign a port; run the baseline (tsc+lint+build). Copy the gitignored env artifacts in stack.env_files (resolved: ${JSON.stringify(ENV_FILES)}) — FILES via \`cp\` (plain cp, dereferences symlinks; never cp -P), DIRS via \`cp -r\`; a missing FILE → WARN (never abort). Do NOT add .env.example (tracked → already in the checkout) and do NOT bulk-copy untracked files from the main repo (avoids stray backlog cards in the worktree). Use the git-authoritative idempotency pre-check. E2: baseline FAILS → do NOT fix it yourself (role boundary — the coder specialist repairs it); return baseline:'fail' + a baselineLog precise enough for a coder to act (failing command, error excerpt, suspect files). Wrap the build in \`timeout 600 <build-cmd>\` (10 min); if killed, return baseline:'timeout' + the partial log. On baseline PASS, VERIFY the worktree on disk and return EVIDENCE (not just a flag): set worktreeVerified:true ONLY after running \`git -C ${MAIN} worktree list --porcelain\` (the worktree path MUST appear in the output) AND \`test -d <wt>/node_modules\` AND confirming the branch; put the LITERAL stdout into worktreeEvidence{ worktreeListPorcelain, artifactsLs:\`ls -la <wt>/node_modules <wt>/.next 2>/dev/null | head\`, baselineLogTail }. The workflow string-matches this evidence — NEVER report worktreeVerified:true without actually running the commands.\n` +
|
|
264
268
|
codexResolveBullet +
|
|
265
269
|
g3Bullet +
|
|
266
270
|
`• G4 card-field validation (setup.md 1b/1c): card missing requirements/acceptance_criteria/files_likely_touched → EXCLUDE (excluded[] + reason). Never HALT for one bad card.\n` +
|
|
@@ -162,6 +162,12 @@ stack:
|
|
|
162
162
|
|
|
163
163
|
# Schema-deploy safety invariant (since v4.33.0). Default false (no-op).
|
|
164
164
|
schema_deploy_from_trunk_only: false # true → /new + new2 auto-deploy schema only from git.trunk_branch
|
|
165
|
+
|
|
166
|
+
# Gitignored env artifacts copied into each git worktree (v4.46.0).
|
|
167
|
+
env_files: # default ['.env.local', '.env']
|
|
168
|
+
- .env.local
|
|
169
|
+
- .env
|
|
170
|
+
# - some/state-dir # a DIR entry is copied with cp -r (project-specific opt-in)
|
|
165
171
|
```
|
|
166
172
|
|
|
167
173
|
Skills propose only `canonical` libraries and refuse `forbidden` ones. Empty
|
|
@@ -196,6 +202,20 @@ the skill/agent set:
|
|
|
196
202
|
multiple cards in parallel worktrees against one shared remote datastore. The actual
|
|
197
203
|
command + env loader stay in the project overlay — core only enforces the branch gate.
|
|
198
204
|
`baldart configure` defaults it to `true` when it detects parallel worktree usage.
|
|
205
|
+
- `stack.env_files` (list, v4.46.0+, default `['.env.local', '.env']`) is the
|
|
206
|
+
SSOT for **gitignored env artifacts copied into each git worktree** by
|
|
207
|
+
`worktree-manager` (`/nw`), reused identically by `/new` and `new2` — no
|
|
208
|
+
per-path divergence. A worktree is a fresh checkout, so any gitignored env the
|
|
209
|
+
build needs must be copied from main. **List only gitignored artifacts** — a
|
|
210
|
+
tracked example env is already present in every checkout. Entries may be files
|
|
211
|
+
(copied with `cp`, which dereferences symlinks) or directories (copied with
|
|
212
|
+
`cp -r`). The mechanism is **stack-agnostic**: add whatever your project needs.
|
|
213
|
+
A directory that carries remote-link/credential state makes the worktree act on
|
|
214
|
+
the same remote as main — add it intentionally (and consider pairing with
|
|
215
|
+
`schema_deploy_from_trunk_only: true`). Such project-specific opinions live in
|
|
216
|
+
the project's own config value / overlay, not in the generic framework. A
|
|
217
|
+
missing FILE is WARNed at copy time (never fatal); a build that truly needs it
|
|
218
|
+
fails clearly.
|
|
199
219
|
- `security-reviewer` evaluates access rules per `stack.database`
|
|
200
220
|
(Firestore rules, Supabase RLS, Mongo validators, DynamoDB IAM,
|
|
201
221
|
Postgres RLS+GRANT).
|
|
@@ -122,6 +122,24 @@ stack:
|
|
|
122
122
|
# "render" | "fly" | "self-hosted" | "none" | "".
|
|
123
123
|
deployment: ""
|
|
124
124
|
|
|
125
|
+
# Gitignored environment artifacts copied into each git worktree (since
|
|
126
|
+
# v4.46.0). A worktree is a fresh checkout, so any GITIGNORED env file/dir the
|
|
127
|
+
# build/run needs (it is NOT in the checkout) must be copied from the main repo.
|
|
128
|
+
# worktree-manager (`/nw`) reads this list; `/new` + new2 reuse the SAME list
|
|
129
|
+
# (no per-path divergence). Rules:
|
|
130
|
+
# - List ONLY gitignored artifacts. A tracked file (e.g. an example env
|
|
131
|
+
# committed to git) is already in every checkout — listing it is redundant.
|
|
132
|
+
# - Entries may be FILES (copied with `cp`) or DIRECTORIES (copied with
|
|
133
|
+
# `cp -r`). Add any project-specific entry your stack needs (e.g. a CLI's
|
|
134
|
+
# gitignored local-state dir). NOTE: a directory carrying remote-link or
|
|
135
|
+
# credential state makes the worktree act on the same remote as main — add
|
|
136
|
+
# such a dir intentionally (and consider schema_deploy_from_trunk_only).
|
|
137
|
+
# - A missing FILE here is WARNed at copy time (never fatal); a build that
|
|
138
|
+
# truly needs it fails clearly. Default: the minimal secret set.
|
|
139
|
+
env_files:
|
|
140
|
+
- .env.local
|
|
141
|
+
- .env
|
|
142
|
+
|
|
125
143
|
# Schema-deploy-from-trunk-only safety invariant (since v4.33.0). Stack-agnostic
|
|
126
144
|
# guard against migration-history desync / "code-ahead-of-schema" production
|
|
127
145
|
# outages on projects that develop multiple cards in PARALLEL git worktrees
|
package/package.json
CHANGED
|
@@ -384,6 +384,17 @@ function detect(cwd = process.cwd()) {
|
|
|
384
384
|
else if (exists('app.yaml') || exists('cloudbuild.yaml')) detectedDeployment = 'gcp';
|
|
385
385
|
// Note: bare Dockerfile → leave empty; user picks "self-hosted" or actual target.
|
|
386
386
|
|
|
387
|
+
// ---- Worktree env artifacts (stack.env_files) --------------------------
|
|
388
|
+
// GITIGNORED env files that must be copied into each worktree (they are NOT in
|
|
389
|
+
// the git checkout). Probe only the UNIVERSAL gitignored env conventions
|
|
390
|
+
// (.env.local / .env) — never a tracked file like .env.example, and never a
|
|
391
|
+
// stack-specific artifact (those are a per-PROJECT opinion: a project that
|
|
392
|
+
// needs e.g. a tool's local-state dir adds it to stack.env_files itself / via
|
|
393
|
+
// its overlay; the generic installer stays stack-agnostic). Fall back to the
|
|
394
|
+
// minimal set so the list is never empty.
|
|
395
|
+
const detectedEnvFiles = ['.env.local', '.env'].filter((f) => exists(f));
|
|
396
|
+
if (detectedEnvFiles.length === 0) detectedEnvFiles.push('.env.local', '.env');
|
|
397
|
+
|
|
387
398
|
const detected = {
|
|
388
399
|
paths: {
|
|
389
400
|
design_system: designSystemPath,
|
|
@@ -433,6 +444,8 @@ function detect(cwd = process.cwd()) {
|
|
|
433
444
|
// Stack-agnostic schema-deploy safety invariant (v4.33.0). Defaulted ON when
|
|
434
445
|
// parallel worktrees are detected (the desync-prone topology), else false.
|
|
435
446
|
schema_deploy_from_trunk_only: usesWorktrees,
|
|
447
|
+
// Gitignored env artifacts copied into each worktree (v4.46.0).
|
|
448
|
+
env_files: detectedEnvFiles,
|
|
436
449
|
// New: surface the detected monorepo + DS signals so skills can read them.
|
|
437
450
|
monorepo: isMonorepo ? {
|
|
438
451
|
detected: true,
|
|
@@ -1057,6 +1070,20 @@ async function interactivePrompts(merged, detected) {
|
|
|
1057
1070
|
'confirm'
|
|
1058
1071
|
);
|
|
1059
1072
|
|
|
1073
|
+
// Worktree env artifacts (stack.env_files, v4.46.0). Gitignored files/dirs
|
|
1074
|
+
// copied into each worktree (stack-agnostic — the user lists whatever their
|
|
1075
|
+
// project needs; a directory entry is copied recursively).
|
|
1076
|
+
const envFilesDefault = (Array.isArray(merged.stack.env_files) && merged.stack.env_files.length
|
|
1077
|
+
? merged.stack.env_files
|
|
1078
|
+
: detected.stack.env_files) || ['.env.local', '.env'];
|
|
1079
|
+
const envFilesAns = await promptForKey(
|
|
1080
|
+
'Gitignored env files/dirs copied into each worktree — comma-separated (gitignored only; NEVER a tracked file like .env.example; a directory entry is copied recursively)',
|
|
1081
|
+
envFilesDefault.join(',')
|
|
1082
|
+
);
|
|
1083
|
+
merged.stack.env_files = envFilesAns
|
|
1084
|
+
? envFilesAns.split(',').map((s) => s.trim()).filter(Boolean)
|
|
1085
|
+
: [];
|
|
1086
|
+
|
|
1060
1087
|
return merged;
|
|
1061
1088
|
}
|
|
1062
1089
|
|
|
@@ -1141,6 +1168,7 @@ async function configure(opts = {}) {
|
|
|
1141
1168
|
`Auth provider: ${detected.stack.auth_provider || '—'}`,
|
|
1142
1169
|
`Framework: ${detected.stack.framework || '—'}`,
|
|
1143
1170
|
`Deployment: ${detected.stack.deployment || '—'}`,
|
|
1171
|
+
`Worktree env: ${(detected.stack.env_files || []).join(', ') || '—'}`,
|
|
1144
1172
|
]);
|
|
1145
1173
|
|
|
1146
1174
|
if (opts.nonInteractive) {
|
package/src/commands/update.js
CHANGED
|
@@ -1285,12 +1285,16 @@ async function update(options = {}, unknownArgs = []) {
|
|
|
1285
1285
|
const missingGit = Object.keys(tpl.git || {})
|
|
1286
1286
|
.filter((k) => !(k in (cur2.git || {})));
|
|
1287
1287
|
// Scalar top-level stack.* keys — e.g. stack.database,
|
|
1288
|
-
// stack.auth_provider, stack.framework, stack.deployment (strings)
|
|
1289
|
-
// stack.schema_deploy_from_trunk_only (boolean, since v4.33.0)
|
|
1290
|
-
// keys (
|
|
1291
|
-
//
|
|
1288
|
+
// stack.auth_provider, stack.framework, stack.deployment (strings),
|
|
1289
|
+
// stack.schema_deploy_from_trunk_only (boolean, since v4.33.0) AND
|
|
1290
|
+
// top-level ARRAY keys (stack.env_files, v4.46.0 —
|
|
1291
|
+
// `typeof [] === 'object'`, so it needs Array.isArray to be caught;
|
|
1292
|
+
// without it the new key would silently never be asked). Sub-OBJECT
|
|
1293
|
+
// keys (charting, animation, testing) stay skipped — Array.isArray({})
|
|
1294
|
+
// is false, so they are not flagged — their drift is handled by
|
|
1295
|
+
// individual sub-prompts in configure.
|
|
1292
1296
|
const missingStack = Object.keys(tpl.stack || {})
|
|
1293
|
-
.filter((k) => ['string', 'boolean'].includes(typeof tpl.stack[k]))
|
|
1297
|
+
.filter((k) => ['string', 'boolean'].includes(typeof tpl.stack[k]) || Array.isArray(tpl.stack[k]))
|
|
1294
1298
|
.filter((k) => !(k in (cur2.stack || {})));
|
|
1295
1299
|
// Top-level nested config namespaces (e.g. `graph:` since v4.21.0).
|
|
1296
1300
|
// Unlike `lsp:` (which propagates only via its `has_lsp_layer` flag),
|