baldart 4.43.1 → 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 +44 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/prd-card-writer.md +2 -1
- package/framework/.claude/agents/prd.md +3 -1
- package/framework/.claude/skills/bug/SKILL.md +3 -1
- package/framework/.claude/skills/new/SKILL.md +24 -6
- package/framework/.claude/skills/new/references/commit.md +1 -1
- package/framework/.claude/skills/new/references/completeness.md +1 -1
- package/framework/.claude/skills/new/references/final-review.md +1 -1
- package/framework/.claude/skills/new/references/implement.md +1 -0
- package/framework/.claude/skills/new/references/review-cycle.md +2 -2
- package/framework/.claude/skills/new/references/setup.md +4 -2
- package/framework/.claude/skills/new/references/team-mode.md +3 -2
- package/framework/.claude/skills/prd/references/validation-phase.md +1 -1
- package/framework/.claude/skills/simplify/SKILL.md +7 -1
- package/framework/.claude/skills/worktree-manager/SKILL.md +238 -35
- package/framework/.claude/skills/worktree-manager/scripts/allocate-id.sh +23 -9
- package/framework/.claude/workflows/new2.js +5 -1
- package/framework/agents/toolchain-protocol.md +15 -0
- 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,50 @@ 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
|
+
|
|
24
|
+
## [4.45.0] - 2026-06-15
|
|
25
|
+
|
|
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).
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **`framework/.claude/skills/worktree-manager/scripts/allocate-id.sh` — `resolve_main()` rewritten `--show-toplevel`-based.** Now resolves the working-tree root (correct under `git init --separate-git-dir`, where `--git-common-dir` + parent pointed at the wrong directory), detects a linked worktree (`git-dir != git-common-dir`) and walks one level up. Identical output to the prior form for in-tree repos; returns non-zero instead of a garbage path when not in a git repo.
|
|
31
|
+
- **`framework/.claude/skills/worktree-manager/SKILL.md` — `/mw` step 4c `$MAIN` fallback** no longer derives the root via `git -C "$WORKTREE_PATH" rev-parse --git-common-dir` + `/..` (parent-of-git-dir, wrong under a separate git dir; appending `/..` to a possibly-relative `--git-common-dir` under `git -C` resolves against the wrong base). It `cd`s into the worktree then `git -C .. rev-parse --show-toplevel`, with a loud guard replacing the silent failure path.
|
|
32
|
+
- **`framework/.claude/skills/worktree-manager/SKILL.md` — `3b. Sync untracked cards`** drops the `git -C "$WORKTREE_PATH" --show-superproject-working-tree || pwd` form (returned the *superproject* for a real git submodule, where the cards do not live; only resolved otherwise by relying on cwd). At step 3b cwd is still the main checkout, so it now uses `git rev-parse --show-toplevel` — correct for normal, submodule, and `separate-git-dir` repos.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **`framework/.claude/skills/worktree-manager/SKILL.md` — new `## Resolving the main repo root` section** documenting the three execution contexts, the correct primitive per context, and an explicit "do NOT use `--git-common-dir` + `/..`" rule (codifying the adversarial finding so the next maintainer doesn't re-attempt the refuted unification). The `/nw` step-1, nw-docs step-0, and `/nw` env-copy sites gain cross-ref comments; env-copy keeps `git -C .. --show-toplevel` but replaces its silent `|| echo '../..'` fallback with a loud guard.
|
|
37
|
+
- **`framework/.claude/skills/prd/references/validation-phase.md` + `framework/.claude/agents/{prd,prd-card-writer}.md`** — the legacy `$MAIN` fallback and the descriptive `$MAIN` comments switch from `--git-common-dir` to the canonical `--show-toplevel` resolution (and point at the new section). `prd/SKILL.md` Step 1 is deliberately **unchanged** — `--show-toplevel` there is correct (refuted "bug").
|
|
38
|
+
- **`framework/.claude/skills/new/references/setup.md` — `$MAIN` resolution corrected**: from a worktree-invocation, resolve via the worktree's parent toplevel rather than `--show-superproject-working-tree` (a submodule primitive) or `git worktree list` (not `separate-git-dir`-safe).
|
|
39
|
+
|
|
40
|
+
## [4.44.0] - 2026-06-15
|
|
41
|
+
|
|
42
|
+
**The toolchain layer (v4.41.0) now reaches the *execution-side* gates too: the worktree baseline/merge builds and the classic `/new` reference suite stop hard-coding `npx tsc`/`npx eslint`/`npm run build` and run the consumer's configured `toolchain.commands.*` instead.** v4.41.0 wired the *review* gates (`qa-sentinel`, `coder`, the `/new`+`/new2` review workflows) through `toolchain.commands.*`, but a dozen gates that actually run a build/lint/typecheck were still hard-coded — so a Biome/Vitest consumer's worktree baseline silently ran `eslint`, and the classic `/new` per-card + final gates ran `npm run build` regardless of config. This release makes those gates toolchain-aware while preserving each site's existing **severity** policy (a configured command failing is the same STOP/continue verdict the default produced — the protocol's no-fallback rule governs only *which* command runs, never what to do with its exit code). The mechanism follows the **execution context**: a markdown **prose note** where a full model writes the gate command (the `/new` references, `/bug`, `/simplify`), and an inline **shell resolver** in `worktree-manager` — which has no `args.config` and whose baseline runs in a weak/background subagent, so it resolves `toolchain.commands.*` from `baldart.config.yml` on disk the same way it already resolves `git.trunk_branch`, rather than relying on a prose note a weak model could skip. The design was adversarially reviewed before implementation (3 skeptics), which corrected the initial plan on three points: (1) the `/mw` best-effort `npm run test 2>/dev/null || true` keeps its `|| true` swallow (never a STOP trigger); (2) the `/mw` changed-files lint scope is preserved as the default (a configured whole-tree command like `biome check .` runs by the consumer's contract); (3) two missed twins — `/bug` and `/simplify` verify steps — were added to scope. **Declared debt (intentionally untouched):** `npm install` stays hard-coded (there is no `toolchain.commands.install` key — adding one would be a 5-layer schema change; keeping this MINOR), `markdownlint` is not in the curated map, and the descriptive `(tsc+lint+build)` labels in `new2.js`/`setup.md` are not load-bearing (the `new2` projectBrief already injects the verbatim instruction — refuted by review). **MINOR** (additive: more gates honor the existing `features.has_toolchain` + `toolchain.commands.*`; no removed surface, **no new `baldart.config.yml` key** ⇒ schema-change propagation rule N/A).
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **`framework/.claude/skills/worktree-manager/SKILL.md` — gates toolchain-aware via an inline shell resolver.** New `## Toolchain-aware gates` convention + a Project-Context dependency on `features.has_toolchain` + `toolchain.commands.{typecheck,lint,build,test}`. Each gate block (`/nw` step 5 baseline, `/mw` step 2 pre-merge, step 4b rebase build, step 5 post-merge) inlines a `_tc()` resolver that reads the config from disk (mirroring the existing `git.trunk_branch`/`git.merge_strategy` grep idiom) and runs the configured command via `eval "${VAR:-<default>}"`, falling back to the hard-coded default when unset/flag-off. Three carve-outs documented: `npm install` is not a gate key, severity is unchanged, and the `/mw` best-effort `test` line keeps its `|| true` swallow.
|
|
47
|
+
- **`framework/.claude/skills/new/SKILL.md` — new `## Toolchain gates` core invariant** (cited as `§ "Toolchain gates"` by the reference modules, same pattern as `§ "Context economy"`): when `has_toolchain`, the mechanical gates use `toolchain.commands.<gate>` verbatim, a non-zero configured command is a real FAIL (no masking fallback), and `npm install`/`markdownlint` are not toolchain keys.
|
|
48
|
+
- **`framework/.claude/skills/new/references/{implement,review-cycle,completeness,final-review,commit,team-mode}.md` — gate sites cite `§ "Toolchain gates"`** so the classic `/new` per-card (Phase 1 verify, Phase 2.55/2.5x re-runs, Phase 3 doc re-verify, completeness gap re-run, commit pre-check), team-mode (per-card coder briefing, group build, group simplify re-run), and final-review build all resolve `toolchain.commands.*` verbatim when the flag is on.
|
|
49
|
+
- **`framework/.claude/skills/{bug,simplify}/SKILL.md` — standalone verify steps toolchain-aware** (the two twins surfaced by the adversarial scope review): `/bug` PHASE 5 regression checks and `/simplify` Step 5 verify gain a self-contained Toolchain note + a Project-Context dependency on `features.has_toolchain` + `toolchain.commands.{lint,typecheck,test}`.
|
|
50
|
+
- **`framework/agents/toolchain-protocol.md` — Consumers section updated** to record the execution-side gates and the two resolution mechanisms (prose note vs on-disk shell resolver), and to restate the `npm install`/`markdownlint` carve-outs.
|
|
51
|
+
|
|
8
52
|
## [4.43.1] - 2026-06-15
|
|
9
53
|
|
|
10
54
|
**The npm publish workflow is now race-safe: pushing several `v*.*.*` tags close together can no longer leave `latest` on a lower version.** Publishing v4.41.0/4.42.0/4.43.0 together exposed the bug — the three tag-push workflow runs executed in **parallel**, and `npm publish` (with no explicit dist-tag) points `latest` at whichever version finishes **last chronologically**, not the highest. Real outcome: `latest` landed on **4.42.0**, so `npx baldart` installed a non-latest version (fixed by hand with `npm dist-tag add baldart@4.43.0 latest`). This release closes the race two ways: (1) a workflow-level **`concurrency` group** (`group: publish-npm`, `cancel-in-progress: false`) serializes all publish runs so they never race on the dist-tag and a publish is never cancelled; (2) a new final step **reconciles `latest` to the highest published version** — it computes the max stable semver across `npm view baldart versions` (unioned with the just-published `VERSION` to survive registry propagation lag, numeric per-segment compare so `4.10.0 > 4.9.0`) and repoints `latest` only when it has drifted. Because the runs are serialized, the last run always leaves `latest` on the maximum regardless of push order. The existing "Verify tag matches VERSION" guard is untouched. **PATCH** (CI/release-machinery bugfix, no change to installed surface or `baldart.config.yml` ⇒ schema-change propagation rule N/A).
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.
|
|
1
|
+
4.46.0
|
|
@@ -311,7 +311,8 @@ applies even when N=1.
|
|
|
311
311
|
worktree on this machine (lock + shared high-water mark under `.worktrees/`):
|
|
312
312
|
|
|
313
313
|
```bash
|
|
314
|
-
# $MAIN is the main repo root
|
|
314
|
+
# $MAIN is the persisted main repo root (see worktree-manager § "Resolving the
|
|
315
|
+
# main repo root" — via --show-toplevel, NOT --git-common-dir).
|
|
315
316
|
ALLOC="$MAIN/.claude/skills/worktree-manager/scripts/allocate-id.sh"
|
|
316
317
|
if [ -x "$ALLOC" ]; then
|
|
317
318
|
N="$("$ALLOC" reserve FEAT "<slug>")" # e.g. prints 0024
|
|
@@ -504,7 +504,9 @@ parallelism (concurrent sessions on sibling worktrees both pick the same "next"
|
|
|
504
504
|
integer and conflict at merge):
|
|
505
505
|
|
|
506
506
|
```bash
|
|
507
|
-
# $MAIN =
|
|
507
|
+
# $MAIN = the persisted main repo root (the same value SKILL.md Step 1 wrote; see
|
|
508
|
+
# worktree-manager § "Resolving the main repo root" — resolved via --show-toplevel,
|
|
509
|
+
# NOT --git-common-dir, which breaks under git init --separate-git-dir).
|
|
508
510
|
ALLOC="$MAIN/.claude/skills/worktree-manager/scripts/allocate-id.sh"
|
|
509
511
|
[ -x "$ALLOC" ] && N="$("$ALLOC" reserve FEAT <slug>") # also BUG | UI | DOC | PERF
|
|
510
512
|
```
|
|
@@ -17,7 +17,7 @@ Argument: optional bug description (e.g., `/bug feature X is not saving`).
|
|
|
17
17
|
|
|
18
18
|
## Project Context
|
|
19
19
|
|
|
20
|
-
**Reads from `baldart.config.yml`:** `paths.design_system`, `paths.references_dir`, `paths.wiki_dir` (Phase 0 `rg` lookup target).
|
|
20
|
+
**Reads from `baldart.config.yml`:** `paths.design_system`, `paths.references_dir`, `paths.wiki_dir` (Phase 0 `rg` lookup target), `features.has_toolchain` + `toolchain.commands.{lint,typecheck,test}` (the PHASE 5 regression gates run those verbatim when the flag is on).
|
|
21
21
|
**Gated by features:** `features.has_design_system` (when `true`, Phase 4's design-system reads become BLOCKING for UI-touching bugs).
|
|
22
22
|
**Overlay:** loads `.baldart/overlays/bug.md` if present — project-specific debug entry points (e.g. SWR debug switches, env summary helpers, error-code modules). The base skill stays generic; project-specific code paths live in the overlay.
|
|
23
23
|
**On missing/empty keys:** ask the user; do not assume defaults. See `framework/agents/project-context.md` § 3.
|
|
@@ -202,6 +202,8 @@ If the project exposes an env-summary helper (listed in `.baldart/overlays/bug.m
|
|
|
202
202
|
|
|
203
203
|
## PHASE 5: VERIFY & CLEAN UP
|
|
204
204
|
|
|
205
|
+
> **Toolchain:** when `features.has_toolchain: true` in `baldart.config.yml`, run `toolchain.commands.{typecheck,lint,test}` **verbatim** instead of the defaults below — per `framework/agents/toolchain-protocol.md`. Empty/absent key (or flag off) → the default. A configured command that exits non-zero is a real FAIL (do not fall back).
|
|
206
|
+
|
|
205
207
|
1. Reproduce original scenario — confirm fix
|
|
206
208
|
2. Check regressions: `npx tsc --noEmit` + `npx eslint --max-warnings=0 <changed-files>`
|
|
207
209
|
3. If tests exist: `npm run test`
|
|
@@ -243,6 +243,24 @@ baselines. Keep that bulk on disk and pass **paths**, not bodies.
|
|
|
243
243
|
|
|
244
244
|
---
|
|
245
245
|
|
|
246
|
+
## Toolchain gates
|
|
247
|
+
|
|
248
|
+
When `features.has_toolchain: true` in `baldart.config.yml`, every mechanical gate
|
|
249
|
+
this skill runs (`lint`, `typecheck`, `test`, `build`) uses the consumer's
|
|
250
|
+
configured command from `toolchain.commands.<gate>` **verbatim** instead of the
|
|
251
|
+
`npm`/`npx` default shown at the call site — per `framework/agents/toolchain-protocol.md`.
|
|
252
|
+
Resolve per gate: a non-empty `toolchain.commands.{lint,typecheck,test,build}` wins;
|
|
253
|
+
empty/absent (or the flag off/missing) → the default at the call site, identical to
|
|
254
|
+
pre-toolchain behavior. A configured command that exits non-zero is a real gate
|
|
255
|
+
**FAIL** — do NOT then run the default (that would mask the failure); fall back only
|
|
256
|
+
when the key is **unset**. `npm install` and `markdownlint` are NOT toolchain keys
|
|
257
|
+
(there is no `commands.install`, and markdownlint is not in the curated map) — they
|
|
258
|
+
stay as written. The gate-output discipline (§ "Context economy" → redirect to disk,
|
|
259
|
+
surface only the exit code) applies to the resolved command exactly as to the default.
|
|
260
|
+
Reference modules cite this section as `§ "Toolchain gates"`.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
246
264
|
|
|
247
265
|
## Routing — il per-card pipeline e i moduli on-demand
|
|
248
266
|
|
|
@@ -250,9 +268,9 @@ baselines. Keep that bulk on disk and pass **paths**, not bodies.
|
|
|
250
268
|
sistema, ri-letto a OGNI turno. Per non pagare 60k+ token di istruzioni di fase a
|
|
251
269
|
ogni turno, il dettaglio passo-passo di ogni fase vive in un **modulo `references/<x>.md`**
|
|
252
270
|
caricato on-demand. Questo file (il core) tiene solo gli invarianti cross-fase
|
|
253
|
-
(Context Tracking, Progress Visibility, § "Context economy",
|
|
254
|
-
Profile, Trivial fast-lane, Risk-signal detector, Fix Application
|
|
255
|
-
mappa di navigazione.
|
|
271
|
+
(Context Tracking, Progress Visibility, § "Context economy", § "Toolchain gates",
|
|
272
|
+
Agent Routing, QA Profile, Trivial fast-lane, Risk-signal detector, Fix Application
|
|
273
|
+
Log) + questa mappa di navigazione.
|
|
256
274
|
|
|
257
275
|
> **HARD RULE — leggi il modulo PRIMA di eseguire la fase.** Quando entri in una
|
|
258
276
|
> fase, **Read** il suo modulo `references/<x>.md` e poi eseguilo. Eseguire una
|
|
@@ -260,9 +278,9 @@ mappa di navigazione.
|
|
|
260
278
|
> compaction che l'ha evacuato) è una violazione di protocollo: ricaricalo. Registra
|
|
261
279
|
> nel tracker, sotto `## Current Card`, il campo `phase_module_loaded: <modulo>` al
|
|
262
280
|
> caricamento, così la § "Context recovery protocol" sa cosa ri-leggere dopo una
|
|
263
|
-
> compaction. I `§ "..."` citati dai moduli (Context economy,
|
|
264
|
-
> Trivial-card fast-lane, Risk-signal detector, Fix Application Log) puntano a
|
|
265
|
-
> che vivono **qui nel core** → risolvono sempre.
|
|
281
|
+
> compaction. I `§ "..."` citati dai moduli (Context economy, Toolchain gates, Context
|
|
282
|
+
> Tracking, Trivial-card fast-lane, Risk-signal detector, Fix Application Log) puntano a
|
|
283
|
+
> sezioni che vivono **qui nel core** → risolvono sempre.
|
|
266
284
|
|
|
267
285
|
**Sequenza (per ogni card, in ordine — i moduli per-card sono caricati per traversata):**
|
|
268
286
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
> Sequential-mode global step numbering resumes here at 26 (Phase 3.5 ended at 25; Phase 3.7 used its own local C.0–C.6 counter). The tracker phase-string `4-commit` therefore maps to step 26, NOT a second step 25.
|
|
8
8
|
|
|
9
|
-
26. **Update tracker**: phase = "4-commit". **Entry assertion** — before committing, verify the Phase 3.7 e2e re-run obligation was honored: read the tracker for `e2e-rerun: triggered` / `e2e-rerun: not-needed`. If Phase 3.7 touched UI files but no `e2e-rerun` entry exists, do NOT commit yet — go run the re-run per Phase 3.7 step 6 first. Also confirm Phase 3.5/3.7 fixes did not leave lint/tsc broken: if the Phase 3.7 fix sub-loop applied any patch, run `npm run lint` + `npx tsc --noEmit` (when typescript) once before committing (redirect to disk per § "Context economy").
|
|
9
|
+
26. **Update tracker**: phase = "4-commit". **Entry assertion** — before committing, verify the Phase 3.7 e2e re-run obligation was honored: read the tracker for `e2e-rerun: triggered` / `e2e-rerun: not-needed`. If Phase 3.7 touched UI files but no `e2e-rerun` entry exists, do NOT commit yet — go run the re-run per Phase 3.7 step 6 first. Also confirm Phase 3.5/3.7 fixes did not leave lint/tsc broken: if the Phase 3.7 fix sub-loop applied any patch, run `npm run lint` + `npx tsc --noEmit` (when typescript) once before committing — when `has_toolchain`, the configured `toolchain.commands.{lint,typecheck}` verbatim (§ "Toolchain gates") — (redirect to disk per § "Context economy").
|
|
10
10
|
27. Stage and commit **all changes together** in the worktree using format `[CARD-ID] Brief description` (MUST per AGENTS.md). Include all relevant files — implementation, review fixes, QA-driven fixes, and doc updates in a single commit. Do NOT merge or push yet — that happens post-batch.
|
|
11
11
|
- **IMPORTANT — explicit staging**: NEVER use `git add -A` or `git add .`. Always stage files by explicit name:
|
|
12
12
|
```bash
|
|
@@ -118,7 +118,7 @@ Before triggering any review, you MUST verify that the coder agent implemented *
|
|
|
118
118
|
- The exact list of unimplemented items (copy the checklist rows)
|
|
119
119
|
- The file-ownership restrictions from `## File Ownership Map`
|
|
120
120
|
- The instruction: "Implement ONLY these missing items. Do not refactor or expand scope."
|
|
121
|
-
- After the fix agent completes, re-run the static gates the fix could have broken — `npm run lint`, `npx tsc --noEmit` (when `stack.language` includes typescript), and `npm test` — not just build + lint (a gap-fix can introduce a type error or break a test that the earlier Phase 2 gate had passed). Redirect each to `/tmp/<gate>-<CARD-ID>.txt` per § "Context economy" (never inline).
|
|
121
|
+
- After the fix agent completes, re-run the static gates the fix could have broken — `npm run lint`, `npx tsc --noEmit` (when `stack.language` includes typescript), and `npm test` (when `has_toolchain`, the configured `toolchain.commands.{lint,typecheck,test}` verbatim — § "Toolchain gates") — not just build + lint (a gap-fix can introduce a type error or break a test that the earlier Phase 2 gate had passed). Redirect each to `/tmp/<gate>-<CARD-ID>.txt` per § "Context economy" (never inline).
|
|
122
122
|
- Re-verify each fixed item against the code — do NOT trust the agent's self-report.
|
|
123
123
|
- Repeat this sub-loop up to **2 times** (per-item budget, shared with step 0 — see "Loop-counter scope"). After 2 loops, if items remain Partial or Missing:
|
|
124
124
|
- Log in `## Issues & Flags`: list each unimplemented requirement.
|
|
@@ -258,7 +258,7 @@ that is a **gate violation**: log it as
|
|
|
258
258
|
- **`security`-domain findings** (path in `paths.high_risk_modules`, or RLS-policy SQL) → route to **security-reviewer** in write mode (canonical writer map v4.26.1 — it owns the security-invariant contract a coder lacks; NEVER route security fixes to coder). **`migration`-domain findings** (SQL under the migrations dir) → route to **coder**. For both, apply the Sub-agent failure protocol's STOP-on-crash rule (never inline-fallback on a security/migration fix). These are NOT collapsed into a generic "everything else" bucket.
|
|
259
259
|
- **All remaining findings** (other code, perf, test) → invoke the **coder** agent once to apply them in a single pass.
|
|
260
260
|
Run in the order doc-reviewer → security-reviewer → coder (skip any whose partition is empty). Pass only the verified findings, not false positives.
|
|
261
|
-
12. Run final build: `npm run lint && npx tsc --noEmit && npm run build` (redirect each to `/tmp/final-<gate>.txt` per § "Context economy"; surface only exit code + bounded extract on failure).
|
|
261
|
+
12. Run final build: `npm run lint && npx tsc --noEmit && npm run build` (when `has_toolchain`, the configured `toolchain.commands.{lint,typecheck,build}` verbatim — § "Toolchain gates"; redirect each to `/tmp/final-<gate>.txt` per § "Context economy"; surface only exit code + bounded extract on failure).
|
|
262
262
|
If any check fails, apply self-healing retry loop (up to 3 times).
|
|
263
263
|
13. **Update tracker** with final review results:
|
|
264
264
|
- Review engine: Codex (a non-Anthropic frontier model, resolved at runtime by `codex-companion.mjs`) (primary) | Claude code-reviewer (fallback)
|
|
@@ -335,6 +335,7 @@
|
|
|
335
335
|
```
|
|
336
336
|
|
|
337
337
|
8. **Run the verification gates and CAPTURE their output to disk** (so step 9 can pass it to a fix agent) — **redirect, never `tee`/stream inline** (per § "Context economy" → Gate-output discipline). Each is its own gate:
|
|
338
|
+
When `features.has_toolchain: true`, substitute each command below with `toolchain.commands.{lint,typecheck,test,build}` run verbatim (§ "Toolchain gates"; defaults shown are the fallback when a key is unset).
|
|
338
339
|
```bash
|
|
339
340
|
cd <worktree-path>
|
|
340
341
|
npm run lint > /tmp/lint-<CARD-ID>.txt 2>&1; echo "lint:$?"
|
|
@@ -117,7 +117,7 @@ After completeness is verified, clean up the implementation before it reaches re
|
|
|
117
117
|
|
|
118
118
|
**Telemetry (Fix Application Log)** — for EVERY finding (valid OR skipped) append one row to the tracker's `## Fix Application Log` section per the schema above. Use `domain=simplify-{reuse|quality|efficiency}` matching the originating agent. Include the `severity` trailing key. Inline: `decision=inline | applied_by=orchestrator | est_lines=<n> | severity=<HIGH|MEDIUM> | finding=<1-line>`. Delegated (domain-override): `decision=<coder|doc-reviewer> | applied_by=<coder|doc-reviewer> | est_lines=<n> | severity=<...> | finding=<1-line>`. Skipped: `decision=skipped | applied_by=- | est_lines=0 | reason=<false-positive|not-worth-addressing>`.
|
|
119
119
|
|
|
120
|
-
5. After all fixes, run `npm run lint` and `npx tsc --noEmit` to confirm nothing broke (redirect to disk per § "Context economy"; surface only exit code + a bounded extract on failure).
|
|
120
|
+
5. After all fixes, run `npm run lint` and `npx tsc --noEmit` (when `has_toolchain`, the configured `toolchain.commands.{lint,typecheck}` verbatim — § "Toolchain gates") to confirm nothing broke (redirect to disk per § "Context economy"; surface only exit code + a bounded extract on failure).
|
|
121
121
|
If either fails, fix the regression (up to **2 retries**). **If it still fails after 2 retries**: do NOT silently continue to Phase 2.6 with a broken tree — log the failure in `## Issues & Flags` as `[SIMPLIFY-REGRESSION]` and invoke `AskUserQuestion` (revert the simplify fixes / keep and have me fix manually / stop the card), mirroring the Phase 3.5 escalation.
|
|
122
122
|
|
|
123
123
|
6. **Update tracker**: phase = "2.55-simplify DONE", log count of fixes applied (or "clean — 0 fixes").
|
|
@@ -288,7 +288,7 @@ skill's Phase 1 falls back to deriving Gherkin scenarios from
|
|
|
288
288
|
Doc-reviewer applies all doc-domain fixes itself. The orchestrator does NOT spawn a coder for doc fixes (since v3.40.0 — `doc` is owned by `doc-reviewer`, see "Domain-Override Domains"). The only doc-reviewer output that leaves this phase unfixed is a **doc-drift→bug finding rooted in CODE** (the implementation contradicts a documented contract). Route it explicitly: if the conflicting code file matches the `security` Domain-Override match rule (`paths.high_risk_modules`) → spawn `security-reviewer` with the finding now, in this phase (a security-class code fix is not deferrable to a `light` Phase 3.7, and security is owned by `security-reviewer` — never a coder); otherwise carry the finding into the Phase 3.7 `/codexreview` input as a known code-drift bug and let the Phase 3.7 fix sub-loop apply it. Either way, append a Fix Application Log row with `domain=codex-correctness` (NOT `doc`) so telemetry attributes it as a code fix. Do NOT leave it accumulating in the tracker with no fix owner.
|
|
289
289
|
14. **Knowledge-corpus sync (OPTIONAL — only if the project ships a corpus-sync agent)**: There is NO shipped `obsidian-sync` agent — do NOT dispatch one (a hard dispatch to a non-existent subagent fails silently). Only when the project provides its own knowledge-corpus sync agent (declared in `.baldart/overlays/new.md`) AND doc-reviewer's findings indicate a corpus impact, invoke that agent with the listed paths after the doc fixes are applied. Otherwise skip with a one-line notice (`knowledge-corpus sync: skipped (no corpus-sync agent configured)`). Non-blocking either way.
|
|
290
290
|
15. **Telemetry** — after doc-reviewer returns, append one row per doc finding to `## Fix Application Log`: `3 | doc | est_lines=<n> | decision=doc-reviewer | applied_by=doc-reviewer | finding=<1-line>`. If 0 findings, append one row: `3 | doc | est_lines=0 | decision=skipped | applied_by=- | reason=no-findings`. **Phase-8 producer (named counter)** — ALSO record the per-card doc-gap counts as a structured line in `## Current Card` (carried into `## Completed Cards` at Phase 5): `doc_gaps: found=<N> fixed=<M>` where `N` = total doc findings doc-reviewer raised and `M` = those it applied. This is the single named producer for Phase 8's `doc_gaps_found` / `doc_gaps_fixed` fields — without it those fields have no upstream write and Phase 8 would hard-code zeros. (D.4a is the team-mode producer of the same counter — see Phase 7 § D.4a.)
|
|
291
|
-
16. Run `npm run lint` and `npx tsc --noEmit` (when `stack.language` includes typescript) to verify nothing broke (redirect to disk per § "Context economy"). If doc-reviewer touched any source-adjacent file (a `.ts`/`.tsx` helper, a co-located doc export), also run `npm run build`. If any check fails, apply the self-healing retry loop (up to 3 times, no user prompt). **If still failing after 3 retries**: do NOT fall through silently to Phase 3.5 — log `[DOC-PHASE-REGRESSION]` in `## Issues & Flags` and invoke `AskUserQuestion` (revert the doc-phase edits that broke the build / keep and fix manually / stop the card).
|
|
291
|
+
16. Run `npm run lint` and `npx tsc --noEmit` (when `stack.language` includes typescript) — when `has_toolchain`, the configured `toolchain.commands.{lint,typecheck,build}` verbatim (§ "Toolchain gates") — to verify nothing broke (redirect to disk per § "Context economy"). If doc-reviewer touched any source-adjacent file (a `.ts`/`.tsx` helper, a co-located doc export), also run `npm run build`. If any check fails, apply the self-healing retry loop (up to 3 times, no user prompt). **If still failing after 3 retries**: do NOT fall through silently to Phase 3.5 — log `[DOC-PHASE-REGRESSION]` in `## Issues & Flags` and invoke `AskUserQuestion` (revert the doc-phase edits that broke the build / keep and fix manually / stop the card).
|
|
292
292
|
17. **Telemetry for the step-16 self-heal** — if the retry loop spawned any fix (a code edit to recover from a doc-phase regression), append a Fix Application Log row for it AFTER the loop settles (the step-15 doc telemetry row was written before this loop ran, so it does not capture step-16 fixes). Then update tracker: phase = "3-doc-review DONE", log doc findings count, fixes applied.
|
|
293
293
|
If doc-reviewer found a recurring gap, append 1-line to `## Lessons Learned`:
|
|
294
294
|
`DOC: <pattern>`
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
- Resolve `$TRUNK` = `git.trunk_branch` from `baldart.config.yml`. **When the key is absent** (consumer updated to ≥4.0.0 without re-running `configure`), do NOT hard-assume `develop` — autodetect the repo's real default branch exactly as worktree-manager does, so `/new` and `nw` agree on the base: `git -C "$MAIN" symbolic-ref --quiet refs/remotes/origin/HEAD` (strip `refs/remotes/origin/`), else the first existing local branch among `develop` / `main` / `master`. A `main`-trunk repo defaulted to `develop` here would diverge from the worktree base `nw` picks and break every `git diff "$TRUNK...HEAD"` gate. Only HALT ("Trunk branch unresolved — run `npx baldart configure`") if nothing resolves. Persist the resolved value as `Trunk branch:` in the tracker `## Worktree` section. **Every later Phase 0 / Phase 6c bash snippet that references the integration trunk MUST use `$TRUNK`, never a baked-in `develop`.** Begin every later consumer with a guard: if `$TRUNK` is empty → HALT with "Trunk branch unresolved — re-read `git.trunk_branch` from the tracker".
|
|
16
16
|
- Resolve `$METRICS` = `paths.metrics` from `baldart.config.yml` (default `docs/metrics`). This is the framework-owned telemetry directory written by Phase 8 (tracker archive, `skill-runs.jsonl`, `sessions/`). The dirty-tree gate (step 3) reads `$METRICS` to recognise — and never surface — its own telemetry output. Persist it as `Metrics dir:` in the tracker `## Worktree` section.
|
|
17
17
|
|
|
18
|
-
1. **Resolve `$MAIN`** — the absolute path of the main repo (not a worktree). If `/new` was invoked from inside a worktree,
|
|
18
|
+
1. **Resolve `$MAIN`** — the absolute path of the main repo (not a worktree). Use `git rev-parse --show-toplevel` when cwd is the main checkout. If `/new` was invoked from **inside a worktree**, that returns the worktree, so resolve the main root from the worktree's parent instead: `cd "$(git rev-parse --show-toplevel)/.." && git rev-parse --show-toplevel` (the worktree lives at `<main>/.worktrees/<name>`). Do **not** use `--git-common-dir` + `/..` (wrong under `git init --separate-git-dir`) or `git worktree list` (its main entry is the git dir, not the working tree, under a separate git dir). This is the same contract as worktree-manager § "Resolving the main repo root". Persist as `Main repo:` in the tracker `## Worktree` section. **Write `$MAIN` to the tracker the moment it is computed** — every later consumer (Phase 6c, Phase 6b) MUST re-read it from the tracker and HALT with "`$MAIN` absent from tracker" if the field is missing or empty, never silently use an undefined `$MAIN` (it does not survive context compaction).
|
|
19
19
|
|
|
20
20
|
1b. **Migration Gate (BLOCKING only when a migration is *declared* — else a silent no-op)** — resolve DB migrations interactively **before** the worktree exists, so the schema is live before any card builds against it. *Why this exists*: a migration applied to a shared/remote DB is owner-gated, so without this gate it is deferred to the END of the batch — and every downstream card in the batch is then built and verified against a schema that is not yet live (`validation_commands` / QA / E2E / DB-generated `tsc` types fail falsely → those cards cascade into deferral/blocked). Front-loading the migration removes that root cause. **The declaration lives in the EPIC card** (`migration_plan` block — project-specific, authored by the user, typically via the `.baldart/overlays/new.md` overlay). Steps:
|
|
21
21
|
|
|
@@ -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.
|
|
@@ -105,6 +105,7 @@ Agent tool call:
|
|
|
105
105
|
a) Print the numbered requirements checklist (anti-skip measure)
|
|
106
106
|
b) Implement ALL requirements
|
|
107
107
|
c) Run: npx tsc --noEmit && npx eslint --max-warnings=0 <your-files>
|
|
108
|
+
(when toolchain.commands.{typecheck,lint} are configured, run those verbatim instead — per agents/toolchain-protocol.md)
|
|
108
109
|
d) Self-heal up to 3 times if checks fail
|
|
109
110
|
e) Verify completeness: for each requirement, confirm code exists (read it)
|
|
110
111
|
f) If any requirement is missing after implementation, implement it now
|
|
@@ -151,7 +152,7 @@ For each completed agent:
|
|
|
151
152
|
|
|
152
153
|
After ALL agents in the group complete successfully:
|
|
153
154
|
|
|
154
|
-
1. **D.1 — Build verification (group)** — Run `npm run build` in the worktree to verify combined changes compile (redirect to `/tmp/build-group.txt` per § "Context economy"; surface only exit code + bounded extract on failure). If build fails, identify which card's changes broke it (from `git diff --name-only` per card), spawn a targeted fix-coder for those files only.
|
|
155
|
+
1. **D.1 — Build verification (group)** — Run `npm run build` (when `has_toolchain`, the configured `toolchain.commands.build` verbatim — § "Toolchain gates") in the worktree to verify combined changes compile (redirect to `/tmp/build-group.txt` per § "Context economy"; surface only exit code + bounded extract on failure). If build fails, identify which card's changes broke it (from `git diff --name-only` per card), spawn a targeted fix-coder for those files only.
|
|
155
156
|
|
|
156
157
|
1.5. **D.1.5 — Effective per-card review profile (compute ONCE; drives D.2 + D.4b)** — For EACH card in the group, compute its **effective codex profile** with the SAME deterministic rule the sequential Phase 3.7 Step C uses, so the two paths never disagree:
|
|
157
158
|
- **Floor**: read the card's `review_profile` field (`skip`/`light`/`balanced`/`deep`) per the QA Profile Selector (fallback-computed only for legacy cards lacking the field).
|
|
@@ -242,7 +243,7 @@ After ALL agents in the group complete successfully:
|
|
|
242
243
|
|
|
243
244
|
3a. **D.3a — Phase 2.5b AC-Closure Gate (per-card, BLOCKING — non-skippable)** — For EACH card in the group, **sequentially**, invoke the full Phase 2.5b gate as documented in `### Phase 2.5b — AC-Closure Gate (BLOCKING — Scope Closure Discipline)`. This includes: build the AC Closure Ledger from the card YAML, run the rationalization scan, invoke `AskUserQuestion` one-per-deferred-AC, run the `implementation_notes` deferral audit, and persist the ledger in the tracker. Until EVERY card in the group exits PASS, do NOT proceed to D.3b. Cards exiting with `not_implemented` ACs that the user routes to "Implementa adesso" must finish their fix-coder loop and re-pass the gate before D.3b starts for the next card. Log under `## AC Closure Ledger — <CARD-ID>` per card.
|
|
244
245
|
|
|
245
|
-
3b. **D.3b — Phase 2.55 Simplify (per-card, FANNED OUT across the group)** — The Simplify agents are **read-only analysis on file-disjoint per-card diffs** (the orchestrator applies the fixes afterward), so there is NO reason to run them one card at a time. **Spawn the per-card Simplify analysis for ALL eligible cards in PARALLEL** — in a SINGLE message, fire each card's Phase 2.55 trio (Reuse / Quality / Efficiency) against that card's diff captured to `/tmp/diff-<CARD-ID>.txt` (per Phase 2.55 step 2 — pass each trio the **path**, scoped to the card's File Ownership Map; never inline the diff). Per-card (not group-aggregate) so findings stay attributable. When all analyses return, **apply fixes per card** (file-disjoint → no write conflict), then re-run `npm run lint` and `npx tsc --noEmit` on the worktree ONCE for the whole group (redirect to disk per § "Context economy"). (Concurrency is capped by the platform; passing N cards is safe — excess agents queue.)
|
|
246
|
+
3b. **D.3b — Phase 2.55 Simplify (per-card, FANNED OUT across the group)** — The Simplify agents are **read-only analysis on file-disjoint per-card diffs** (the orchestrator applies the fixes afterward), so there is NO reason to run them one card at a time. **Spawn the per-card Simplify analysis for ALL eligible cards in PARALLEL** — in a SINGLE message, fire each card's Phase 2.55 trio (Reuse / Quality / Efficiency) against that card's diff captured to `/tmp/diff-<CARD-ID>.txt` (per Phase 2.55 step 2 — pass each trio the **path**, scoped to the card's File Ownership Map; never inline the diff). Per-card (not group-aggregate) so findings stay attributable. When all analyses return, **apply fixes per card** (file-disjoint → no write conflict), then re-run `npm run lint` and `npx tsc --noEmit` (when `has_toolchain`, the configured `toolchain.commands.{lint,typecheck}` verbatim — § "Toolchain gates") on the worktree ONCE for the whole group (redirect to disk per § "Context economy"). (Concurrency is capped by the platform; passing N cards is safe — excess agents queue.)
|
|
246
247
|
- **Gate (enumerated, `TRIVIAL_CARDS`-driven)**: SKIP D.3b for a card in **`TRIVIAL_CARDS`** (the set already computed at D.1.5 — `review_profile == skip` AND 0 Step-A triggers AND **non-source diff**), aligning team mode with sequential Phase 2.55's `IS_TRIVIAL` re-confirmation on the ACTUAL diff and with team-mode's own D.1.5 SSOT. A trivial card has no substantive diff to simplify, and Simplify is quality-only (no merge-gate coverage to lose). Log `simplify: SKIPPED (trivial — non-source diff)`. **A card with `review_profile == skip` whose committed diff DID touch a source file is NOT in `TRIVIAL_CARDS` → run D.3b for it** (exactly as sequential 2.55 does — `skip` is the floor, the real diff is the deciding check). For `light`/`balanced`/`deep` cards D.3b runs unchanged. This is the ONLY enumerated skip — never skip D.3b "for time" on a non-trivial card. (Skipped cards are simply omitted from the parallel fan-out.)
|
|
247
248
|
|
|
248
249
|
3c. **D.3c — Phase 2.6 E2E-Review (per-card)** — First, evaluate the existing Gate table for EVERY card at once (skip when `features.has_e2e_review: false`, backend-only diff per the diff predicate documented in Phase 2.6, or card type in the Phase 2.6 skip set — `backend`/`api`/`db`/`infra`/`docs`/`chore`/`config`). In practice most cards in a group skip this gate (backend/db/api), so the eligible set is usually 0–1. For the cards that PASS the gate, invoke `/e2e-review` in programmatic mode with that card's payload. Each `/e2e-review` keeps its own isolated state dir (`.baldart/e2e-review/<CARD-ID>/`), so multiple runs do not clobber each other's artifacts.
|
|
@@ -66,7 +66,7 @@ created by Step 1 (HARD RULE 17). `$WORKTREE_PATH` is set in the state file.
|
|
|
66
66
|
|
|
67
67
|
**Variable guard (R6).** Before executing any item in Step 7, resolve and verify:
|
|
68
68
|
- `$WORKTREE_PATH` — read from the state file `## Worktree / path:`. HALT with `"ABORT: WORKTREE_PATH not set in state file — cannot commit or merge."` if absent or empty.
|
|
69
|
-
- `$MAIN` — the absolute path of the main (non-worktree) git repository root. **READ the persisted value from the state file `## Worktree / main_path:`** (written at SKILL.md Step 1, the R6 persist-then-read pattern `/new` uses). HALT with `"ABORT: MAIN absent from state — re-run from a session whose Step 1 persisted main_path."` if the field is absent or empty. Only when the field is genuinely missing (legacy state file) FALL BACK to deriving it once
|
|
69
|
+
- `$MAIN` — the absolute path of the main (non-worktree) git repository root. **READ the persisted value from the state file `## Worktree / main_path:`** (written at SKILL.md Step 1, the R6 persist-then-read pattern `/new` uses). HALT with `"ABORT: MAIN absent from state — re-run from a session whose Step 1 persisted main_path."` if the field is absent or empty. Only when the field is genuinely missing (legacy state file) FALL BACK to deriving it once from the worktree's parent toplevel: `MAIN="$(cd "$WORKTREE_PATH" && git -C .. rev-parse --show-toplevel)"`. (Do **not** use `git rev-parse --git-common-dir` + `/..` — that returns the parent of the git dir, which is wrong under `git init --separate-git-dir`; `--show-toplevel` is the true working-tree root. This mirrors the canonical resolution in worktree-manager § "Resolving the main repo root".) Every subsequent `git` command that runs outside the worktree MUST use `git -C "$MAIN" …`; never use a bare `git` command whose cwd is undefined. (This is the same variable name and resolution contract `/new` uses — the two skills must agree.)
|
|
70
70
|
|
|
71
71
|
### Resolve remaining items
|
|
72
72
|
|
|
@@ -8,7 +8,7 @@ description: Review changed code for reuse, quality, and efficiency, then fix an
|
|
|
8
8
|
|
|
9
9
|
## Project Context
|
|
10
10
|
|
|
11
|
-
**Reads from `baldart.config.yml`:** `paths.references_dir`, `paths.components_primitives`, `paths.components_root`, `paths.design_system
|
|
11
|
+
**Reads from `baldart.config.yml`:** `paths.references_dir`, `paths.components_primitives`, `paths.components_root`, `paths.design_system`, `features.has_toolchain` + `toolchain.commands.{lint,typecheck,test}` (the Step 5 verify gates run those verbatim when the flag is on — see Step 5).
|
|
12
12
|
**Gated by features:** `features.has_design_system` (Design System check section is BLOCKING when `true`).
|
|
13
13
|
**Overlay:** loads `.baldart/overlays/simplify.md` if present — project-specific utility module paths, hook conventions.
|
|
14
14
|
**On missing/empty keys:** ask the user; do not assume defaults. See `framework/agents/project-context.md` § 3.
|
|
@@ -142,6 +142,12 @@ When extracting shared code, write to the SAME `${paths.*}` keys the review phas
|
|
|
142
142
|
These static checks run AFTER Step 4 has mutated code — a pre-fix pass would not cover the post-fix
|
|
143
143
|
code, so re-running them here is mandatory whenever Step 4 changed anything.
|
|
144
144
|
|
|
145
|
+
> **Toolchain:** when `features.has_toolchain: true` in `baldart.config.yml`, run the command from
|
|
146
|
+
> `toolchain.commands.{lint,typecheck,test}` **verbatim** instead of the default shown below — per
|
|
147
|
+
> `framework/agents/toolchain-protocol.md`. Per gate: a non-empty config command wins; empty/absent
|
|
148
|
+
> (or the flag off) falls back to the default. A configured command that exits non-zero is a real
|
|
149
|
+
> FAIL (do not fall back to the default).
|
|
150
|
+
|
|
145
151
|
Run the linter (use the project's lint command):
|
|
146
152
|
|
|
147
153
|
```
|
|
@@ -20,6 +20,8 @@ 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.
|
|
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.
|
|
23
25
|
- Protocol reference: `framework/agents/project-context.md`. Skills must ASK when a needed key is missing — never assume.
|
|
24
26
|
|
|
25
27
|
## Effort
|
|
@@ -30,6 +32,47 @@ reasoning depth for this run — detect it once at kickoff and strip the token
|
|
|
30
32
|
before consuming user input. Level→behavior mapping, parsing contract, and
|
|
31
33
|
precedence caveats: `framework/agents/effort-protocol.md`.
|
|
32
34
|
|
|
35
|
+
## Toolchain-aware gates
|
|
36
|
+
|
|
37
|
+
When `features.has_toolchain: true` in `baldart.config.yml`, every mechanical
|
|
38
|
+
gate in this skill (type-check, lint, build, test) runs the command from
|
|
39
|
+
`toolchain.commands.<gate>` **verbatim** instead of the hardcoded default — per
|
|
40
|
+
`framework/agents/toolchain-protocol.md`. Resolution is done **in shell**, the
|
|
41
|
+
same way this skill already resolves `git.trunk_branch` / `git.merge_strategy`
|
|
42
|
+
from disk (a background `/nw` baseline runner is a weak/background model — a prose
|
|
43
|
+
"please substitute" note would be silently skipped while the hardcoded command
|
|
44
|
+
ran anyway; the shell resolver is model-independent). Each gate block inlines this
|
|
45
|
+
compact resolver (self-contained, since each runs as its own `Bash` call):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Resolve baldart.config.yml from disk; missing/unreadable → empty → hardcoded default.
|
|
49
|
+
CFG="baldart.config.yml"; [ -f "$CFG" ] || CFG="$(git rev-parse --show-toplevel 2>/dev/null)/baldart.config.yml"
|
|
50
|
+
_tc() { grep -E '^[[:space:]]*has_toolchain:[[:space:]]*true' "$CFG" >/dev/null 2>&1 || return 0
|
|
51
|
+
grep -A20 '^toolchain:' "$CFG" 2>/dev/null | grep -A15 '^[[:space:]]*commands:' \
|
|
52
|
+
| grep -E "^[[:space:]]+$1:" | head -1 \
|
|
53
|
+
| sed -E "s/.*$1:[[:space:]]*\"?([^\"#]*)\"?.*/\1/" | sed -E 's/[[:space:]]+$//'; }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`eval "${VAR:-<default>}"` then runs the configured command or falls back.
|
|
57
|
+
|
|
58
|
+
**Three carve-outs specific to this skill:**
|
|
59
|
+
- **`npm install` is NOT a toolchain gate** — there is no `toolchain.commands.install`
|
|
60
|
+
key (the command map is lint/format/typecheck/test/test_related/build/audit).
|
|
61
|
+
Dependency installation stays as-is; do **not** route it through the resolver.
|
|
62
|
+
- **Severity is unchanged.** The resolver picks *which* command runs, never *what
|
|
63
|
+
to do with its exit code*. The protocol's "a configured command that exits
|
|
64
|
+
non-zero is a real FAIL — do not then run the default" governs only resolution
|
|
65
|
+
(never mask a failure by running the default after the configured one failed);
|
|
66
|
+
it does **not** change each block's STOP/continue policy: a `/nw` baseline
|
|
67
|
+
type-check/lint failure is still "report but continue", a build failure is still
|
|
68
|
+
"STOP", exactly as written at each block.
|
|
69
|
+
- **The `/mw` best-effort test line stays best-effort.** `npm run test 2>/dev/null
|
|
70
|
+
|| true` (pre-merge step 2) is intentionally non-blocking; the resolved command
|
|
71
|
+
keeps the `|| true` swallow, so `test` is never a `/mw` STOP trigger.
|
|
72
|
+
|
|
73
|
+
A configured **lint** command may be whole-tree (e.g. `npx biome check .`) where a
|
|
74
|
+
default is changed-files-scoped (`/mw` pre-merge) — that wider scope is the
|
|
75
|
+
consumer's configured contract, applied only when `has_toolchain` is on.
|
|
33
76
|
|
|
34
77
|
**IMMEDIATE EXECUTION**: When invoked via `/nw`, `/mw`, `/lw`, or `/cw`, do NOT explain the process. Start executing the matching command flow immediately:
|
|
35
78
|
|
|
@@ -66,7 +109,7 @@ backlog cards, design references — and never touch source code.
|
|
|
66
109
|
What docs mode SKIPS vs the standard flow:
|
|
67
110
|
- No `npm install` (no `node_modules/` inside the worktree)
|
|
68
111
|
- No port allocation (no dev server)
|
|
69
|
-
- No
|
|
112
|
+
- No env-artifact (`stack.env_files`) copy
|
|
70
113
|
- No `npm run build` baseline verification
|
|
71
114
|
- No lint / tsc baseline checks
|
|
72
115
|
- No `--dev` server bootstrap
|
|
@@ -112,6 +155,9 @@ Output:
|
|
|
112
155
|
# Without this, the worktree IS git-tracked: `git status` on the main
|
|
113
156
|
# repo explodes with every file inside the worktree, exactly defeating
|
|
114
157
|
# the parallel-isolation purpose of the docs-mode.
|
|
158
|
+
# Main-checkout context (the docs worktree is created below, cwd is still the
|
|
159
|
+
# main repo) → `--show-toplevel` is the main root, separate-git-dir-safe. See
|
|
160
|
+
# § "Resolving the main repo root".
|
|
115
161
|
MAIN_ROOT="$(git rev-parse --show-toplevel)"
|
|
116
162
|
if [ ! -f "$MAIN_ROOT/.gitignore" ] || ! grep -qE '^\.worktrees/?$' "$MAIN_ROOT/.gitignore"; then
|
|
117
163
|
echo "ERROR: .worktrees/ is not in $MAIN_ROOT/.gitignore" >&2
|
|
@@ -195,7 +241,7 @@ git -C "$MAIN_ROOT" worktree add "$WORKTREE_REL" -b "$BRANCH" "origin/$TRUNK"
|
|
|
195
241
|
}
|
|
196
242
|
```
|
|
197
243
|
|
|
198
|
-
**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`,
|
|
199
245
|
`npx tsc`, `npx eslint`, dev server start, `git checkout`/`switch`/`branch` on main.
|
|
200
246
|
|
|
201
247
|
### mw-docs programmatic
|
|
@@ -367,7 +413,8 @@ unmerged sibling branch, invisible to both the local backlog and the trunk
|
|
|
367
413
|
merge-base. They collide at rebase/merge time.
|
|
368
414
|
|
|
369
415
|
`scripts/allocate-id.sh` closes that race **on the same machine**. Every worktree
|
|
370
|
-
shares one main repo root (resolved
|
|
416
|
+
shares one main repo root (resolved by the canonical `resolve_main()` helper in
|
|
417
|
+
that script — see § "Resolving the main repo root" below), and
|
|
371
418
|
`.worktrees/` lives there and is gitignored — so it is the natural shared
|
|
372
419
|
coordination point, exactly like `registry.json`. The allocator anchors a lock
|
|
373
420
|
and a per-prefix high-water-mark there:
|
|
@@ -381,6 +428,36 @@ and a per-prefix high-water-mark there:
|
|
|
381
428
|
.claude/skills/worktree-manager/scripts/allocate-id.sh release <worktree-path>
|
|
382
429
|
```
|
|
383
430
|
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Resolving the main repo root
|
|
434
|
+
|
|
435
|
+
Several steps need `$MAIN` — the absolute path of the **main repo working tree**
|
|
436
|
+
(never a worktree). It is resolved in **three distinct execution contexts**, and
|
|
437
|
+
each genuinely needs a different primitive — this is NOT entropy, and "unifying"
|
|
438
|
+
them onto one naked primitive regresses real cases (verified):
|
|
439
|
+
|
|
440
|
+
1. **Main-checkout cwd** (a worktree is being *created*, or cards synced before
|
|
441
|
+
any `cd`): `git rev-parse --show-toplevel`. This is the true working-tree root
|
|
442
|
+
even when `.git` lives elsewhere (`git init --separate-git-dir`). Sites:
|
|
443
|
+
`/nw` step 1, nw-docs step 0, `3b. Sync untracked cards` (cwd is still main
|
|
444
|
+
there), and `/prd` SKILL.md Step 1.
|
|
445
|
+
2. **Worktree cwd** (after `cd "$WORKTREE_PATH"`): `git -C .. rev-parse --show-toplevel`.
|
|
446
|
+
The worktree's parent is `.worktrees/`, which lives inside the main repo, so
|
|
447
|
+
its toplevel is the main root — correct under a separate git dir too. Sites:
|
|
448
|
+
`/nw` step 4 env-copy, `/mw` step 4c `$MAIN` fallback.
|
|
449
|
+
3. **Arbitrary cwd, main-or-worktree** (the `allocate-id.sh` startup, called from
|
|
450
|
+
either): the canonical `resolve_main()` in `scripts/allocate-id.sh` — it uses
|
|
451
|
+
`--show-toplevel`, then detects a linked worktree (`git-dir != git-common-dir`)
|
|
452
|
+
and walks one level up.
|
|
453
|
+
|
|
454
|
+
**Do NOT resolve `$MAIN` via `git rev-parse --git-common-dir` + `/..`.** That
|
|
455
|
+
returns the parent of the *git dir*, which equals the working-tree root only when
|
|
456
|
+
`.git` is in-tree; under `git init --separate-git-dir` it points at the wrong
|
|
457
|
+
directory. Always derive the root through `--show-toplevel`. `resolve_main()` is
|
|
458
|
+
the reference implementation; the SKILL snippets mirror it inline because the
|
|
459
|
+
script is not on disk inside a freshly-checked-out worktree.
|
|
460
|
+
|
|
384
461
|
Shared files, all under `$MAIN/.worktrees/` (already gitignored):
|
|
385
462
|
- `.id-alloc.lock/` — cross-process mutex (atomic `mkdir`, stale-stolen after 30s
|
|
386
463
|
via the dir's own mtime; it is a directory, NOT a git ref, so it does not touch
|
|
@@ -422,6 +499,9 @@ Supports three modes:
|
|
|
422
499
|
# Resolve $MAIN (main repo root) and $TRUNK (git.trunk_branch) up front, and
|
|
423
500
|
# persist both onto the registry entry created in step 6 (R6). Every later step
|
|
424
501
|
# reads them from the registry with a presence guard — never from in-context state.
|
|
502
|
+
# Main-checkout context: cwd is the main repo (the orchestrator invokes /nw from
|
|
503
|
+
# $MAIN — setup.md resolves $MAIN before spawning), so `--show-toplevel` is the
|
|
504
|
+
# main root and is separate-git-dir-safe. See § "Resolving the main repo root".
|
|
425
505
|
MAIN="$(git rev-parse --show-toplevel)"
|
|
426
506
|
# .gitignore safety (auto-heal, NON-blocking — code-mode differs from nw-docs, which
|
|
427
507
|
# hard-aborts). Without `.worktrees/` ignored the worktree is git-tracked and `git status`
|
|
@@ -520,7 +600,14 @@ but are NOT on the trunk branch yet. The worktree (branched from the trunk) won'
|
|
|
520
600
|
# when the key is absent — emit it resolved, never the literal ${paths.backlog_dir}
|
|
521
601
|
# token, which is not valid bash). Same resolution as /new's card-scoped diff block.
|
|
522
602
|
BACKLOG_DIR="<value of paths.backlog_dir, or 'backlog' if the key is absent>"
|
|
523
|
-
|
|
603
|
+
# At this point cwd is still the MAIN checkout (the `cd "$WORKTREE_PATH"` happens
|
|
604
|
+
# in step 4 below), so `--show-toplevel` IS the main repo root — correct for a
|
|
605
|
+
# normal repo, a git submodule, and a `--separate-git-dir` repo alike. (The old
|
|
606
|
+
# `git -C "$WORKTREE_PATH" --show-superproject-working-tree || pwd` was wrong for a
|
|
607
|
+
# real submodule — it returned the SUPERPROJECT, not this repo's root where the
|
|
608
|
+
# cards live — and only resolved otherwise by relying on this same cwd accident.
|
|
609
|
+
# See § "Resolving the main repo root".)
|
|
610
|
+
MAIN_ROOT="$(git rev-parse --show-toplevel)"
|
|
524
611
|
|
|
525
612
|
# For each card in the batch, check if its YAML exists in the main repo but not in the worktree
|
|
526
613
|
for CARD_FILE in $(ls "$MAIN_ROOT/$BACKLOG_DIR"/*.yml 2>/dev/null); do
|
|
@@ -547,11 +634,62 @@ npm install
|
|
|
547
634
|
# 2. Own .next cache — already isolated since each worktree has its own
|
|
548
635
|
# directory tree. The default .next inside the worktree is sufficient.
|
|
549
636
|
|
|
550
|
-
# 3. Copy environment
|
|
551
|
-
#
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
+
#
|
|
650
|
+
# cwd is the worktree; its parent (`.worktrees/`) lives inside the main repo,
|
|
651
|
+
# so `git -C ..` resolves the MAIN repo's toplevel — correct under a
|
|
652
|
+
# `--separate-git-dir` repo too (unlike `--git-common-dir`+parent). The old
|
|
653
|
+
# silent `|| echo '../..'` fallback masked a resolution failure with a relative
|
|
654
|
+
# guess; fail loud instead. See § "Resolving the main repo root".
|
|
655
|
+
MAIN_ROOT="$(git -C .. rev-parse --show-toplevel 2>/dev/null)"
|
|
656
|
+
[ -n "$MAIN_ROOT" ] || { echo "ERROR: cannot resolve main repo root from worktree $(pwd)" >&2; exit 1; }
|
|
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
|
|
555
693
|
|
|
556
694
|
# 4. Pick a free dev server port (avoid 3000 used by main)
|
|
557
695
|
PORT=3001
|
|
@@ -559,11 +697,15 @@ while lsof -i :$PORT -sTCP:LISTEN >/dev/null 2>&1; do
|
|
|
559
697
|
PORT=$((PORT + 1))
|
|
560
698
|
if [ $PORT -gt 3099 ]; then echo "ERROR: No free port in 3001-3099" >&2; exit 1; fi
|
|
561
699
|
done
|
|
562
|
-
# Write PORT to
|
|
563
|
-
|
|
564
|
-
|
|
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"
|
|
565
707
|
else
|
|
566
|
-
echo "PORT=$PORT" >>
|
|
708
|
+
echo "PORT=$PORT" >> "$PORT_ENV_FILE"
|
|
567
709
|
fi
|
|
568
710
|
|
|
569
711
|
# 5. ASSERT git hooks are ACTIVE — not just readable.
|
|
@@ -597,16 +739,27 @@ fi
|
|
|
597
739
|
### 5. Verify baseline
|
|
598
740
|
|
|
599
741
|
```bash
|
|
742
|
+
# Toolchain-aware (§ "Toolchain-aware gates"): when features.has_toolchain: true,
|
|
743
|
+
# run toolchain.commands.{typecheck,lint,build} verbatim; else the defaults below.
|
|
744
|
+
CFG="baldart.config.yml"; [ -f "$CFG" ] || CFG="$(git rev-parse --show-toplevel 2>/dev/null)/baldart.config.yml"
|
|
745
|
+
_tc() { grep -E '^[[:space:]]*has_toolchain:[[:space:]]*true' "$CFG" >/dev/null 2>&1 || return 0
|
|
746
|
+
grep -A20 '^toolchain:' "$CFG" 2>/dev/null | grep -A15 '^[[:space:]]*commands:' \
|
|
747
|
+
| grep -E "^[[:space:]]+$1:" | head -1 \
|
|
748
|
+
| sed -E "s/.*$1:[[:space:]]*\"?([^\"#]*)\"?.*/\1/" | sed -E 's/[[:space:]]+$//'; }
|
|
749
|
+
|
|
750
|
+
TC_TC=$(_tc typecheck); TC_LINT=$(_tc lint); TC_BUILD=$(_tc build)
|
|
751
|
+
|
|
600
752
|
# TypeScript + lint (fast)
|
|
601
|
-
npx tsc --noEmit
|
|
602
|
-
npx eslint --max-warnings=0 src/
|
|
753
|
+
eval "${TC_TC:-npx tsc --noEmit}"
|
|
754
|
+
eval "${TC_LINT:-npx eslint --max-warnings=0 src/}"
|
|
603
755
|
|
|
604
756
|
# Full build verification (required — confirms worktree is functional)
|
|
605
|
-
npm run build
|
|
757
|
+
eval "${TC_BUILD:-npm run build}"
|
|
606
758
|
```
|
|
607
759
|
|
|
608
760
|
If build fails → STOP and report. Do NOT continue — the worktree is broken.
|
|
609
761
|
If only tsc/lint fails → report but continue (the trunk branch should be clean, may be a transient issue).
|
|
762
|
+
(Severity is by-gate as stated here; the toolchain resolver only changes *which* command runs — § "Toolchain-aware gates".)
|
|
610
763
|
|
|
611
764
|
### 6. Update registry
|
|
612
765
|
|
|
@@ -673,12 +826,32 @@ fi
|
|
|
673
826
|
|
|
674
827
|
### 2. Env sync check
|
|
675
828
|
|
|
676
|
-
Compare
|
|
677
|
-
|
|
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):
|
|
678
834
|
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
|
|
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
|
|
682
855
|
```
|
|
683
856
|
|
|
684
857
|
### 3. Pre-merge safety commit + checks inside the worktree
|
|
@@ -717,15 +890,27 @@ Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>"
|
|
|
717
890
|
fi
|
|
718
891
|
|
|
719
892
|
# 2. Run quality checks
|
|
720
|
-
#
|
|
721
|
-
|
|
722
|
-
|
|
893
|
+
# Toolchain-aware (§ "Toolchain-aware gates"): when features.has_toolchain: true,
|
|
894
|
+
# run toolchain.commands.{lint,typecheck,build,test} verbatim; else the defaults below.
|
|
895
|
+
CFG="baldart.config.yml"; [ -f "$CFG" ] || CFG="$(git rev-parse --show-toplevel 2>/dev/null)/baldart.config.yml"
|
|
896
|
+
_tc() { grep -E '^[[:space:]]*has_toolchain:[[:space:]]*true' "$CFG" >/dev/null 2>&1 || return 0
|
|
897
|
+
grep -A20 '^toolchain:' "$CFG" 2>/dev/null | grep -A15 '^[[:space:]]*commands:' \
|
|
898
|
+
| grep -E "^[[:space:]]+$1:" | head -1 \
|
|
899
|
+
| sed -E "s/.*$1:[[:space:]]*\"?([^\"#]*)\"?.*/\1/" | sed -E 's/[[:space:]]+$//'; }
|
|
900
|
+
TC_LINT=$(_tc lint); TC_TC=$(_tc typecheck); TC_BUILD=$(_tc build); TC_TEST=$(_tc test)
|
|
901
|
+
|
|
902
|
+
# Lint — default is changed-files-scoped (vs trunk); a configured whole-tree command
|
|
903
|
+
# (e.g. `npx biome check .`) runs verbatim by the consumer's contract (§ carve-out).
|
|
904
|
+
CHANGED_TS=$(git diff --name-only "origin/$TRUNK" -- '*.ts' '*.tsx')
|
|
905
|
+
eval "${TC_LINT:-npx eslint --max-warnings=0 $CHANGED_TS}"
|
|
906
|
+
eval "${TC_TC:-npx tsc --noEmit}"
|
|
723
907
|
|
|
724
908
|
# Full build (required before PR per AGENTS.md)
|
|
725
|
-
npm run build
|
|
909
|
+
eval "${TC_BUILD:-npm run build}"
|
|
726
910
|
|
|
727
|
-
# Tests
|
|
728
|
-
|
|
911
|
+
# Tests — BEST-EFFORT by design: never a STOP trigger. Keep the `|| true` swallow
|
|
912
|
+
# whether configured or default (§ carve-out: the /mw test line stays best-effort).
|
|
913
|
+
{ eval "${TC_TEST:-npm run test}"; } 2>/dev/null || true
|
|
729
914
|
```
|
|
730
915
|
|
|
731
916
|
If lint, tsc, or build fails → report and STOP. Do NOT proceed to PR.
|
|
@@ -898,7 +1083,13 @@ done
|
|
|
898
1083
|
|
|
899
1084
|
# Continue rebase + verify build (no stash to restore — step 4b never stashes)
|
|
900
1085
|
git rebase --continue
|
|
901
|
-
|
|
1086
|
+
# Toolchain-aware (§ "Toolchain-aware gates"): toolchain.commands.build verbatim when set, else default.
|
|
1087
|
+
CFG="baldart.config.yml"; [ -f "$CFG" ] || CFG="$(git rev-parse --show-toplevel 2>/dev/null)/baldart.config.yml"
|
|
1088
|
+
_tc() { grep -E '^[[:space:]]*has_toolchain:[[:space:]]*true' "$CFG" >/dev/null 2>&1 || return 0
|
|
1089
|
+
grep -A20 '^toolchain:' "$CFG" 2>/dev/null | grep -A15 '^[[:space:]]*commands:' \
|
|
1090
|
+
| grep -E "^[[:space:]]+$1:" | head -1 \
|
|
1091
|
+
| sed -E "s/.*$1:[[:space:]]*\"?([^\"#]*)\"?.*/\1/" | sed -E 's/[[:space:]]+$//'; }
|
|
1092
|
+
TC_BUILD=$(_tc build); eval "${TC_BUILD:-npm run build}"
|
|
902
1093
|
```
|
|
903
1094
|
|
|
904
1095
|
If build fails after rebase → STOP and report. The rebase introduced incompatibilities.
|
|
@@ -911,11 +1102,16 @@ Read `git.merge_strategy` from `baldart.config.yml` (default: `pr`):
|
|
|
911
1102
|
|
|
912
1103
|
```bash
|
|
913
1104
|
# $MAIN and $TRUNK were resolved in step 1 (read from the registry entry, R6) and
|
|
914
|
-
# presence-guarded. Fall back to deriving $MAIN from
|
|
1105
|
+
# presence-guarded. Fall back to deriving $MAIN from the worktree ONLY if it is
|
|
915
1106
|
# still unset — never silently re-derive over a value the registry already supplied.
|
|
916
1107
|
if [ -z "$MAIN" ]; then
|
|
917
|
-
|
|
918
|
-
|
|
1108
|
+
# Canonical worktree→main resolution (see § "Resolving the main repo root"):
|
|
1109
|
+
# cd INTO the worktree so `git -C ..` resolves the MAIN repo's toplevel. NOT
|
|
1110
|
+
# `--git-common-dir`+`/..` — that returns the parent of the git dir (wrong under
|
|
1111
|
+
# `git init --separate-git-dir`), and appending `/..` to a possibly-relative
|
|
1112
|
+
# `--git-common-dir` under `git -C` resolves against the wrong base.
|
|
1113
|
+
MAIN="$(cd "$WORKTREE_PATH" 2>/dev/null && git -C .. rev-parse --show-toplevel 2>/dev/null)"
|
|
1114
|
+
[ -n "$MAIN" ] || { echo "ERROR: cannot resolve \$MAIN from worktree $WORKTREE_PATH (registry mainRoot was empty)." >&2; exit 1; }
|
|
919
1115
|
fi
|
|
920
1116
|
|
|
921
1117
|
# Resolve strategy from baldart.config.yml (default: pr)
|
|
@@ -1093,8 +1289,15 @@ fi
|
|
|
1093
1289
|
### 5. Post-merge verification
|
|
1094
1290
|
|
|
1095
1291
|
```bash
|
|
1096
|
-
|
|
1097
|
-
|
|
1292
|
+
# Toolchain-aware (§ "Toolchain-aware gates"): toolchain.commands.{typecheck,build} verbatim when set, else defaults.
|
|
1293
|
+
CFG="baldart.config.yml"; [ -f "$CFG" ] || CFG="$(git rev-parse --show-toplevel 2>/dev/null)/baldart.config.yml"
|
|
1294
|
+
_tc() { grep -E '^[[:space:]]*has_toolchain:[[:space:]]*true' "$CFG" >/dev/null 2>&1 || return 0
|
|
1295
|
+
grep -A20 '^toolchain:' "$CFG" 2>/dev/null | grep -A15 '^[[:space:]]*commands:' \
|
|
1296
|
+
| grep -E "^[[:space:]]+$1:" | head -1 \
|
|
1297
|
+
| sed -E "s/.*$1:[[:space:]]*\"?([^\"#]*)\"?.*/\1/" | sed -E 's/[[:space:]]+$//'; }
|
|
1298
|
+
TC_TC=$(_tc typecheck); TC_BUILD=$(_tc build)
|
|
1299
|
+
eval "${TC_TC:-npx tsc --noEmit}"
|
|
1300
|
+
eval "${TC_BUILD:-npm run build}"
|
|
1098
1301
|
```
|
|
1099
1302
|
|
|
1100
1303
|
If post-merge build fails → STOP and report. Do NOT cleanup worktree (may need to investigate).
|
|
@@ -1181,7 +1384,7 @@ Active worktrees:
|
|
|
1181
1384
|
Age: 2 days
|
|
1182
1385
|
Status: 3 uncommitted changes
|
|
1183
1386
|
Build: verified ✓
|
|
1184
|
-
Env: in sync | STALE (main
|
|
1387
|
+
Env: in sync | STALE (a main env artifact changed)
|
|
1185
1388
|
|
|
1186
1389
|
2. BUG-0500 fix-auth
|
|
1187
1390
|
Branch: feat/BUG-0500-fix-auth
|
|
@@ -1285,5 +1488,5 @@ Cleanup complete:
|
|
|
1285
1488
|
- If merge conflicts during /mw, STOP and ask the user. Do not auto-resolve.
|
|
1286
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).
|
|
1287
1490
|
- Commit lock protocol: Each worktree has its own `COMMIT_LOCK` in its git dir — no cross-worktree interference.
|
|
1288
|
-
- 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.
|
|
1289
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.
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
# backlog scan cannot see IDs that are still in flight on an unmerged sibling
|
|
9
9
|
# worktree branch.
|
|
10
10
|
#
|
|
11
|
-
# Mechanism: every worktree shares the same main repo root (resolved
|
|
12
|
-
# `
|
|
11
|
+
# Mechanism: every worktree shares the same main repo root (resolved by
|
|
12
|
+
# `resolve_main()` below), and `.worktrees/` lives there and is
|
|
13
13
|
# gitignored. We anchor a lock + a per-prefix high-water-mark file there, so a
|
|
14
14
|
# reservation is atomic across every worktree on this machine. The high-water
|
|
15
15
|
# bumped under the lock is the correctness mechanism; the max() against the real
|
|
@@ -34,15 +34,29 @@ LOCK_MAX_TRIES=150 # ~30s at 0.2s/try
|
|
|
34
34
|
|
|
35
35
|
err() { printf '%s\n' "$*" >&2; }
|
|
36
36
|
|
|
37
|
-
# --- Resolve the
|
|
37
|
+
# --- Resolve the main repo root from the main checkout OR any worktree -----
|
|
38
|
+
# CANONICAL main-repo-root resolution for the whole framework (mirrored inline in
|
|
39
|
+
# worktree-manager/SKILL.md + prd, where this script is not reachable from a
|
|
40
|
+
# worktree checkout). Built on `--show-toplevel`, NOT `--git-common-dir`+parent:
|
|
41
|
+
# the latter returns the PARENT OF THE GIT DIR, which is wrong under
|
|
42
|
+
# `git init --separate-git-dir` (the git dir lives outside the working tree).
|
|
43
|
+
# `--show-toplevel` is the true working-tree root in every case. From a linked
|
|
44
|
+
# worktree `--show-toplevel` returns the WORKTREE root, so we detect that
|
|
45
|
+
# (git-dir != git-common-dir) and walk one level up out of `.worktrees/` — the
|
|
46
|
+
# worktree path is always `<main>/.worktrees/<name>` (SKILL.md R8) — and resolve
|
|
47
|
+
# the main checkout's toplevel there. Fails (return 1) rather than print garbage.
|
|
38
48
|
resolve_main() {
|
|
39
|
-
local common
|
|
49
|
+
local top gd common
|
|
50
|
+
top="$(git rev-parse --show-toplevel 2>/dev/null)" || return 1
|
|
51
|
+
gd="$(git rev-parse --git-dir 2>/dev/null)" || return 1
|
|
40
52
|
common="$(git rev-parse --git-common-dir 2>/dev/null)" || return 1
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
if [ "$gd" = "$common" ]; then
|
|
54
|
+
printf '%s\n' "$top" # main checkout — toplevel IS the main repo root
|
|
55
|
+
else
|
|
56
|
+
# linked worktree — the main root is the toplevel of the directory that
|
|
57
|
+
# contains `.worktrees/` (one level above this worktree).
|
|
58
|
+
(cd "$top/.." 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null) || return 1
|
|
59
|
+
fi
|
|
46
60
|
}
|
|
47
61
|
|
|
48
62
|
# --- Read a paths.* / git.* scalar from baldart.config.yml -----------------
|
|
@@ -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` +
|
|
@@ -64,6 +64,21 @@ protocol. The `/new` workflow scripts receive the resolved config via their
|
|
|
64
64
|
`args.config` payload (the consuming skill passes `baldart.config.yml`) — they
|
|
65
65
|
must never hard-code project facts (see the workflows contamination contract).
|
|
66
66
|
|
|
67
|
+
Since v4.42.0 the **execution-side** gates resolve through this protocol too: the
|
|
68
|
+
`worktree-manager` skill (`/nw` baseline + `/mw` pre-merge / rebase / post-merge
|
|
69
|
+
build gates), the classic `/new` reference suite (`implement`, `review-cycle`,
|
|
70
|
+
`completeness`, `final-review`, `commit`, `team-mode` — cited as `§ "Toolchain
|
|
71
|
+
gates"` from the `/new` core), and the standalone `/bug` + `/simplify` verify
|
|
72
|
+
steps. Two execution-context-specific resolution mechanisms are used: a markdown
|
|
73
|
+
**prose note** where a full model reads the skill and writes the gate command
|
|
74
|
+
(the `/new` references, `/bug`, `/simplify`, the agents), and an inline **shell
|
|
75
|
+
resolver** in `worktree-manager` (it has no `args.config` and its baseline runs in
|
|
76
|
+
a weak/background subagent, so it resolves `toolchain.commands.*` from
|
|
77
|
+
`baldart.config.yml` on disk — the same way it already resolves `git.trunk_branch`
|
|
78
|
+
— rather than relying on a prose note a weak model could skip). `npm install` is
|
|
79
|
+
**not** a gate (there is no `commands.install` key), and `markdownlint` is not in
|
|
80
|
+
the curated map — both stay as written everywhere.
|
|
81
|
+
|
|
67
82
|
## Fallback rules
|
|
68
83
|
|
|
69
84
|
- A configured command that EXITS NON-ZERO is a genuine gate **FAIL** — do not
|
|
@@ -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),
|