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@