mirror of https://github.com/garrytan/gstack.git
82 lines
6.3 KiB
Markdown
82 lines
6.3 KiB
Markdown
# Fix #1671: `/office-hours` always reports SESSION_COUNT: 0
|
|
|
|
**Status:** SHIPPED
|
|
**Branch:** fix-1671-profile-migration
|
|
**Date:** 2026-05-23
|
|
**Issue:** https://github.com/garrytan/gstack/issues/1671
|
|
**Original PR that introduced the bug:** garrytan/gstack#1039 / commit `0a803f9` / v1.0.0.0 / 2026-04-18
|
|
|
|
## The problem
|
|
|
|
`/office-hours` reports `SESSION_COUNT: 0` and `TIER: introduction` on every invocation, even for users who have run the skill many times. The `welcome_back` tier (`bin/gstack-developer-profile:165-169`) that exists to skip the closing pitch for returning users is unreachable. Live ~5 weeks on every fresh-`$HOME` user since v1.0.0.0.
|
|
|
|
## Root cause
|
|
|
|
The v1.0.0.0 migration moved the read path to `~/.gstack/developer-profile.json` but left the writer in `office-hours/SKILL.md.tmpl` writing to the legacy `~/.gstack/builder-profile.jsonl`. The `ensure_profile` stub created on first read has `sessions: []`; subsequent writes go to a file the reader never re-reads. Reader and writer disagree on storage.
|
|
|
|
Full root-cause analysis (including RC2/RC3 follow-ups): https://github.com/garrytan/gstack/issues/1671
|
|
|
|
## The fix
|
|
|
|
Make the writer use the same file the reader does.
|
|
|
|
### Changes
|
|
|
|
1. **`bin/gstack-developer-profile`** — add `--log-session '<json>'` subcommand:
|
|
- Validates required fields (`date`, `mode`), silent-skip on invalid input (matches `bin/gstack-timeline-log:22-26`).
|
|
- Reads existing `developer-profile.json` via `bun -e`.
|
|
- Appends entry to `sessions[]`. Updates `signals_accumulated` (per-signal-string increment, same as `do_migrate:67-69`), unions `resources_shown` and `topics`.
|
|
- Atomic mktemp+mv write (matches existing pattern at line 54).
|
|
- Calls `gstack-brain-enqueue "developer-profile.json"` after write, mirroring `bin/gstack-timeline-log:40`.
|
|
|
|
2. **`bin/gstack-developer-profile:do_read`** — filter `mode:"resources"` entries when picking LAST_PROJECT / LAST_ASSIGNMENT / LAST_DESIGN_TITLE / CROSS_PROJECT / DESIGN_*. The Phase 6 resources auto-append happens after the real session in the same /office-hours invocation; without the filter, that resources entry clobbers real-session state for the user's next session. Latent bug that was masked by the broken writer; activated by the fix.
|
|
|
|
3. **`office-hours/SKILL.md.tmpl`** — swap writers at lines 490 and 893:
|
|
- From: `echo '{...}' >> "$GSTACK_STATE_ROOT/builder-profile.jsonl"`
|
|
- To: `~/.claude/skills/gstack/bin/gstack-developer-profile --log-session '{...}' 2>/dev/null || true`
|
|
- Run `bun run gen:skill-docs` to regenerate `office-hours/SKILL.md`.
|
|
|
|
### What's NOT in the fix (intentionally)
|
|
|
|
- **No new binary.** The owner binary for `developer-profile.json` is `gstack-developer-profile`; the writer belongs there as a subcommand. `--log-session` joins the binary's existing `--migrate` / `--derive` write-side subcommand boundary, not the `gstack-*-log` event-writer family. Verb name still matches `gstack-*-log`.
|
|
- **No mkdir-locks.** Concurrent /office-hours calls have a read-modify-write race on `developer-profile.json`. The codebase accepts the same race in `gstack-config` (r-m-w on YAML, no lock). Not introduced by this fix; out of scope.
|
|
- **No schema bump.** Schema stays at `schema_version: 1`. The fix doesn't change the schema, just makes the writer use it.
|
|
- **No auto-reconcile for affected users.** Existing users with stranded `builder-profile.jsonl` entries don't get their past history auto-merged into `developer-profile.json`. On their next /office-hours run, the first new session lands in `welcome_back`; past data stays in the legacy file (still readable by other tools during deprecation). Most affected users have only a handful of stranded sessions so the loss is mostly aesthetic. Dropped the one-release-only reconcile pathway as net noise — Garry's "right-sized diff" voice.
|
|
- **No autoplan timeline rollup (RC2).** Separate concern, separate PR.
|
|
- **No project-scope opt-in (RC3).** Separate concern, separate PR.
|
|
- **No gbrain glob change.** The office-hours manifest still globs `~/.gstack/builder-profile.jsonl` for context; once new writes stop landing there, the snapshot goes cold. Update in a follow-up if it becomes a UX issue.
|
|
|
|
### Tests (all gate-tier, free, deterministic)
|
|
|
|
1. **Regression test** in `test/gstack-developer-profile.test.ts`:
|
|
- Fresh `$HOME`.
|
|
- Run /office-hours preamble: gstack-developer-profile creates empty stub.
|
|
- Call `--log-session` with a startup-mode JSON.
|
|
- Run `--read` again. Assert `SESSION_COUNT: 1`, `TIER: welcome_back`.
|
|
- Fails on current main (subcommand doesn't exist). Passes with fix.
|
|
|
|
2. **`do_read` mode filter test:** after recording a startup session followed by a resources entry, `--read` returns LAST_PROJECT / LAST_ASSIGNMENT / LAST_DESIGN_TITLE from the real session, not from the resources entry. RESOURCES_SHOWN still aggregates correctly.
|
|
|
|
3. **Validation + aggregation tests:** `--log-session` silently skips invalid JSON / missing required fields, injects `ts` if missing, preserves user-set `ts`, correctly aggregates signals/resources/topics across multiple sessions.
|
|
|
|
4. **Static-grep invariant** in `test/static-no-legacy-writes.test.ts` (new): walks every skill dir, asserts no production code path writes to `builder-profile.jsonl` except allowlisted readers (`gstack-developer-profile`, `gstack-memory-ingest.ts`, `gstack-artifacts-init`, doc files). Prevents future writers from regressing onto the legacy file.
|
|
|
|
### Acceptance criteria
|
|
|
|
- Second `/office-hours` invocation on a fresh `$HOME` returns `TIER: welcome_back`.
|
|
- `bun test` passes on the touched files in isolation.
|
|
- `bun run gen:skill-docs` produces clean diff matching the `.tmpl` edits.
|
|
|
|
### Rollout
|
|
|
|
- One commit. PATCH version bump per CHANGELOG style guide.
|
|
- CHANGELOG entry written by `/ship`. User-facing voice: lead with what users experience now that they didn't before (welcome_back tier kicks in on second visit).
|
|
|
|
## Follow-up TODOs
|
|
|
|
- Deprecate `builder-profile.jsonl` entirely (writer + shim + memory-ingest type) after one release.
|
|
- Fix RC2 (autoplan inlines sub-skills, bypassing their timeline-log preambles).
|
|
- Add `GSTACK_PROFILE_SCOPE` opt-in for power users with multiple agent identities (RC3).
|
|
- /plan-tune doesn't currently call `--derive`, so `inferred`/`gap` can drift (pre-existing, unrelated to #1671).
|
|
- `mode:"resources"` entries inflate SESSION_COUNT under the existing tier aggregator (pre-existing, unrelated to #1671 root cause).
|