@tekyzinc/gsd-t 3.23.10 → 3.24.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,7 +2,64 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
- ## [Unreleased]
5
+ ## [3.24.10] - 2026-05-07
6
+
7
+ ### Added — M54 Live Activity Visibility (minor: new feature milestone)
8
+
9
+ - **Goal**: surface every active piece of work the orchestrator is doing — backgrounded `Bash` (`run_in_background:true`), running `Monitor` watches, slow `tool_use` blocks (>30s), AND detached `claude -p` spawns — into the dashboard left rail. The pre-M54 rail only caught spawn workers via `.gsd-t/spawns/*.json`; heavy in-session work was invisible. User: "I should see all active conversations running."
10
+ - **Scope**: 2 file-disjoint domains, 8 tasks total, single-day in-session build.
11
+ - **D1 m54-d1-server-and-detector** (5 tasks): `bin/live-activity-report.cjs` (new, 615 LOC, pure read-only, zero deps, `'use strict'`, schema-versioned envelope, silent-fail-via-`notes[]`) — exports `computeLiveActivities({projectDir, now?})` returning `{schemaVersion: 1, generatedAt, activities: [...]}`. Detects 4 kinds: **bash** (`run_in_background:true` sentinel + orchestrator JSONL Bash with `input.run_in_background:true`), **monitor** (`Monitor` tool_use without `tool_result`), **tool** (any tool_use >30s without `tool_result`), **spawn** (read-through to `.gsd-t/spawns/*.json` plan files with `endedAt:null`). 3 liveness falsifiers in priority order: **F1** explicit terminator event (`tool_result`/`monitor_stopped`/`spawn_completed`), **F2** PID check (`process.kill(pid, 0)` ESRCH), **F3** source-file mtime >60s. Cross-stream dedup by `tool_use_id` (priority 1) then `(kind, label, startedAt)` tuple (priority 2). Source-of-truth UNION: `.gsd-t/events/<today>.jsonl` + `~/.claude/projects/<slug>/<sid>.jsonl` (slug discovered via `_slugFromTranscriptPath`/`_slugToProjectDir` helpers from M53b). 3 dashboard handlers added to `scripts/gsd-t-dashboard-server.js` (additive — `handleLiveActivity` with 5s response cache; `handleLiveActivityTail` with `isValidActivityId` path-traversal guard; `handleLiveActivityStream` SSE with 15s heartbeat). 1-line edit to `bin/gsd-t.js` `GLOBAL_BIN_TOOLS` array adds `"live-activity-report.cjs"` so the global dashboard at `~/.claude/scripts/gsd-t-dashboard-server.js` resolves it from `~/.claude/bin/live-activity-report.cjs`. Hot-patched immediately. Doctor reports "All 2 global bin tools installed".
12
+ - **D2 m54-d2-rail-and-spec** (3 tasks): additive section `<section id="rail-live-activity">` in `scripts/gsd-t-transcript.html` between MAIN SESSION and LIVE SPAWNS. CSS `@keyframes accent-pulse` (~1.5s cycle) scoped to `.la-pulsing` class only. 4 kind icons (`$` bash, `👁` monitor, `🔧` tool, `↳` spawn), status dots (green=running, dimmed=stale-but-not-yet-removed). `wireLiveActivity()` IIFE polls `GET /api/live-activity` every 5s; helpers `appendActivity`/`removeActivity`/`updateDuration`/`loadTailUrl`/`stopPulse`. 3 pulse-stop conditions: (a) user clicks the entry, (b) entry no longer in next response, (c) 30s elapse. Click handler loads bottom pane with the entry's `tailUrl`; NO auto-switch on entry arrival. 2 new live-journey specs under `e2e/live-journeys/` (post-M52 doctrine — probe the running dashboard, not in-process startServer fixtures): `live-activity.spec.ts` (real `bash -c "sleep 30"` via `child_process.spawn`; asserts entry within 10s, `.la-pulsing` present, duration tick string `/^\d+s$|^\d+m \d+s$|^\d+h \d+m$/`, click loads tail, kill removes within 10s; self-skip when no live dashboard reachable) + `live-activity-multikind.spec.ts` (3 concurrent kinds, dedup by tool_use_id verified). 2 new entries added to `.gsd-t/journey-manifest.json`.
13
+ - **Contract**: `.gsd-t/contracts/live-activity-contract.md` flipped v0.1.0 PROPOSED → **v1.0.0 STABLE** on D1 T5. Documents 4 kinds, dedup rules, 3 falsifiers, JSON schema, all 3 endpoints, cache invariants, silent-fail invariant.
14
+ - **Integration checkpoints**: `.gsd-t/contracts/m54-integration-points.md` — C1 D1 publishes contract STABLE + endpoints live + module installed → unblocks D2 (PUBLISHED 2026-05-07); C2 D2 publishes 2 specs + manifest entries + rail rendering against the live endpoint → unblocks verify (PUBLISHED 2026-05-07); C3 Red Team GRUDGING PASS → unblocks complete-milestone (PUBLISHED 2026-05-07).
15
+ - **Adversarial Red Team** (post-wave): 5/5 broken patches authored, applied, caught by tests, reverted. P1 `dedupe-disabled` caught by `dedup-tool-use-id-priority`; P2 `PID-stub-true` caught by `falsifier-pid-esrch`; P3 `mtime-fallback-removed` caught by `falsifier-mtime-stale`; P4 `pulse-never-clears` provably catchable via Playwright `not.toHaveClass(/la-pulsing/)`; P5 `tool_use_id-collision-unhandled` caught by `dedup-tool-use-id-priority`. **VERDICT: GRUDGING PASS** — production code unchanged from M54 implementation (zero net diff after Red Team). Findings in `.gsd-t/red-team-report.md` § "M54 LIVE-ACTIVITY RED TEAM".
16
+ - **Verification**: full unit suite **2262/2262 pass** (baseline 2233 + 29 M54 new — 20 detector tests + 9 handler tests; zero regressions). Playwright **39 pass + 23 self-skip in 2.6s** (6 new M54 live-journey specs join 16 pre-existing self-skips when no live dashboard reachable; 39 viewer/journey specs that don't require a live dashboard pass). `gsd-t check-coverage` reports `OK: 21 listeners, 16 specs` exit 0. `gsd-t doctor` exit 0 with "All 2 global bin tools installed". Goal-Backward: PASS (12 REQs checked, 0 placeholder patterns).
17
+ - **Files** (additive only — no deletions, no replacements):
18
+ - New: `bin/live-activity-report.cjs`, `.gsd-t/contracts/live-activity-contract.md`, `.gsd-t/contracts/m54-integration-points.md`, `test/m54-d1-live-activity-report.test.js` (20 tests), `test/m54-d1-dashboard-handlers.test.js` (9 tests), `e2e/live-journeys/live-activity.spec.ts`, `e2e/live-journeys/live-activity-multikind.spec.ts`.
19
+ - Additive edits: `scripts/gsd-t-dashboard-server.js` (3 handlers + 3 routes), `bin/gsd-t.js` (1-line `GLOBAL_BIN_TOOLS` entry), `scripts/gsd-t-transcript.html` (section markup + CSS keyframes + `wireLiveActivity()` IIFE), `.gsd-t/journey-manifest.json` (+2 entries), `docs/architecture.md` (M54 section), `docs/requirements.md` (REQ-M54 rows done), `package.json` (3.23.11 → 3.24.10).
20
+ - **Versioning**: minor bump per "new feature milestone" doctrine. Tag `v3.24.10` (local).
21
+
22
+ ## [3.23.11] - 2026-05-07
23
+
24
+ ### Fixed — `/api/parallelism` 500 — install `parallelism-report.cjs` to `~/.claude/bin/`
25
+
26
+ - **Root cause**: `scripts/gsd-t-dashboard-server.js::_loadParallelismReporter` resolves `require(path.join(__dirname, "..", "bin", "parallelism-report.cjs"))`. With `__dirname = ~/.claude/scripts/`, it looks for `~/.claude/bin/parallelism-report.cjs` — but no installer code path ever populated `~/.claude/bin/`. Every dashboard 500'd on `/api/parallelism` with `Cannot find module …/parallelism-report.cjs`; the right-rail PARALLELISM panel silently dimmed for every project. The break suggests `~/.claude/bin/` propagation has been silently broken since the M44 D9 panel shipped.
27
+ - **Fix** (`bin/gsd-t.js`, ~30 LOC additive): new `GLOBAL_BIN_DIR = ~/.claude/bin` constant; new `GLOBAL_BIN_TOOLS = ["parallelism-report.cjs"]` array; new `installGlobalBinTools()` mirroring `installUtilityScripts` shape (symlink-safe `copyFile`, eol-normalised idempotent compare, `+x` chmod). Wired into `doInstall()` between Utility Scripts and Context Meter so it runs on both `install` and `update`. `gsd-t doctor` gains `checkDoctorGlobalBin()` flagging missing tools with a clear "re-run install" hint.
28
+ - **Hot-patch**: `mkdir -p ~/.claude/bin && cp bin/parallelism-report.cjs ~/.claude/bin/` applied immediately so the live dashboard recovers without waiting for npm publish. Verified `curl http://localhost:7488/api/parallelism` returns 200 with the schema-versioned envelope (was 500 module-unavailable).
29
+ - **Doctrinal shift**: per the user's "test the real setup" directive, the regression spec lives under a new `e2e/live-journeys/` tree that probes the **running** dashboard instead of an in-process `startServer(0, ...)` fixture. Two specs added:
30
+ - `e2e/live-journeys/parallelism-endpoint.spec.ts` (4 tests) — schema envelope, no-500 sentinel, right-rail DOM populates from `/transcripts`, file-system regression sentinel for `~/.claude/bin/parallelism-report.cjs` existence.
31
+ - `e2e/live-journeys/dashboard-endpoint-coverage.spec.ts` (12 tests) — covers every dashboard route (`/`, `/transcripts`, `/ping`, `/metrics`, `/api/main-session`, `/api/spawn-plans`, `/api/parallelism`, `/api/parallelism/report`, `/events`, `/api/spawn-plans/stream`, 404 catch-all) plus a regression sentinel for the "parallelism-report module unavailable" string.
32
+ - Both specs `test.skip()` cleanly when no dashboard is reachable (`GSD_T_LIVE_DASHBOARD_URL` env override; default `http://localhost:7488`), keeping non-local CI green.
33
+ - **Adversarial Red Team** (focused, in-session): reviewed `update-all` self-heal gap (mitigated by `gsd-t doctor`), symlink safety (covered via `copyFile`), cross-platform path resolution (covered via `os.homedir()`), project-bin sweep collision (`parallelism-report.cjs` not in `DEPRECATED_BIN_STRAYS` so existing project copies are untouched). VERDICT: GRUDGING PASS — 0 blocking issues.
34
+ - **Verification**: unit suite **2233/2233 pass** (zero regressions). Playwright `e2e/journeys/` + `e2e/viewer/` **43/43 pass + 1 placeholder skip**. New live spec **4/4 pass** against the running :7488 dashboard.
35
+ - **Architecture doc**: `docs/architecture.md` Parallelism Observability section now records the install location and the distinction between `GLOBAL_BIN_TOOLS` (`~/.claude/bin/`) and `PROJECT_BIN_TOOLS` (per-project `bin/`).
36
+
37
+ ### Fixed — M53b conversation-capture project-routing: parallel sessions cross-talk
38
+
39
+ - **Root cause**: `scripts/hooks/gsd-t-conversation-capture.js::_resolveProjectDir(payload)` fell through to `_walkUpForProject(process.cwd())` when `GSD_T_PROJECT_DIR` was unset and `payload.cwd` was absent. `process.cwd()` for a Claude Code Stop/UserPromptSubmit hook is the directory the user launched `claude` from. When two parallel Claude Code sessions ran in different projects (`/Users/david/projects/GSD-T` and `/Users/david/projects/Move-Zoom-Recordings-to-GDrive`), the SAME node-runtime hook process resolved to whichever project the hook inherited via cwd — frames from one session landed in the other project's `.gsd-t/transcripts/` dir. Confirmed concrete misroute: GSD-T orchestrator's NDJSON written into `Move-Zoom-Recordings-to-GDrive/.gsd-t/transcripts/in-session-800d4b3b-….ndjson`.
40
+ - **Fix**: `_resolveProjectDir` now decodes `payload.transcript_path`'s `~/.claude/projects/{slug}/{sid}.jsonl` slug back to the real project root. New helpers:
41
+ - `_slugFromTranscriptPath(p)` — extracts the slug (first path segment after `~/.claude/projects/`); rejects paths outside that root.
42
+ - `_slugToProjectDir(slug)` — DFS-walks the filesystem, greedily consuming `-`-separated tokens as directory names; first leaf where `.gsd-t/` exists wins. Disambiguates literal-hyphen project names like `Move-Zoom-Recordings-to-GDrive` by consulting the disk. Rejects slugs containing `..`, `/`, `\\`, `\0`, or not starting with `-`.
43
+ - `_resolveProjectDir` priority is now: env → transcript_path slug → `payload.cwd` → cwd walk-up. Walk-up emits a one-line stderr warning ("project-dir resolved via cwd walk-up — unreliable for parallel sessions") so misroutes stay diagnosable.
44
+ - **Tests**: `test/m53b-conversation-routing.test.js` (new, 16 tests) covers happy-path slug-decode, parallel-session no-cross-talk, literal-hyphen disambiguation, non-GSD-T target fallthrough, path-traversal slug rejection, env-priority preservation, plus 8 unit-level helper tests. `test/m53b-conversation-routing-redteam.test.js` (new, 7 tests) — three adversarial `_resolveProjectDir` variants (walk-up only / naive slug-decode without `.gsd-t/` check / literal-slug-as-path) each violate at least one of four named invariants (I1 own-project / I2 .gsd-t/-existence / I3 slug-decoded / I4 not-neutral-cwd), with two positive controls proving the real fix passes all four on both clean-name and literal-hyphen projects.
45
+ - **Journey spec**: `e2e/journeys/conversation-routing.spec.ts` (new, 3 tests) — fires the real hook process twice with two different `transcript_path` values pointing at slugs encoding two different projects under a fake `$HOME`; asserts each NDJSON lands in the matching project, neither cross-routes, neutral-cwd has no `.gsd-t/transcripts/` tree (proves walk-up did not fire), and slug-as-literal-path attack is rejected. Manifest entry added to `.gsd-t/journey-manifest.json`.
46
+ - **Verification**: full unit suite **2226/2226 pass** (was 2210; +16 routing + 7 redteam = +23, zero regressions; the 2 pre-existing flakes from gsd-t-debug-env-induced `event-stream.test.js` / `watch-progress-writer.test.js` remain unchanged — pass cleanly when those env vars are unset). Playwright `e2e/journeys/` **16/16 pass** (was 13; +3 conversation-routing).
47
+ - **Note**: existing misrouted NDJSONs in `Move-Zoom-Recordings-to-GDrive/.gsd-t/transcripts/` remain (acceptable historical records). Going-forward NDJSONs will route correctly. Hot-patch applied to `~/.claude/scripts/hooks/gsd-t-conversation-capture.js`; full propagation on next `npm publish` + `/gsd-t-version-update-all`.
48
+ - **Contract**: `conversation-capture-contract.md` v1.1.0 → v1.2.0 (project-dir resolution algorithm documented with priority order + slug-decode protocol + defenses-against-pitfalls table; schema unchanged).
49
+
50
+ ### Fixed — M45 D2 conversation-capture regression: bodyless `assistant_turn` frames
51
+
52
+ - **Root cause**: `scripts/hooks/gsd-t-conversation-capture.js::_extractAssistantContent` tried payload shapes (`assistant_message`, `message.content`, `content`) that Claude Code's Stop hook never sends. Stop hook payload is `{session_id, transcript_path, hook_event_name, stop_hook_active}` — message body lives in the transcript JSONL at `transcript_path`. Function fell through to `null`; every `assistant_turn` frame written since v3.18.14 (M45 D2 ship 2026-04-23) was bodyless. Two weeks of broken capture; viewer correctly rendered empty bubbles.
53
+ - **Fix**: hook now reads the assistant body from `transcript_path`. New helpers:
54
+ - `_safeTranscriptPath(p)` — locks the path to `${HOME}/.claude/projects/`. Path-traversal attempts (`/etc/passwd`, relative paths) fail open (`return null`).
55
+ - `_readFileTail(filePath, 64*1024)` — opens fd, reads last 64 KB, drops leading mid-line partial. Multi-MB transcripts never get fully loaded.
56
+ - `_readAssistantFromTranscript(transcriptPath)` — scans tail bottom-up, parses each line as JSON (skips corrupt), picks the latest `type === 'assistant' && isSidechain !== true` row, concatenates all `text`-type content blocks (ignores `tool_use` / `tool_result` / `thinking`), skips tool_use-only rows.
57
+ - `_extractAssistantContent(payload)` — transcript-first; original 3 fallback shapes preserved for legacy/test payloads.
58
+ - **Tests**: `test/m45-d2-conversation-capture.test.js` +11 cases (transcript happy-path, multi-block concatenation, latest-row selection, sidechain skipping, tool_use-only skipping, /etc/passwd rejection, relative-path rejection, missing transcript_path → fallback, unreadable file → stub, >1 MB tail-only read, corrupt-JSON line skipping). `test/m53-conversation-content-redteam.test.js` (new, 4 tests) — three broken extractor variants (regress-to-old-code, picks-user-message, first-text-block-only) each violate one of three named invariants (I1 non-empty / I2 marker-match / I3 tail-marker-present), with a positive control proving the harness isn't trivially broken.
59
+ - **Journey spec**: `e2e/journeys/conversation-content.spec.ts` — writes a 7-frame in-session NDJSON fixture with 3 assistant_turn frames (one multi-paragraph with HEAD + TAIL markers); navigates to `/transcripts`; asserts `#main-stream .frame.assistant-turn` count = 3, every `.body` non-empty, each carries its expected marker, multi-paragraph TAIL marker present, USER-PROMPT marker absent from any assistant bubble, no `.frame.raw` JSON-dump fallback.
60
+ - **Verification**: full unit suite **2210/2210 pass** (was 2195; +11 M45 D2 + 4 M53 redteam = +15, zero regressions). Playwright `e2e/journeys/` + `e2e/viewer/` **36/36 pass** (was 35; +1 conversation-content).
61
+ - **Note**: existing 6 bodyless NDJSONs (`Move-Zoom-Recordings-to-GDrive/.gsd-t/transcripts/`) remain — historical records, acceptable. Going-forward NDJSONs will be populated. The installed hook at `~/.claude/scripts/hooks/gsd-t-conversation-capture.js` syncs on next `npm publish` + `/gsd-t-version-update-all`.
62
+ - **Contract**: `conversation-capture-contract.md` v1.0.0 → v1.1.0 (assistant-body extraction protocol documented; schema unchanged — same `assistant_turn` frame, just populated where v1.0.0 was bodyless).
6
63
 
7
64
  ## [3.23.10] - 2026-05-06
8
65
 
package/bin/gsd-t.js CHANGED
@@ -45,6 +45,7 @@ const { mapHeadlessExitCode } = require(path.join(__dirname, "headless-exit-code
45
45
  const CLAUDE_DIR = path.join(os.homedir(), ".claude");
46
46
  const COMMANDS_DIR = path.join(CLAUDE_DIR, "commands");
47
47
  const SCRIPTS_DIR = path.join(CLAUDE_DIR, "scripts");
48
+ const GLOBAL_BIN_DIR = path.join(CLAUDE_DIR, "bin");
48
49
  const CLAUDE_TEMPLATES_DIR = path.join(CLAUDE_DIR, "templates");
49
50
  const GLOBAL_CLAUDE_MD = path.join(CLAUDE_DIR, "CLAUDE.md");
50
51
  const SETTINGS_JSON = path.join(CLAUDE_DIR, "settings.json");
@@ -1169,6 +1170,33 @@ function installUtilityScripts() {
1169
1170
  }
1170
1171
  }
1171
1172
 
1173
+ // ─── Global Bin Tools (~/.claude/bin/) ───────────────────────────────────────
1174
+ // Modules resolved by globally-installed scripts via
1175
+ // `path.join(__dirname, "..", "bin", <tool>)` (e.g. gsd-t-dashboard-server.js
1176
+ // → parallelism-report.cjs). Distinct from PROJECT_BIN_TOOLS, which copy into
1177
+ // each registered project's bin/.
1178
+ const GLOBAL_BIN_TOOLS = ["parallelism-report.cjs", "live-activity-report.cjs"];
1179
+
1180
+ function installGlobalBinTools() {
1181
+ ensureDir(GLOBAL_BIN_DIR);
1182
+ for (const tool of GLOBAL_BIN_TOOLS) {
1183
+ const src = path.join(PKG_ROOT, "bin", tool);
1184
+ const dest = path.join(GLOBAL_BIN_DIR, tool);
1185
+ if (!fs.existsSync(src)) {
1186
+ warn(`Global bin tool source missing: ${tool} — skipping`);
1187
+ continue;
1188
+ }
1189
+ const srcContent = fs.readFileSync(src, "utf8");
1190
+ const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, "utf8") : "";
1191
+ if (normalizeEol(srcContent) !== normalizeEol(destContent)) {
1192
+ copyFile(src, dest, `bin/${tool}`);
1193
+ try { fs.chmodSync(dest, 0o755); } catch {}
1194
+ } else {
1195
+ info(`bin/${tool} unchanged`);
1196
+ }
1197
+ }
1198
+ }
1199
+
1172
1200
  // ─── CGC (CodeGraphContext) ──────────────────────────────────────────────────
1173
1201
 
1174
1202
  function installCgc() {
@@ -1508,6 +1536,9 @@ async function doInstall(opts = {}) {
1508
1536
  heading("Utility Scripts");
1509
1537
  installUtilityScripts();
1510
1538
 
1539
+ heading("Global Bin Tools (~/.claude/bin/)");
1540
+ installGlobalBinTools();
1541
+
1511
1542
  heading("Context Meter (PostToolUse)");
1512
1543
  const cmHook = configureContextMeterHooks(SETTINGS_JSON);
1513
1544
  if (cmHook.installed) {
@@ -2725,6 +2756,20 @@ function checkDoctorInstallation() {
2725
2756
  issues += checkDoctorClaudeMd();
2726
2757
  issues += checkDoctorSettings();
2727
2758
  issues += checkDoctorEncoding(installed);
2759
+ issues += checkDoctorGlobalBin();
2760
+ return issues;
2761
+ }
2762
+
2763
+ function checkDoctorGlobalBin() {
2764
+ let issues = 0;
2765
+ const missing = GLOBAL_BIN_TOOLS.filter((tool) => !fs.existsSync(path.join(GLOBAL_BIN_DIR, tool)));
2766
+ if (missing.length === 0) {
2767
+ success(`All ${GLOBAL_BIN_TOOLS.length} global bin tool${GLOBAL_BIN_TOOLS.length === 1 ? "" : "s"} installed (~/.claude/bin/)`);
2768
+ } else {
2769
+ warn(`Missing global bin tool${missing.length === 1 ? "" : "s"} in ~/.claude/bin/: ${missing.join(", ")}`);
2770
+ info("Fix: re-run `npx @tekyzinc/gsd-t install` (or `update`)");
2771
+ issues++;
2772
+ }
2728
2773
  return issues;
2729
2774
  }
2730
2775