W4: transcript consent prose in /setup-gbrain, /sync-gbrain and memory.md

The Step 7.5 gate is rewritten as a consent question (manual merge of s09 H5,
with H1b and H6): it checks gstack-config has, asks once with A) recent,
B) all history, E) never, names Claude Code and Codex sessions from every
repo-policy-allowed project and the destination brain, stores the mode value
(never the letter), asks yes/no when no transcripts exist, never asks
spawned or headless sessions, and drops the silent no-question bulk paths
and the false skill-start-hook claim (s09 H5c). Path 4 no longer stores off
on the user's behalf. /sync-gbrain gains Step 1.6 (ask when the key is absent
or holds a legacy value). Step 10 and the sync verdict show the mode.
setup-gbrain/memory.md gains #transcripts (values, sources, destination,
override precedence, one-time restage, held staged pages, already-ingested
pages stay).

W7a: setup-gbrain-bin-invocation-paths drops the silent-bulk and
preamble-hook pins (that prose is gone) for negative checks (no pre-answer
--bulk, no skill-start claim), loosens the gstack-config pin (t06 #18) to
the command without <choice>, and adds the DX 'gated on has' order check for
both skills.
This commit is contained in:
garrytan committed 2026-10-03 00:08:46 +00:00
1 parent 26c6bf263f
commit 67bd3d6267
6 files changed
+220 -69

No files matched your search

+10 -6
View File
@@ -397,14 +397,15 @@ the prereq is fixed.
---
## Step 7.5: Transcript & memory ingest gate
## Step 7.5: Transcript ingest consent
**SKIP entirely on Path 4 (Remote MCP).** Transcript ingest shells out to
the local `gbrain` CLI which Path 4 doesn't install. Remote-mode users
rely on the brain server's own ingest cadence — if your brain admin wants
this machine's transcripts indexed, they pull from your `gstack-artifacts-$USER`
repo (set up in Step 7) on whatever schedule they prefer. Set
`gstack-config set transcript_ingest_mode off` and continue to Step 8.
repo (set up in Step 7) on whatever schedule they prefer. Do not store a
transcript mode for the user here; transcripts stay skipped until they choose
(`/sync-gbrain` asks). Continue to Step 8.
For Paths 1, 2a, 2b, 3, run the ingest gate:
@@ -541,7 +542,7 @@ state, repairs only what's missing, and reports here.
```bash
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"; : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
~/.claude/skills/gstack/bin/gstack-gbrain-detect 2>/dev/null || true
~/.claude/skills/gstack/bin/gstack-config get transcript_ingest_mode 2>/dev/null || echo "off"
~/.claude/skills/gstack/bin/gstack-config has transcript_ingest_mode && echo "transcript_ingest_mode: $(~/.claude/skills/gstack/bin/gstack-config get transcript_ingest_mode)" || echo "transcript_ingest_mode: not set"
~/.claude/skills/gstack/bin/gstack-config get artifacts_sync_mode 2>/dev/null || echo "off"
[ -f "$GSTACK_STATE_ROOT"/.gbrain-sync-state.json ] && cat "$GSTACK_STATE_ROOT"/.gbrain-sync-state.json || echo "{}"
```
@@ -561,7 +562,7 @@ gbrain status: GREEN (mode: remote-http)
Repo policy ..... OK {read-write|read-only|deny}
Artifacts repo .. OK {gstack_artifacts_remote URL}
Artifacts sync .. OK {artifacts_sync_mode}
Transcripts ..... OK route to artifacts repo → remote brain
Transcripts ..... OK {recent|all}: route to artifacts repo → remote brain | INFO {off|not set}: skipped
Code search ..... {OK local-pglite (~/.gbrain/pglite) | N/A declined at Step 4d}
CLAUDE.md ....... OK
Smoke test ...... INFO printed for post-restart manual verification
@@ -592,13 +593,16 @@ gbrain status: GREEN (mode: local-stdio)
Repo policy ..... OK <read-write|read-only|deny>
Code import ..... OK <last_imported_head>
Artifacts sync .. OK <artifacts_sync_mode> to <remote>
Transcripts ..... OK <N> sessions, last ingest <when>
Transcripts ..... OK <recent|all>: <N> sessions, last ingest <when> | INFO <off|not set>: skipped
CLAUDE.md ....... OK
Smoke test ...... OK put → search round-trip
Run `/setup-gbrain` again any time gbrain feels off; it's safe and idempotent.
```
A `not set` transcript mode is not a failure: transcripts stay skipped until
the user chooses, and `/sync-gbrain` asks.
If any row is YELLOW or RED, the verdict line says so and the failing rows
surface a one-line "next action" (e.g.,
`Engine .......... ERR PGLite corrupt — re-import from a clone of your brain remote with \`gbrain import\``).
+60 -4
View File
@@ -1,9 +1,9 @@
# gstack memory ingest — what it does, what stays local, what you can do with it
This is the user-facing reference for the V1 transcript + memory ingest
feature in `/setup-gbrain`. If you ran `/setup-gbrain` and it asked
"Ingest THIS repo's transcripts into gbrain?", this doc explains what
happens after you say yes.
This is the user-facing reference for the transcript + memory ingest
feature in `/setup-gbrain` and `/sync-gbrain`. If either asked whether to
ingest your coding-agent sessions into gbrain, this doc explains what each
answer does.
## What gets ingested
@@ -20,6 +20,62 @@ happens after you say yes.
| Retros | `retro` | `~/.gstack/projects/<slug>/retros/*.md` | Medium |
| Builder profile | `builder-profile-entry` | `~/.gstack/builder-profile.jsonl` | Low |
## Transcripts
Curated memory (learnings, timeline, plans, designs, retros, eureka, builder
profile) syncs on every `/sync-gbrain`. Session transcripts sync only after
you choose to share them. The choice is the `transcript_ingest_mode` config
key:
| Value | What `/sync-gbrain` ingests |
|---|---|
| `recent` | Sessions from the last 90 days |
| `all` | All history (`--full` walks every session file) |
| `off` | No transcripts; other memory still syncs |
Both sources are covered: Claude Code sessions (`~/.claude/projects/`) and
Codex sessions (`~/.codex/sessions/`), from every project on this machine
that your per-remote trust policy allows, not only the current repo. Pages go
to your brain: the local engine (PGLite or Supabase) in local-stdio mode, or
the artifacts repo the remote brain pulls from in remote-http mode.
Set or change it:
```bash
gstack-config set transcript_ingest_mode recent # or all, or off
gstack-config has transcript_ingest_mode && gstack-config get transcript_ingest_mode
```
`gstack-config set` rejects any other value and keeps the stored one.
**Not set.** Until you choose, transcripts are skipped. Each sync prints one
line saying so, even with `--quiet`, and the next interactive `/sync-gbrain`
asks once. Older values from previous gate versions (`A`-`E`, `incremental`)
also count as not chosen and get the question again. A stored `off` prints
`transcripts off (your choice)` without `--quiet` and is never asked again.
**One-run override.** A `--sources` list (or `GSTACK_MEMORY_INGEST_SOURCES`)
that names `transcript` ingests transcripts for that run whatever the mode;
the sync still prints the mode notice. `--sources all`, an empty list, or a
list with no valid types does not override.
```bash
bun run bin/gstack-gbrain-sync.ts --incremental --sources transcript
```
**Interrupted imports.** If a sync was interrupted mid-import and your
transcript choice changed before the next run, the next sync restages memory
from scratch once instead of resuming, and prints a line saying so.
**Staged pages wait.** Transcript pages already staged under
`~/.gstack/transcripts/` (remote-http mode) stay on disk and are not pushed
by `gstack-brain-sync` until the mode is `recent` or `all`. Other artifacts
keep pushing.
**Already-ingested transcripts stay.** Switching to `off` stops new ingests;
it does not remove pages already in the brain. Delete them there (see
"Delete a page" below).
## What stays local
- **State files** (`~/.gstack/.gbrain-sync-state.json`,
+1 -1
View File
@@ -19,7 +19,7 @@
{
"id": "transcript-gate",
"file": "transcript-gate.md",
"title": "Step 7.5: transcript & memory ingest gate (probe, thresholds, ingest AskUserQuestion)",
"title": "Step 7.5: transcript ingest consent (has-check, probe, recent/all/off AskUserQuestion)",
"trigger": "running the Step 7.5 transcript & memory ingest gate on Paths 1, 2a, 2b, or 3 (Path 4 skips this section entirely — see the skeleton's skip note)"
},
{
+71 -45
View File
@@ -1,64 +1,90 @@
After memory sync is wired (Step 7) but before persisting the CLAUDE.md
config (Step 8), offer to bring this Mac's coding-agent transcripts +
curated `~/.gstack/` artifacts into gbrain so the retrieval surface
(per-skill manifests, salience block) has data to surface.
config (Step 8), ask whether this machine's coding-agent session
transcripts may go into gbrain. Curated `~/.gstack/` artifacts (learnings,
timeline, plans, designs, retros) sync whatever the answer; this question
covers transcripts only. Transcripts are ingested only after the user picks
`recent` or `all`.
Run the probe to size the operation:
Check whether the user already chose. The gate keys on `has`, because `get`
prints the `off` default for a key that was never set:
```bash
bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe
_TIM=$(~/.claude/skills/gstack/bin/gstack-config get transcript_ingest_mode 2>/dev/null || true)
if ~/.claude/skills/gstack/bin/gstack-config has transcript_ingest_mode; then
case "$(printf '%s' "$_TIM" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')" in
recent|all|off) echo "TRANSCRIPT_MODE: $_TIM" ;;
*) echo "TRANSCRIPT_MODE: ask (stored value '$_TIM' is from an older version)" ;;
esac
else
echo "TRANSCRIPT_MODE: ask (not set)"
fi
```
Read the output. If `Total files in window: 0`, skip — there's nothing
to ingest. Set `gstack-config set transcript_ingest_mode incremental`
silently and continue to Step 8.
- `TRANSCRIPT_MODE: recent`, `all` or `off`: the user already chose. Do not
ask again; continue to Step 8.
- `TRANSCRIPT_MODE: ask`: ask once, below. With `SESSION_KIND: spawned` or
`headless`, do not ask and do not store a value. Consent is never
auto-chosen; leave the key unset (transcripts stay skipped) and report
that the next interactive `/sync-gbrain` will ask.
If `New (never ingested)` is < 200 AND total bytes are < 100MB: silent
bulk via `bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --bulk --quiet`. Set
`transcript_ingest_mode=incremental` and continue.
Size the question with two probes. `--sources transcript` makes the probe
count transcripts even though none are consented yet:
```bash
bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe --sources transcript
bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe --sources transcript --all-history
```
Read `Total files in window`, `Total bytes` and the estimate from each.
Otherwise (the "many transcripts on disk" path): AskUserQuestion with
the exact counts AND the value promise. Default scope is **current repo
only, last 90 days**:
**No transcripts found** (both probes report 0): ask yes/no instead:
> "Found <N_repo> transcripts in THIS repo (<repo-slug>) over the last
> 90 days, plus <N_other> across other repos on this machine (<bytes>
> total if all ingested). Ingest THIS repo's transcripts into gbrain?
> "Ingest coding-agent sessions as they appear?"
Yes stores `recent`; No stores `off`.
**Otherwise**, AskUserQuestion with the counts. Name both session sources
and the destination brain from Step 4: the user's Supabase brain (Paths 1,
2a, 2b) or the local PGLite brain on this machine (Path 3).
> "Found <N_recent> Claude Code and Codex sessions from the last 90 days
> (<N_all> in all history, <bytes>) across the projects on this machine
> that your repo policy allows. Ingest them into your <Supabase | local
> PGLite> brain?
>
> What you get after this: every gstack skill auto-loads recent salience
> from your past sessions in this repo, so the agent finds your prior
> work without you describing it. You can query 'what was I doing on
> day X' and get a real answer. Per-session pages are searchable,
> taggable, and deletable. Secret scanning runs before any push.
> What you get: gstack skills load recent context from your past sessions,
> so the agent finds your prior work without you describing it, and you
> can ask 'what was I doing on day X'. Per-session pages are searchable,
> taggable and deletable. Secret scanning runs before any push.
>
> What stays the same: nothing leaves your machine unless gbrain sync
> is enabled (Step 7). Per-repo trust policies still apply.
> What stays the same: nothing leaves this machine unless brain sync is
> enabled (Step 7). Per-repo trust policies still apply. The first full
> sync can take a while on a large history (estimate: <est>); skills stay
> usable while it runs.
>
> Multi-Mac note: if you HAVE enabled brain sync (Step 7), these
> transcript pages will sync across your Macs. Caveat: deleting a
> transcript page later removes it from gbrain but git history retains
> it in prior commits. Use `gstack-transcript-prune` to delete in bulk;
> use `git filter-repo` on the brain remote for hard-delete from
> history."
> Multi-machine note: with brain sync enabled, transcript pages sync across
> your machines. Deleting a page later removes it from gbrain, but git
> history keeps it in earlier commits. Use `gstack-transcript-prune` to
> delete in bulk, and `git filter-repo` on the brain remote to remove it
> from history."
Options:
- A) Yes — this repo, last 90 days (recommended; ~est min)
- B) Yes — this repo, ALL history
- C) Yes — this repo + other repos on this machine
- D) Skip historical, track new from now (`transcript_ingest_mode=incremental`)
- E) Never ingest transcripts (`transcript_ingest_mode=off`)
- A) Yes, last 90 days (`recent`)
- B) Yes, all history (`all`)
- E) No, never ingest transcripts (`off`)
After answer:
Store the mode value, never the letter:
```bash
~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode <recent|all|off>
```
For `recent` or `all`, run the first sync now:
```bash
~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode <choice>
bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --full --no-brain-sync
```
(`--no-brain-sync` because Step 7 already wired that path; this just
runs the code import + memory ingest stages. Brain-sync will run on the
next preamble hook.)
(`--no-brain-sync` because Step 7 already wired that path; this runs the
code import and memory ingest stages, and `all` walks the full history.)
For `off`, run nothing here; other memory still syncs on the next
`/sync-gbrain`.
If A/D/E, ingest is incremental from this point on; preamble-boundary
hook runs `bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --incremental --quiet` on every skill
start (cheap mtime fast-path).
New transcripts are ingested on the next `/sync-gbrain` run. Nothing at
skill start ingests them.
Reference doc for users: `setup-gbrain/memory.md` (linked from CLAUDE.md
Step 8).
Reference doc for users: `setup-gbrain/memory.md#transcripts`.
+57 -1
View File
@@ -53,7 +53,7 @@ the skill itself, not a dispatcher binary):
- `/sync-gbrain --code-only` — only run the code stage; skip memory + brain-sync
- `/sync-gbrain --dry-run` — preview what would sync; no writes anywhere
- `/sync-gbrain --no-memory` / `--no-brain-sync` — selectively skip stages
- `/sync-gbrain --sources <types|all>` — memory types to ingest (env `GSTACK_MEMORY_INGEST_SOURCES`); by default, types a federated gstack source already indexes are skipped
- `/sync-gbrain --sources <types|all>` — memory types to ingest (env `GSTACK_MEMORY_INGEST_SOURCES`); by default, types a federated gstack source already indexes are skipped, and transcripts are skipped unless `transcript_ingest_mode` is `recent` or `all` (a list naming `transcript` overrides that for one run)
- `/sync-gbrain --quiet` — suppress per-stage output
- `/sync-gbrain --refresh-cache` — force-rebuild brain-aware planning cache. Skips code + memory stages; routes to `gstack-brain-cache refresh --project <slug>`.
- `/sync-gbrain --audit` — emit summary of gstack-owned pages per project + sensitive-content audit. Read-only.
@@ -187,6 +187,61 @@ gets the actionable remediation message.
---
## Step 1.6: Transcript consent
Claude Code and Codex session transcripts are ingested only when
`transcript_ingest_mode` is `recent` (last 90 days) or `all` (all history).
Skip this step for `--code-only`, `--no-memory`, `--dry-run`,
`--refresh-cache` and `--audit`. Otherwise check whether the user chose;
`has` tells an absent key from the `off` default that `get` prints:
```bash
_TIM=$(~/.claude/skills/gstack/bin/gstack-config get transcript_ingest_mode 2>/dev/null || true)
if ~/.claude/skills/gstack/bin/gstack-config has transcript_ingest_mode; then
case "$(printf '%s' "$_TIM" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')" in
recent|all|off) echo "TRANSCRIPT_MODE: $_TIM" ;;
*) echo "TRANSCRIPT_MODE: ask (stored value '$_TIM' is from an older version)" ;;
esac
else
echo "TRANSCRIPT_MODE: ask (not set)"
fi
```
`recent`, `all` or `off`: continue to Step 2 without asking. On `ask`, with
`SESSION_KIND: spawned` or `headless`, do not ask and store nothing: the sync
skips transcripts and prints how to choose. In an interactive session, ask
once. Count first (`--sources transcript` counts sessions that are not
consented yet):
```bash
bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe --sources transcript
bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe --sources transcript --all-history
```
If both report `Total files in window: 0`, ask yes/no: "Ingest coding-agent
sessions as they appear?" Yes stores `recent`, No stores `off`. Otherwise
AskUserQuestion: name both sources (Claude Code and Codex sessions from every
project on this machine that repo policy allows), the counts from the two
probes, and the destination: the local brain (`gbrain_engine` from Step 1:
PGLite or Supabase) or, in remote-http mode, the artifacts repo the remote
brain pulls from. Say that a full sync of a large history can take a while
and skills stay usable meanwhile. Options:
- A) Yes, last 90 days (`recent`)
- B) Yes, all history (`all`)
- E) No, never ingest transcripts (`off`)
Store the value, never the letter, then continue to Step 2:
```bash
~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode <recent|all|off>
```
Other memory types sync whatever the answer. Details:
`setup-gbrain/memory.md#transcripts`.
---
## Step 2: Run the orchestrator
Pass user args to the orchestrator. Do not paraphrase them — pass through
@@ -444,6 +499,7 @@ gbrain status: GREEN
Call graph ...... OK <N> edges resolved (code-callers/callees live)
~/.gstack source. OK <gstack-brain-{user}> (page_count=<N>) — managed by /setup-gbrain
Memory sync ..... OK <artifacts_sync_mode>
Transcripts ..... OK <recent|all> | INFO <off|not set>: skipped
CLAUDE.md ....... OK ## GBrain Search Guidance present
Last sync ....... OK <last_sync from state file>
+21 -12
View File
@@ -74,10 +74,8 @@ describe('setup-gbrain templates (skeleton + sections) — bin invocation paths'
);
});
test('the silent-bulk mention uses bun run + .ts (R2, transcript-gate section)', () => {
expect(transcriptGate).toContain(
'bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --bulk --quiet'
);
test('the gate never bulk-ingests before the user answers (transcript-gate section)', () => {
expect(transcriptGate).not.toMatch(/gstack-memory-ingest\.ts --bulk/);
});
test('the post-answer full-sync step uses bun run + .ts (R3, transcript-gate section)', () => {
@@ -86,19 +84,30 @@ describe('setup-gbrain templates (skeleton + sections) — bin invocation paths'
);
});
test('the preamble-hook incremental-sync mention uses bun run + .ts (R4, transcript-gate section)', () => {
expect(transcriptGate).toContain(
'bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --incremental --quiet'
);
test('no skill-start hook is claimed to ingest transcripts; /sync-gbrain is named (transcript-gate section)', () => {
expect(transcriptGate).not.toMatch(/every skill\s+start/i);
expect(transcriptGate).toContain('/sync-gbrain');
});
test('the neighboring gstack-config line in the post-answer block is untouched (bash script, no extension)', () => {
expect(transcriptGate).toContain(
'~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode <choice>'
);
test('the post-answer gstack-config line stores a mode value, not the answer letter (bash script, no extension)', () => {
expect(transcriptGate).toContain('~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode');
expect(transcriptGate).not.toContain('transcript_ingest_mode <choice>');
});
});
describe('transcript consent question is gated on `gstack-config has`', () => {
const syncTmpl = fs.readFileSync(path.join(ROOT, 'sync-gbrain', 'SKILL.md.tmpl'), 'utf-8');
for (const [name, text] of [['setup-gbrain transcript gate', transcriptGate], ['sync-gbrain', syncTmpl]] as const) {
test(`${name} checks presence with has before asking, and never asks spawned or headless sessions`, () => {
const hasAt = text.indexOf('gstack-config has transcript_ingest_mode');
expect(hasAt).toBeGreaterThanOrEqual(0);
expect(text.indexOf('AskUserQuestion', hasAt)).toBeGreaterThan(hasAt);
for (const v of ['recent', 'all', 'off']) expect(text).toContain(`\`${v}\``);
expect(text).toMatch(/spawned[\s\S]{0,40}headless[\s\S]{0,80}do not ask/i);
});
}
});
describe('setup-gbrain/memory.md — bin invocation paths', () => {
test('no bare gstack-memory-ingest invocation remains', () => {
expect(memoryDoc).not.toMatch(bareMemoryIngest);