mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-31 07:19:31 +02:00
64f9aafa1e
* fix(office-hours): #1671 — session writer was writing to the legacy file User-visible symptom: returning /office-hours users get the same closing pitch every visit, no matter how many times they've run the skill. The welcome_back tier (which exists specifically to skip the pitch for returning users) was unreachable. Live since 2026-04-18 / v1.0.0.0 on every fresh-$HOME user. 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. Reader and writer disagreed on storage, so SESSION_COUNT never incremented and /office-hours always treated the user as a first-timer. Fix: - bin/gstack-developer-profile: new --log-session subcommand that read-modify-writes developer-profile.json's sessions[] array (atomic mktemp+mv, signals/resources/topics aggregation, gbrain-enqueue mirror of gstack-timeline-log:40). Naming matches the gstack-*-log family verb. - bin/gstack-developer-profile: do_read filters mode:"resources" entries when picking LAST_PROJECT/LAST_ASSIGNMENT/LAST_DESIGN_TITLE so the Phase 6 resources auto-append doesn't clobber real-session state. Latent bug that was masked by the broken writer; activated by the fix. - office-hours/SKILL.md.tmpl: lines 490 + 893 swap echo >> for --log-session. - test/gstack-developer-profile.test.ts: +8 tests covering --log-session contract (regression, aggregation, dedup, validation, ts handling) plus the mode-filter regression. All 8 fail on main, all 8 pass with this fix. - test/static-no-legacy-writes.test.ts: new static-grep invariant walking every skill dir to prevent future regressions onto the legacy file. Affected users: stranded builder-profile.jsonl entries are not recovered automatically by this PR. 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 pre-existing users have only a handful of stranded sessions. See docs/designs/FIX_1671_PROFILE_MIGRATION.md for scope decisions (RC2/RC3 follow-ups, what was intentionally left out, and why). Issue: #1671 * test(office-hours): refine #1671 invariant regex comment for literal-path scope Clarifies that the WRITE_PATTERN regex catches literal-path writes only; variable-indirected writes (FILE=...; echo >> "$FILE") are not detected. The SKILL.md.tmpl assertions in the same suite pin the exact #1671 regression class directly; this regex is a backstop, not a flow analyzer. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(timeline): pass read filters as data * feat(next-version): support monorepo VERSION paths via --version-path + .gstack/version-path The workspace-aware ship queue hardcoded the VERSION file at the repo root. In monorepos where versioning is subproject-scoped (one app inside a larger repo), every PR's VERSION lookup 404s, the queue silently empties, and parallel /ship sessions all bump from "current main + 1" — producing a cascade of slot collisions. Repro: tinas-second-brain repo. Root VERSION is absent; the real VERSION lives at "Tinas Second Brain/health-tracker/VERSION". In one day, four sequential collisions: 0.4.0.1 -> 0.5.0.0 -> 0.5.0.1 -> 0.5.0.2 -> 0.5.0.3. Fix: add a --version-path flag and a repo-local .gstack/version-path config file. Resolution priority: CLI flag > .gstack/version-path > "VERSION". The resolved path threads through all four call sites — git show origin/<base>:<path>, the GitHub Contents API, the GitLab files API, and the local sibling-worktree scan — and shows up in the JSON output as version_path so /ship and operators can see what got picked. The previous warning "could not fetch VERSION (fork or private)" was misleading whenever the real cause was wrong path. The new wording names the path that 404'd and hints at the two knobs. Backward-compatible: no flag, no config, no change in behavior. Tests: 6 unit tests for resolveVersionPath (priority, parsing, blank / missing / empty edge cases) + a second integration smoke that drives --version-path end-to-end and asserts it surfaces in JSON output. * fix(investigate): support standalone freeze hook path * fix(browse): clarify localhost bind failures * fix(migration): defer v1.40.0.0 done-marker until every repair succeeds (#1581) The v1.40.0.0 migration unconditionally `touch`ed its done-marker, even when the jq-gated `.brain-privacy-map.json` patch was skipped because jq was missing on the user's machine. On subsequent runs, the script short-circuited on the marker so the privacy-map repair never landed. Federation sync then silently dropped `/plan-eng-review` test plans. Track every failure mode via a single `incomplete` flag: jq missing, malformed JSON, jq mutation failure, tempfile creation failure, `mv` failure, allowlist append failure, gitattributes append failure. The marker is written only when `incomplete=0`, so the migration runner retries on the next /gstack-upgrade once the prerequisites are met. * test(migration): unit tests for v1.40.0.0 deferred done-marker fix (#1581) 8 cases pinning the fix: - Case 1 (happy path): jq present, fresh privacy-map → all three files patched, marker written. - Case 2 (regression for #1581): jq missing, privacy-map present → marker must NOT be written. Fails against the buggy script, passes against the fix. - Case 3 (recovery): jq missing, then jq restored → patch lands on second run. - Case 4 (idempotency): privacy-map already has correct entry → no mutation, marker written. - Case 5 (fresh-init): privacy-map file absent → allowlist + gitattrs patched, marker written. - Case 6 (malformed JSON): broken privacy-map JSON → no marker, no mutation. - Case 7 (jq mutation failure): fake jq returning 1 → no marker, tempfile cleaned up. - Case 8 (allowlist append failure): read-only allowlist → no marker. Tests use spawnSync('bash', [MIGRATION], …) with isolated tmpHomes. "jq missing" sets PATH to a curated dir of symlinks to standard utils, omitting jq; "jq mutation fails" uses an `exit 1` shim. Avoids blanket-clearing PATH (which would hide bash/grep/etc). * fix(brain-sync): make artifact sync work on Windows (discover-new + drain) Automatic artifact sync was fully non-functional on Windows (Git Bash): --discover-new enqueued nothing and the --once drain staged nothing, so artifacts_sync_mode looked active but no artifacts ever reached the repo. Three independent Windows-only causes in bin/gstack-brain-sync: 1. discover-new matched os.path.relpath (backslash separators on Windows) against the forward-slash allowlist globs, so no nested file ever matched. Normalized the relpath to "/". 2. discover-new enqueued via subprocess.run([gstack-brain-enqueue, rel]), but Windows Python cannot exec a bash-shebang script, so nothing was enqueued even once matched. Now appends to the queue in-process. 3. compute_paths_to_stage ends in print(p); Windows Python emits CRLF, the bash `read -r` keeps the trailing CR, and `git add -- "path<CR>"` matches nothing under `2>/dev/null || true`. Now strips the CR before staging. The in-process enqueue mirrors gstack-brain-enqueue's contract: one atomic O_APPEND write per record (each line < PIPE_BUF) so a parallel writer-shim append can't interleave mid-record, and the discover cursor advances only after the write succeeds, so a failed write retries instead of silently recording the file as synced. Skip-list entries are separator-normalized on both the discover and drain (compute_paths_to_stage) sides, so a backslash .brain-skip.txt entry can't be honored at discovery yet bypassed at commit. Adds test/brain-sync-windows-paths.test.ts (static invariants -- behavioral spawn tests cannot run on the Windows lane, since Node/Bun cannot exec the bin/ shebang scripts there) and wires it into windows-free-tests.yml. Verified red->green and end-to-end on Windows 11 / Git Bash; macOS/Linux behavior unchanged (os.sep is already "/", no CRLF, compute path logic unchanged besides the shared skip normalization). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: detect bun.lock (Bun v1.2+ text lockfile) in diff-scope CONFIG gstack-diff-scope only matched the legacy binary lockfile `bun.lockb` but not the newer text-based `bun.lock` introduced in Bun v1.2+. Projects using current Bun versions were silently missing the SCOPE_CONFIG signal when only the lockfile changed. 🤖 Generated with [Qoder][https://qoder.com] * fix(ios-qa): resolve CoreDevice tunnel via devicectl + keep tunnel alive The daemon's tunnel bootstrap used `dns.resolve6` to look up `<device>.coredevice.local`, which fails with ESERVFAIL on macOS 26.x (Darwin 25.x) because Node's resolve6 path goes through libresolv and does NOT consult mDNSResponder. `dns.lookup` (getaddrinfo) does. Even when resolution works, CoreDevice in Xcode 26 only holds the USB tunnel up while a devicectl command is in-flight, so the IPv6 ULA becomes unroutable within ~10-15s of idle and subsequent proxy requests time out. Two-part fix: 1. Resolution order is now (a) `xcrun devicectl device info details --json-output` to read `result.connectionProperties.tunnelIPAddress` directly, (b) mDNS via `dns.lookup`, (c) legacy `dns.resolve6` as a last-ditch fallback. 2. After a successful bootstrap the daemon spawns a periodic `devicectl device info details` (~5s) to keep the tunnel session alive. Cleaned up on SIGINT/SIGTERM/exit. Adds tests for `getDeviceTunnelIPv6FromDevicectl`, the `resolveTunnelIPv6` fallback chain, and `startTunnelKeepalive`. Existing bootstrap tests updated to include the new `device info details` spawn step. Tested against: iPhone 12 Pro on iOS 26.x via Mac Mini M-series running macOS Sequoia 15.x / Darwin 25.3.0. * chore(release): v1.44.1.0 — 9-PR community fix wave (post-windhoek paper-cut) Bump VERSION + CHANGELOG entry. Wave covers /office-hours session counter, iOS QA macOS 26 tunnels, Windows brain-sync, browse server bind diagnostics, monorepo VERSION layouts, /investigate freeze hook on standalone installs, gstack-timeline-read quote injection, v1.40.0.0 migration on jq-less machines, bun.lock detection. 9 community PRs: #1676 #1635 #1627 #1648 #1664 #1589 #1672 #1649 #1673 9 contributors credited: @pryow @jbetala7 @cfeddersen @Gujiassh @spacegeologist @stedfn @daveowenatl @hiSandog @sternryan 4 issues closed: #1671 #1677 #1634 #1647 #1581 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Rook <rook@robomovers.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Jayesh Betala <jayesh.betala7@gmail.com> Co-authored-by: Christoph <astaran@herr-der-ringe-film.de> Co-authored-by: gujishh <baiaoshh@163.com> Co-authored-by: zhengzuo0-ai <zheng.zuo0@gmail.com> Co-authored-by: Stefan Neamtu <stefan.neamtu@nearone.org> Co-authored-by: Dave Owen <daveowen66@gmail.com> Co-authored-by: 陈家名 <chenjiaming@kezaihui.com> Co-authored-by: Ryan Stern <206953196+sternryan@users.noreply.github.com>
260 lines
11 KiB
Cheetah
260 lines
11 KiB
Cheetah
---
|
|
name: investigate
|
|
preamble-tier: 2
|
|
version: 1.0.0
|
|
description: |
|
|
Systematic debugging with root cause investigation. Four phases: investigate,
|
|
analyze, hypothesize, implement. Iron Law: no fixes without root cause.
|
|
Use when asked to "debug this", "fix this bug", "why is this broken",
|
|
"investigate this error", or "root cause analysis".
|
|
Proactively invoke this skill (do NOT debug directly) when the user reports
|
|
errors, 500 errors, stack traces, unexpected behavior, "it was working
|
|
yesterday", or is troubleshooting why something stopped working. (gstack)
|
|
allowed-tools:
|
|
- Bash
|
|
- Read
|
|
- Write
|
|
- Edit
|
|
- Grep
|
|
- Glob
|
|
- AskUserQuestion
|
|
- WebSearch
|
|
triggers:
|
|
- debug this
|
|
- fix this bug
|
|
- why is this broken
|
|
- root cause analysis
|
|
- investigate this error
|
|
hooks:
|
|
PreToolUse:
|
|
- matcher: "Edit"
|
|
hooks:
|
|
- type: command
|
|
command: 'bash -c ''S="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"; [ -x "$S" ] || S="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"; [ -x "$S" ] && bash "$S" || exit 0'''
|
|
statusMessage: "Checking debug scope boundary..."
|
|
- matcher: "Write"
|
|
hooks:
|
|
- type: command
|
|
command: 'bash -c ''S="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"; [ -x "$S" ] || S="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"; [ -x "$S" ] && bash "$S" || exit 0'''
|
|
statusMessage: "Checking debug scope boundary..."
|
|
gbrain:
|
|
schema: 1
|
|
context_queries:
|
|
- id: prior-investigations
|
|
kind: list
|
|
filter:
|
|
type: timeline
|
|
tags_contains: "repo:{repo_slug}"
|
|
content_contains: "investigate"
|
|
sort: updated_at_desc
|
|
limit: 5
|
|
render_as: "## Prior investigations in this repo"
|
|
- id: project-learnings
|
|
kind: filesystem
|
|
glob: "~/.gstack/projects/{repo_slug}/learnings.jsonl"
|
|
tail: 10
|
|
render_as: "## Recent learnings (patterns + pitfalls)"
|
|
- id: recent-eureka
|
|
kind: filesystem
|
|
glob: "~/.gstack/analytics/eureka.jsonl"
|
|
tail: 5
|
|
render_as: "## Recent eureka moments (cross-project)"
|
|
---
|
|
|
|
{{PREAMBLE}}
|
|
|
|
# Systematic Debugging
|
|
|
|
## Iron Law
|
|
|
|
**NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST.**
|
|
|
|
Fixing symptoms creates whack-a-mole debugging. Every fix that doesn't address root cause makes the next bug harder to find. Find the root cause, then fix it.
|
|
|
|
---
|
|
|
|
{{GBRAIN_CONTEXT_LOAD}}
|
|
|
|
## Phase 1: Root Cause Investigation
|
|
|
|
Gather context before forming any hypothesis.
|
|
|
|
1. **Collect symptoms:** Read the error messages, stack traces, and reproduction steps. If the user hasn't provided enough context, ask ONE question at a time via AskUserQuestion.
|
|
|
|
2. **Read the code:** Trace the code path from the symptom back to potential causes. Use Grep to find all references, Read to understand the logic.
|
|
|
|
3. **Check recent changes:**
|
|
```bash
|
|
git log --oneline -20 -- <affected-files>
|
|
```
|
|
Was this working before? What changed? A regression means the root cause is in the diff.
|
|
|
|
4. **Reproduce:** Can you trigger the bug deterministically? If not, gather more evidence before proceeding.
|
|
|
|
5. **Check investigation history:** Search prior learnings for investigations on the same files. Recurring bugs in the same area are an architectural smell. If prior investigations exist, note patterns and check if the root cause was structural.
|
|
|
|
{{LEARNINGS_SEARCH:query=debug investigation root cause hypothesis bug fix}}
|
|
|
|
Output: **"Root cause hypothesis: ..."** — a specific, testable claim about what is wrong and why.
|
|
|
|
### Refresh learnings for the hypothesis you just named
|
|
|
|
The top-of-skill learnings pull above is keyed to "debug investigation" broadly. Now that you have a specific hypothesis, re-pull learnings keyed to that hypothesis so prior fixes for the same problem-shape surface.
|
|
|
|
Pick ONE keyword from the hypothesis. The keyword should be a noun: the failing component name, the basename of the file you suspect (without extension), or the bug noun. The keyword MUST be alphanumeric or hyphen only — no quotes, slashes, dots, colons, or whitespace. If your candidate has any of those, simplify to just the alphanumeric stem.
|
|
|
|
Worked examples (investigate-specific): good keywords are `auth-cookie`, `session-expiry`, `redirect-loop`. Bad: `auth.ts:47`, `fix the auth bug`, `<hypothesis-keyword>`.
|
|
|
|
```bash
|
|
~/.claude/skills/gstack/bin/gstack-learnings-search --query "<your-keyword>" --limit 5 2>/dev/null || true
|
|
```
|
|
|
|
If any learnings come back, name which one applies to your investigation in one sentence. If none come back, continue without reference — the absence of a matching prior learning is itself useful information.
|
|
|
|
---
|
|
|
|
## Scope Lock
|
|
|
|
After forming your root cause hypothesis, lock edits to the affected module to prevent scope creep.
|
|
|
|
```bash
|
|
_FREEZE_SCRIPT="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"
|
|
[ -x "$_FREEZE_SCRIPT" ] || _FREEZE_SCRIPT="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"
|
|
[ -x "$_FREEZE_SCRIPT" ] && echo "FREEZE_AVAILABLE" || echo "FREEZE_UNAVAILABLE"
|
|
```
|
|
|
|
**If FREEZE_AVAILABLE:** Identify the narrowest directory containing the affected files. Write it to the freeze state file:
|
|
|
|
```bash
|
|
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"
|
|
STATE_DIR="$GSTACK_STATE_ROOT"
|
|
mkdir -p "$STATE_DIR"
|
|
echo "<detected-directory>/" > "$STATE_DIR/freeze-dir.txt"
|
|
echo "Debug scope locked to: <detected-directory>/"
|
|
```
|
|
|
|
Substitute `<detected-directory>` with the actual directory path (e.g., `src/auth/`). Tell the user: "Edits restricted to `<dir>/` for this debug session. This prevents changes to unrelated code. Run `/unfreeze` to remove the restriction."
|
|
|
|
If the bug spans the entire repo or the scope is genuinely unclear, skip the lock and note why.
|
|
|
|
**If FREEZE_UNAVAILABLE:** Skip scope lock. Edits are unrestricted.
|
|
|
|
---
|
|
|
|
## Phase 2: Pattern Analysis
|
|
|
|
Check if this bug matches a known pattern:
|
|
|
|
| Pattern | Signature | Where to look |
|
|
|---------|-----------|---------------|
|
|
| Race condition | Intermittent, timing-dependent | Concurrent access to shared state |
|
|
| Nil/null propagation | NoMethodError, TypeError | Missing guards on optional values |
|
|
| State corruption | Inconsistent data, partial updates | Transactions, callbacks, hooks |
|
|
| Integration failure | Timeout, unexpected response | External API calls, service boundaries |
|
|
| Configuration drift | Works locally, fails in staging/prod | Env vars, feature flags, DB state |
|
|
| Stale cache | Shows old data, fixes on cache clear | Redis, CDN, browser cache, Turbo |
|
|
|
|
Also check:
|
|
- `TODOS.md` for related known issues
|
|
- `git log` for prior fixes in the same area — **recurring bugs in the same files are an architectural smell**, not a coincidence
|
|
|
|
**External pattern search:** If the bug doesn't match a known pattern above, WebSearch for:
|
|
- "{framework} {generic error type}" — **sanitize first:** strip hostnames, IPs, file paths, SQL, customer data. Search the error category, not the raw message.
|
|
- "{library} {component} known issues"
|
|
|
|
If WebSearch is unavailable, skip this search and proceed with hypothesis testing. If a documented solution or known dependency bug surfaces, present it as a candidate hypothesis in Phase 3.
|
|
|
|
---
|
|
|
|
## Phase 3: Hypothesis Testing
|
|
|
|
Before writing ANY fix, verify your hypothesis.
|
|
|
|
1. **Confirm the hypothesis:** Add a temporary log statement, assertion, or debug output at the suspected root cause. Run the reproduction. Does the evidence match?
|
|
|
|
2. **If the hypothesis is wrong:** Before forming the next hypothesis, consider searching for the error. **Sanitize first** — strip hostnames, IPs, file paths, SQL fragments, customer identifiers, and any internal/proprietary data from the error message. Search only the generic error type and framework context: "{component} {sanitized error type} {framework version}". If the error message is too specific to sanitize safely, skip the search. If WebSearch is unavailable, skip and proceed. Then return to Phase 1. Gather more evidence. Do not guess.
|
|
|
|
3. **3-strike rule:** If 3 hypotheses fail, **STOP**. Use AskUserQuestion:
|
|
```
|
|
3 hypotheses tested, none match. This may be an architectural issue
|
|
rather than a simple bug.
|
|
|
|
A) Continue investigating — I have a new hypothesis: [describe]
|
|
B) Escalate for human review — this needs someone who knows the system
|
|
C) Add logging and wait — instrument the area and catch it next time
|
|
```
|
|
|
|
**Red flags** — if you see any of these, slow down:
|
|
- "Quick fix for now" — there is no "for now." Fix it right or escalate.
|
|
- Proposing a fix before tracing data flow — you're guessing.
|
|
- Each fix reveals a new problem elsewhere — wrong layer, not wrong code.
|
|
|
|
---
|
|
|
|
## Phase 4: Implementation
|
|
|
|
Once root cause is confirmed:
|
|
|
|
1. **Fix the root cause, not the symptom.** The smallest change that eliminates the actual problem.
|
|
|
|
2. **Minimal diff:** Fewest files touched, fewest lines changed. Resist the urge to refactor adjacent code.
|
|
|
|
3. **Write a regression test** that:
|
|
- **Fails** without the fix (proves the test is meaningful)
|
|
- **Passes** with the fix (proves the fix works)
|
|
|
|
4. **Run the full test suite.** Paste the output. No regressions allowed.
|
|
|
|
5. **If the fix touches >5 files:** Use AskUserQuestion to flag the blast radius:
|
|
```
|
|
This fix touches N files. That's a large blast radius for a bug fix.
|
|
A) Proceed — the root cause genuinely spans these files
|
|
B) Split — fix the critical path now, defer the rest
|
|
C) Rethink — maybe there's a more targeted approach
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 5: Verification & Report
|
|
|
|
**Fresh verification:** Reproduce the original bug scenario and confirm it's fixed. This is not optional.
|
|
|
|
Run the test suite and paste the output.
|
|
|
|
Output a structured debug report:
|
|
```
|
|
DEBUG REPORT
|
|
════════════════════════════════════════
|
|
Symptom: [what the user observed]
|
|
Root cause: [what was actually wrong]
|
|
Fix: [what was changed, with file:line references]
|
|
Evidence: [test output, reproduction attempt showing fix works]
|
|
Regression test: [file:line of the new test]
|
|
Related: [TODOS.md items, prior bugs in same area, architectural notes]
|
|
Status: DONE | DONE_WITH_CONCERNS | BLOCKED
|
|
════════════════════════════════════════
|
|
```
|
|
|
|
Log the investigation as a learning for future sessions. Use `type: "investigation"` and include the affected files so future investigations on the same area can find this:
|
|
|
|
```bash
|
|
~/.claude/skills/gstack/bin/gstack-learnings-log '{"skill":"investigate","type":"investigation","key":"ROOT_CAUSE_KEY","insight":"ROOT_CAUSE_SUMMARY","confidence":9,"source":"observed","files":["affected/file1.ts","affected/file2.ts"]}'
|
|
```
|
|
|
|
{{LEARNINGS_LOG}}
|
|
|
|
{{GBRAIN_SAVE_RESULTS}}
|
|
|
|
---
|
|
|
|
## Important Rules
|
|
|
|
- **3+ failed fix attempts → STOP and question the architecture.** Wrong architecture, not failed hypothesis.
|
|
- **Never apply a fix you cannot verify.** If you can't reproduce and confirm, don't ship it.
|
|
- **Never say "this should fix it."** Verify and prove it. Run the tests.
|
|
- **If fix touches >5 files → AskUserQuestion** about blast radius before proceeding.
|
|
- **Completion status:**
|
|
- DONE — root cause found, fix applied, regression test written, all tests pass
|
|
- DONE_WITH_CONCERNS — fixed but cannot fully verify (e.g., intermittent bug, requires staging)
|
|
- BLOCKED — root cause unclear after investigation, escalated
|