Skip to content

Commit 7a9faaa

Browse files
Willardgmooreclaude
andcommitted
chore: merge origin/main (v1.33.2.0) into plan-status-skill
Brings in: - v1.33.2.0: setup guard against Conductor worktree pollution (garrytan#1446) - v1.33.1.0: learnings token-OR query + task-shaped retrieval (garrytan#1442) - v1.33.0.0: /sync-gbrain memory stage batch-import refactor (garrytan#1432) VERSION stays at 1.34.0.0 (no collision — queue-aware check confirms). CHANGELOG: our [1.34.0.0] entry at top, three new main entries below. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2 parents db64267 + dc6252d commit 7a9faaa

23 files changed

Lines changed: 2143 additions & 229 deletions

CHANGELOG.md

Lines changed: 156 additions & 0 deletions
Large diffs are not rendered by default.

CLAUDE.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,3 +778,40 @@ Key routing rules:
778778
- Ship/deploy/PR → invoke /ship or /land-and-deploy
779779
- Save progress → invoke /context-save
780780
- Resume context → invoke /context-restore
781+
782+
## GBrain Search Guidance (configured by /sync-gbrain)
783+
<!-- gstack-gbrain-search-guidance:start -->
784+
785+
GBrain is set up and synced on this machine. The agent should prefer gbrain
786+
over Grep when the question is semantic or when you don't know the exact
787+
identifier yet.
788+
789+
**This worktree is pinned to a worktree-scoped code source** via the
790+
`.gbrain-source` file in the repo root (kubectl-style context). Any
791+
`gbrain code-def`, `code-refs`, `code-callers`, `code-callees`, or `query`
792+
call from anywhere under this worktree routes to that source by default —
793+
no `--source` flag needed. Conductor sibling worktrees of the same repo
794+
each have their own pin and their own indexed pages, so semantic results
795+
match the actual code on disk in this worktree.
796+
797+
Two indexed corpora available via the `gbrain` CLI:
798+
- This worktree's code (auto-pinned via `.gbrain-source`).
799+
- `~/.gstack/` curated memory (registered as `gstack-brain-<user>` source via
800+
the existing federation pipeline).
801+
802+
Prefer gbrain when:
803+
- "Where is X handled?" / semantic intent, no exact string yet:
804+
`gbrain search "<terms>"` or `gbrain query "<question>"`
805+
- "Where is symbol Y defined?" / symbol-based code questions:
806+
`gbrain code-def <symbol>` or `gbrain code-refs <symbol>`
807+
- "What calls Y?" / "What does Y depend on?":
808+
`gbrain code-callers <symbol>` / `gbrain code-callees <symbol>`
809+
- "What did we decide last time?" / past plans, retros, learnings:
810+
`gbrain search "<terms>" --source gstack-brain-<user>`
811+
812+
Grep is still right for known exact strings, regex, multiline patterns, and
813+
file globs. Run `/sync-gbrain` after meaningful code changes; for ongoing
814+
auto-sync across all worktrees, run `gbrain autopilot --install` once per
815+
machine — gbrain's daemon handles incremental refresh on a schedule.
816+
817+
<!-- gstack-gbrain-search-guidance:end -->

TODOS.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,67 @@ Check whether @wwybdd23-bot has replied to our clarifying questions on GitHub is
1010

1111
---
1212

13+
## /sync-gbrain memory stage perf follow-up
14+
15+
### P2: Investigate `gbrain import` perf on large staging dirs
16+
17+
**What:** Cold-run time on a 5131-file staging dir is >10 min in `gbrain import`
18+
alone (after gstack's prepare phase, which is now <10s after dropping per-file
19+
gitleaks). On 501 files it took 10s. The scaling is worse than linear and the
20+
bottleneck is inside gbrain, not the gstack orchestrator.
21+
22+
**Why:** With memory-ingest's prepare phase now fast, the remaining cold-run cost
23+
is entirely on the gbrain side. Users with large corpora (5K+ files) currently pay
24+
~15-30 min on first ingest. Likely culprits in `~/git/gbrain/src/core/import-file.ts`:
25+
26+
- N+1 SQL queries: `engine.getPage(slug)` for each file's content_hash check
27+
(line 242 + 478) — should be batched into a single query
28+
- Per-page auto-link reconciliation that fires even for unchanged content
29+
- FTS / vector index updates without batching transactions
30+
31+
**Pros:** Lives in gbrain (cleaner separation). Fix in gbrain benefits other
32+
gbrain callers too (`gbrain sync`, MCP `put_page` workflows). Likely 10-50x
33+
speedup from batched queries alone.
34+
35+
**Cons:** Cross-repo change, requires gbrain test coverage for the new batched
36+
path. Not on the gstack critical path; gstack's architecture is already correct.
37+
38+
**Context:** Verified on real corpus 2026-05-10. gstack-side prepare with
39+
`--scan-secrets` off runs in <10s. The full gbrain import on the same staged
40+
dir consumes 100% CPU for >10 min. Both observations from
41+
`bin/gstack-memory-ingest.ts:ingestPass` reaching the `runGbrainImport` call
42+
quickly, then the child process taking the bulk of the wall time.
43+
44+
**Depends on:** None — gstack's batch-ingest architecture (D1-D8 in
45+
`docs/designs/SYNC_GBRAIN_BATCH_INGEST.md`) is already shipped and correct.
46+
47+
---
48+
49+
### P3: Cache "no changes since last import" at the prepare-batch level
50+
51+
**What:** Even with the prepare phase fast (<10s for 5135 files), walking and
52+
mtime-stat'ing every file on a true no-op run adds a few seconds and creates
53+
spurious staging dirs. Cache the most-recent-source-mtime per-source in the
54+
state file; if no source dir has a newer mtime, skip the walk + stage + import
55+
entirely.
56+
57+
**Why:** Most `/sync-gbrain` invocations have nothing new to ingest. The
58+
fastest path is "do nothing, fast." `gbrain doctor` should still report state,
59+
but the actual ingest pipeline can short-circuit when last_full_walk is recent
60+
and no source-tree mtime has moved.
61+
62+
**Pros:** Trivial implementation (~20 lines in `ingestPass`). Makes the
63+
incremental fast-path actually live up to "<30s" in the original plan.
64+
65+
**Cons:** Adds a cache invalidation surface. If a user edits a file but its
66+
parent dir's mtime doesn't update (rare on macOS APFS), changes get missed.
67+
Mitigation: only short-circuit when last_full_walk is recent (e.g. <1 min ago).
68+
69+
**Context:** Filed during 2026-05-10 perf testing after `--scan-secrets` was
70+
made opt-in. Lower priority than the gbrain-side perf issue above.
71+
72+
---
73+
1374
## Browser-skills follow-on (Phases 2-4)
1475

1576
### P1: Browser-skills Phase 2 — `/scrape` and `/skillify` skill templates

bin/gstack-gbrain-sync.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -442,14 +442,30 @@ function runMemoryIngest(args: CliArgs): StageResult {
442442
timeout: 35 * 60 * 1000,
443443
});
444444

445-
const summary = (result.stderr || "").split("\n").filter((l) => l.includes("[memory-ingest]")).slice(-1)[0] || "ingest pass complete";
446-
445+
// D6: parse [memory-ingest] lines from the child's stderr. ERR-prefixed
446+
// lines indicate a system-level failure (gbrain crashed or CLI missing)
447+
// and the child exits non-zero. Per-file failures are summarized in the
448+
// last non-ERR [memory-ingest] line but do NOT make the verdict ERR.
449+
const stderrLines = (result.stderr || "").split("\n");
450+
const memLines = stderrLines.filter((l) => l.includes("[memory-ingest]"));
451+
const errLine = memLines.find((l) => l.includes("[memory-ingest] ERR"));
452+
const lastMemLine = memLines.slice(-1)[0];
453+
const rawSummary = errLine || lastMemLine || "ingest pass complete";
454+
// Strip the "[memory-ingest] " prefix and any leading "ERR: " for cleaner
455+
// verdict output. The orchestrator's own formatStage will prefix with OK/ERR.
456+
const summary = rawSummary
457+
.replace(/^.*\[memory-ingest\]\s*/, "")
458+
.replace(/^ERR:\s*/, "");
459+
460+
const ok = result.status === 0;
447461
return {
448462
name: "memory",
449463
ran: true,
450-
ok: result.status === 0,
464+
ok,
451465
duration_ms: Date.now() - t0,
452-
summary: result.status === 0 ? summary : `memory ingest exited ${result.status}`,
466+
summary: ok
467+
? summary
468+
: `${summary}${result.status === null ? " (killed by signal / timeout)" : ` (exit ${result.status})`}`,
453469
};
454470
}
455471

bin/gstack-learnings-search

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ cat "${FILES[@]}" 2>/dev/null | GSTACK_SEARCH_TYPE="$TYPE" GSTACK_SEARCH_QUERY="
4848
const lines = (await Bun.stdin.text()).trim().split('\n').filter(Boolean);
4949
const now = Date.now();
5050
const type = process.env.GSTACK_SEARCH_TYPE || '';
51-
const query = (process.env.GSTACK_SEARCH_QUERY || '').toLowerCase();
51+
const queryRaw = (process.env.GSTACK_SEARCH_QUERY || '').toLowerCase();
52+
const queryTokens = queryRaw.split(/\s+/).filter(Boolean);
5253
const limit = parseInt(process.env.GSTACK_SEARCH_LIMIT || '10', 10);
5354
const slug = process.env.GSTACK_SEARCH_SLUG || '';
5455
@@ -94,12 +95,11 @@ let results = Array.from(seen.values());
9495
// Filter by type
9596
if (type) results = results.filter(e => e.type === type);
9697
97-
// Filter by query
98-
if (query) results = results.filter(e =>
99-
(e.key || '').toLowerCase().includes(query) ||
100-
(e.insight || '').toLowerCase().includes(query) ||
101-
(e.files || []).some(f => f.toLowerCase().includes(query))
102-
);
98+
// Filter by query (token-OR: match if ANY whitespace-split token appears in ANY haystack)
99+
if (queryTokens.length > 0) results = results.filter(e => {
100+
const haystacks = [(e.key || '').toLowerCase(), (e.insight || '').toLowerCase(), ...(e.files || []).map(f => f.toLowerCase())];
101+
return queryTokens.some(tok => haystacks.some(h => h.includes(tok)));
102+
});
103103
104104
// Sort by effective confidence desc, then recency
105105
results.sort((a, b) => {

0 commit comments

Comments
 (0)