Files
gstack/bin/gstack-timeline-read
garrytan cd7485b0b4 W1: guarded migration of every executable state-root site; uninstall deletes only ~/.gstack
Bins, careful/freeze hooks, setup, upgrade migrations, browse/src, design,
ios-qa daemon, lib and scripts resolve the state root through
bin/gstack-state-root.sh (bash) or lib/state-root.ts (TS). Bins source the
twin and stop with a reinstall message when it is missing; hooks source it
and never spawn gstack-paths. browse/src/config.ts and lib/cso/state.ts
delegate to resolveStateRoot. Analytics writers and readers move together
so the usage log stays one file. gstack-uninstall deletes state only at
~/.gstack, refuses (exit 2) when it resolves to /, $HOME or an ancestor,
the checkout or the git root, and leaves any other resolved root in place
with the removal command. Fixtures that copy single bins now copy the twin.
2026-09-30 06:38:50 +00:00

99 lines
3.2 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.
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) 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
cat "$TIMELINE_FILE" 2>/dev/null | 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 limitRaw = process.env.GSTACK_TIMELINE_LIMIT || '20';
const parsedLimit = Number.parseInt(limitRaw, 10);
const limit = Number.isSafeInteger(parsedLimit) && parsedLimit > 0 ? parsedLimit : 20;
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 : ''));
}
" 2>/dev/null || exit 0