From b730e63a9ca308f4b4a93b73b69202e70413f9d1 Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 3 Oct 2026 21:14:47 +0000 Subject: [PATCH] W6 T1, T3, T4 (DX): two-question consent gates and transcript docs Both gates recognize every stored value form (unrecognized values are asked as not recognized by this version), ask the window (recent, all, new, off) and then the repo scope, and store nothing until both answers are known. setup-gbrain/memory.md#transcripts documents every value form, examples, clearing the allowlist, multi-root merging, what the purge removes and the downgrade steps. --- setup-gbrain/memory.md | 73 ++++++++++++++---- setup-gbrain/sections/transcript-gate.md.tmpl | 31 +++++--- sync-gbrain/SKILL.md.tmpl | 29 ++++++-- test/transcript-gate-values.test.ts | 74 +++++++++++++++++++ 4 files changed, 176 insertions(+), 31 deletions(-) create mode 100644 test/transcript-gate-values.test.ts diff --git a/setup-gbrain/memory.md b/setup-gbrain/memory.md index 16935b523..23a2c3485 100644 --- a/setup-gbrain/memory.md +++ b/setup-gbrain/memory.md @@ -25,13 +25,15 @@ answer does. 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: +key, optionally narrowed by the `transcript_repos` allowlist: | Value | What `/sync-gbrain` ingests | |---|---| | `recent` | Sessions from the last 90 days | | `all` | All history (`--full` walks every session file) | +| `new@2026-10-03T17:00:00Z` | Only sessions whose first record is at or after that UTC time. A session that started earlier stays out even when it is appended to later. A session with no start timestamp stays out. | | `off` | No transcripts; other memory still syncs | +| `recent+repos`, `all+repos`, `new@…+repos` | The same window, only for the repos in `transcript_repos`. gstack adds and removes `+repos` itself. | Both sources are covered: Claude Code sessions (`~/.claude/projects/`) and Codex sessions (`~/.codex/sessions/`), from every project on this machine @@ -43,38 +45,79 @@ Set or change it: ```bash gstack-config set transcript_ingest_mode recent # or all, or off +gstack-config set transcript_ingest_mode "new@$(date -u +%Y-%m-%dT%H:%M:%SZ)" # only sessions from now on +gstack-config set transcript_repos github.com/acme/app,github.com/acme/api # only these repos +gstack-config unset transcript_repos # every repo again gstack-config has transcript_ingest_mode && gstack-config get transcript_ingest_mode +bun run lib/transcript-consent.ts --describe # the effective consent in words ``` -`gstack-config set` rejects any other value and keeps the stored one. +`gstack-config set` rejects any other value and keeps the stored one. The +cutoff takes only the `Z` (UTC) form, to the second. `transcript_repos` +accepts any git remote spelling (`https://…`, `git@…:…`, with or without +`.git`) and stores its canonical form; an empty list is rejected. Setting +or clearing the allowlist updates the `+repos` marker in the same write. +`gstack-config set transcript_ingest_mode ` keeps the marker while an +allowlist exists, and `off` never carries it. + +**Several config roots.** When gstack reads more than one state root (for +example `GSTACK_HOME` plus `~/.gstack`), the most restrictive answer wins: +`off` in any root means off, the latest cutoff and any 90-day window both +apply, and only repos in every root's allowlist count. The resolved root +must hold the consent itself. **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. +and values this version does not recognize 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. +the sync still prints the mode notice. An override never widens a scoped +consent: a `new@` cutoff and the `transcript_repos` allowlist still apply. +`--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 ``` +**What each sync tells you.** Without `--quiet` the sync prints the consent +in words (window or cutoff, up to three repos, the config roots it came +from). The ingest summary counts sessions left out by reason: before the +cutoff, no start time, not in the allowlist, and repo policy deny. A sync +under `new@` that ingests nothing says so and says new sessions will appear +on the next sync. + **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. +transcript choice changed before the next run (the mode, the cutoff or the +allowlist), 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. +**Staged pages.** Transcript pages already staged under +`~/.gstack/transcripts/` (remote-http mode) wait while the mode is not +`recent`, `all` or `new@…`: they stay on disk and are not pushed by +`gstack-brain-sync`. Other artifacts keep pushing. When you narrow a scoped +consent (a later cutoff, a smaller allowlist), the next sync removes the +staged pages that are now out of scope and prints `removed N staged +transcript pages outside the new scope`; widening again re-stages them from +the original sessions. Before every push, `gstack-brain-sync` also checks +its own unpushed commits and rewrites them without any page outside your +consent (`removed N excluded transcript pages from unpublished sync +commits`). The removal covers staged and unpushed pages only. -**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). +**Already-ingested transcripts stay.** Switching to `off` or narrowing the +scope stops new ingests; it does not remove pages already in the brain or +already pushed. Delete them there (see "Delete a page" below). + +**Downgrading gstack.** A gstack that predates `new@` and `transcript_repos` +reads those values as unrecognized and ingests no transcripts +automatically. It does honour an explicit transcript override without any +scope, so before downgrading: remove `--sources transcript` and +`GSTACK_MEMORY_INGEST_SOURCES=transcript` from scripts, and let pending sync +publish, or run `gstack-config set transcript_ingest_mode off`. ## What stays local diff --git a/setup-gbrain/sections/transcript-gate.md.tmpl b/setup-gbrain/sections/transcript-gate.md.tmpl index 3751a1e23..422a5baeb 100644 --- a/setup-gbrain/sections/transcript-gate.md.tmpl +++ b/setup-gbrain/sections/transcript-gate.md.tmpl @@ -3,24 +3,27 @@ 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`. +`recent`, `all` or `new` (only sessions that start from now on). 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 _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 + _T='[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]t[0-9][0-9]:[0-9][0-9]:[0-9][0-9]z' 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)" ;; + recent|all|off|recent+repos|all+repos|new@$_T|new@$_T+repos) + echo "TRANSCRIPT_MODE: $_TIM (repos: $(~/.claude/skills/gstack/bin/gstack-config get transcript_repos 2>/dev/null || true))" ;; + *) echo "TRANSCRIPT_MODE: ask (stored value '$_TIM' is not recognized by this version)" ;; esac else echo "TRANSCRIPT_MODE: ask (not set)" fi ``` -- `TRANSCRIPT_MODE: recent`, `all` or `off`: the user already chose. Do not - ask again; continue to Step 8. +- `TRANSCRIPT_MODE:` with a value (`recent`, `all`, `off`, `new@