T2b — bootstrap synthesizes draft entity content from CLAUDE.md + README
+ recent learnings.jsonl and emits as JSON for the caller. Skill template
is responsible for the AUQ-confirm-before-write flow (D10 T4 extraction-
review requirement). Cli stays pure (no AUQ logic); agent owns user
interaction.
T18 — list/purge subcommands close the lifecycle loop:
list [--project <slug>] — enumerate gstack-owned pages in brain
(probe all 8 gstack/* page types)
purge <slug> — delete one gstack page, refuses non-gstack/
slugs (defensive)
list defaults to all-projects (cross-project user-profile included).
With --project, filters to per-project pages plus the cross-project
user-profile. --json flag emits machine-readable output for the agent.
Retention sweep + audit subcommand are deferred to a follow-up commit
(they need the lifecycle scheduling design, not just CLI plumbing).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
D9 cross-model finding from codex outside voice: salience-sourced digests
can include emotionally-weighted personal pages (family, therapy,
reflection). Pulling those into a coding-review prompt leaks sensitive
context into work-flow reasoning.
fetchSalience now strips entries whose slugs don't match an allowlist
prefix BEFORE writing to the cache file. Default allowlist is
SALIENCE_DEFAULT_ALLOWLIST = ['projects/', 'concepts/', 'gstack/'].
User can extend via:
gstack-config set salience_allowlist 'projects/,gstack/,concepts/,custom/'
or override with GSTACK_SALIENCE_ALLOWLIST env var.
Digest still records the strip count for transparency. Empty result
emits 'all N entries stripped' note rather than silent absence.
test/salience-allowlist.test.ts: 9 tests covering default permits,
default blocks, empty allowlist, env override, whitespace trimming,
and the invariant that defaults contain nothing sensitive (personal,
family, therapy, reflection, private, medical, health).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When autoplan dispatches 4 planning skills back-to-back and they all hit
a cold-miss on the same digest, only ONE actually fetches from the brain.
The rest dedup via the project-scoped lockfile at
~/.gstack/projects/<slug>/brain-cache/.refresh.lock.
Reuses the 5-min stale-takeover convention from /sync-gbrain. Lock is
taken over when:
- File is older than CACHE_REFRESH_LOCK_TIMEOUT_MS
- PID is on the same host and dead (process.kill(pid, 0) fails)
- Lock file is corrupt (defensive)
withRefreshLock(projectSlug, fn) returns either the callback's value or
the literal 'dedup'. The CLI emits exit code 3 + diagnostic stderr on
dedup, so callers can choose to wait + retry (resolver does this) or
fall through to stale-but-usable behavior.
test/cache-concurrent-refresh.test.ts: 7 tests covering acquire/release,
stale-takeover, dead-PID takeover, corrupt-lock recovery, error-path
release, and cross-project lock location.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>