mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 14:06:50 +01:00
* fix(timeline): reject a missing or malformed --limit with exit 2 (#1723) gstack-timeline-read parsed --limit with parseInt and fell back to 20, so '--limit 1abc' became 1, 'nope', 0 and -5 silently became the default, and a bare --limit crashed on an unbound $2. Validate the argument as a positive integer before the no-timeline early exit and exit 2 with an example. No flag still means 20. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(skill-start): echo SESSIONS, the live session-marker count (#1651) The sessions directory is touched and expired at skill start, but the count was never printed, so CLAUDE.md rules that read the preamble's session count had nothing to read. Echo SESSIONS: N (markers fresher than 120 minutes, this session included). Idea from @0xDevNinja's PR #1747, redone in the consolidated skill-start script. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(office-hours): pick the closing tier from the profile read before this session is logged (#2801) Phase 4.5 appended this session to the builder profile and Phase 6 then read the profile to choose its closing, so every run saw itself as history: a first-time user got welcome_back and was asked how the assignment issued minutes earlier went. Phase 4.5 now reads the profile before --log-session and records the prior tier, count and last-session fields in the transcript; Phase 6 uses those values (this session is prior + 1) and re-reads only the running totals. A failed read proceeds as a first session and says so. Tier headers now name the session numbers each tier actually covers. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(office-hours): print the full design doc inline before asking for approval (#879) Tool output collapses in Claude Code, so users were asked to approve a doc they could not see. Port of @RyanAlberts's PR #1116 into the carved design-and-handoff section. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(office-hours): report success only when this run wrote its design doc (#1049) Before telemetry, check the design doc this run wrote, by its exact path under the state root, never the newest file in the folder. Success needs a non-empty doc there; otherwise the outcome is abort or error with failed step design_doc. Rewrite of @walton-chris's PR #1049, which matched the newest global session marker and hardcoded ~/.gstack. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(plan reviews): skip the /office-hours prerequisite offer in spawned and headless sessions (#1958) The BENEFITS_FROM offer had no recommended option, so a spawned run had nothing defined to auto-pick and a headless run could only block. Skip it there and continue with the standard review. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: regenerate SKILL.md files Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: office-hours tier order, failed-read fallback, inline doc, design-doc check and offer skip Rendered-block tests run the Phase 4.5 read and log on a temp state root (first session reads introduction, failed read is marked) and the pre-telemetry check against this run's doc, an empty doc, another session's doc and a path outside the state root. Parity caps take the measured growth. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore(release): v1.91.37.0 changelog and TODOS Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(browse): run watchdog test servers under a private HOME The headed-mode case starts a real server with BROWSE_HEADED=1, and its headed launch writes ~/.gstack/.auth.json for the extension. With the caller's HOME, a slow kill let that write reach the real home and failed the free runner's home-containment check (free-suite shard 5 on #3075). Give each spawned server a private HOME and GSTACK_HOME, keeping Playwright's browser cache path. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(evals): name the office-hours-auto-mode case so the PR lane can select it The PR lane runs each short probe with a Bun name pattern ending in its case id. The office-hours AskUserQuestion-blocked smoke had a descriptive test name and no CASE_TEST_NAMES label, so whenever office-hours changed its shard matched 0 tests and failed in 0 s (E2E Evals slice 6 on #3075). Name the test after its id, keep the description on the describe, and check that every literal-named PR probe has a test the pattern selects. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(office-hours): skip a last-session greeting clause whose value is empty A profile backfilled from the timeline can carry a welcome_back or regular tier with empty LAST_ASSIGNMENT and LAST_PROJECT. The welcome_back and regular greetings now drop the "last time" clause and its question when the value it names is empty, and say only "Welcome back." Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
113 lines
3.7 KiB
Bash
Executable File
113 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# gstack-timeline-read — read and format project timeline
|
|
# Usage: gstack-timeline-read [--since "7 days ago"] [--limit N] [--branch NAME]
|
|
#
|
|
# Session timeline: local-only, never sent anywhere.
|
|
# Reads ~/.gstack/projects/$SLUG/timeline.jsonl, filters, formats.
|
|
# Exit 0 silently if no timeline file exists or nothing matches. Missing bun
|
|
# exits 127 and a failed read exits non-zero with its stderr (#2790). A missing
|
|
# or non-positive-integer --limit exits 2 before any read (#1723).
|
|
set -euo pipefail
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
eval "$("$SCRIPT_DIR/gstack-slug" 2>/dev/null)"
|
|
. "$(dirname "$0")/gstack-state-root.sh" 2>/dev/null || { echo "$0: cannot resolve the gstack state root: $(dirname "$0")/gstack-state-root.sh is missing. fix: reinstall with ./setup or /gstack-upgrade (docs/state-root.md)" >&2; exit 1; }
|
|
gstack_state_root_select
|
|
GSTACK_HOME="$_gstack_sr_root"
|
|
|
|
SINCE=""
|
|
LIMIT=20
|
|
BRANCH=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--since) SINCE="$2"; shift 2 ;;
|
|
--limit)
|
|
if [[ $# -lt 2 ]]; then
|
|
echo "gstack-timeline-read: --limit must be a positive integer (got no value); example: --limit 20" >&2
|
|
exit 2
|
|
fi
|
|
if [[ ! "$2" =~ ^[1-9][0-9]*$ ]]; then
|
|
echo "gstack-timeline-read: --limit must be a positive integer (got '$2'); example: --limit 20" >&2
|
|
exit 2
|
|
fi
|
|
LIMIT="$2"; shift 2 ;;
|
|
--branch) BRANCH="$2"; shift 2 ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
|
|
TIMELINE_FILE="$GSTACK_HOME/projects/$SLUG/timeline.jsonl"
|
|
|
|
if [ ! -f "$TIMELINE_FILE" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
command -v bun >/dev/null 2>&1 || {
|
|
echo "gstack-timeline-read: bun not found on PATH, so the timeline could not be read. Fix: install Bun (https://bun.sh), then re-run ./setup." >&2
|
|
exit 127
|
|
}
|
|
|
|
cat "$TIMELINE_FILE" | GSTACK_TIMELINE_SINCE="$SINCE" GSTACK_TIMELINE_BRANCH="$BRANCH" GSTACK_TIMELINE_LIMIT="$LIMIT" bun -e "
|
|
const lines = (await Bun.stdin.text()).trim().split('\n').filter(Boolean);
|
|
const since = process.env.GSTACK_TIMELINE_SINCE || '';
|
|
const branch = process.env.GSTACK_TIMELINE_BRANCH || '';
|
|
const limit = Number(process.env.GSTACK_TIMELINE_LIMIT);
|
|
|
|
let sinceMs = 0;
|
|
if (since) {
|
|
// Parse relative time like '7 days ago'
|
|
const match = since.match(/(\d+)\s*(day|hour|minute|week|month)s?\s*ago/i);
|
|
if (match) {
|
|
const n = parseInt(match[1]);
|
|
const unit = match[2].toLowerCase();
|
|
const ms = { minute: 60000, hour: 3600000, day: 86400000, week: 604800000, month: 2592000000 };
|
|
sinceMs = Date.now() - n * (ms[unit] || 86400000);
|
|
}
|
|
}
|
|
|
|
const entries = [];
|
|
for (const line of lines) {
|
|
try {
|
|
const e = JSON.parse(line);
|
|
if (sinceMs && new Date(e.ts).getTime() < sinceMs) continue;
|
|
if (branch && e.branch !== branch) continue;
|
|
entries.push(e);
|
|
} catch {}
|
|
}
|
|
|
|
if (entries.length === 0) process.exit(0);
|
|
|
|
// Take last N entries
|
|
const recent = entries.slice(-limit);
|
|
|
|
// Skill counts (completed events only)
|
|
const counts = {};
|
|
const branches = new Set();
|
|
for (const e of entries) {
|
|
if (e.event === 'completed') {
|
|
counts[e.skill] = (counts[e.skill] || 0) + 1;
|
|
}
|
|
if (e.branch) branches.add(e.branch);
|
|
}
|
|
|
|
// Output summary
|
|
const countStr = Object.entries(counts)
|
|
.sort((a, b) => b[1] - a[1])
|
|
.map(([s, n]) => n + ' /' + s)
|
|
.join(', ');
|
|
|
|
if (countStr) {
|
|
console.log('TIMELINE: ' + countStr + ' across ' + branches.size + ' branch' + (branches.size !== 1 ? 'es' : ''));
|
|
}
|
|
|
|
// Output recent events
|
|
console.log('');
|
|
console.log('## Recent Events');
|
|
for (const e of recent) {
|
|
const ts = (e.ts || '').replace('T', ' ').replace(/\.\d+Z$/, 'Z');
|
|
const dur = e.duration_s ? ' (' + e.duration_s + 's)' : '';
|
|
const outcome = e.outcome ? ' [' + e.outcome + ']' : '';
|
|
console.log('- ' + ts + ' /' + e.skill + ' ' + e.event + outcome + dur + (e.branch ? ' on ' + e.branch : ''));
|
|
}
|
|
"
|