@tekyzinc/gsd-t 3.20.12 → 3.20.13
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 +16 -0
- package/package.json +1 -1
- package/scripts/gsd-t-dashboard-server.js +40 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [3.20.13] - 2026-05-05
|
|
6
|
+
|
|
7
|
+
### Fixed — visualizer: surface in-session NDJSONs when `.index.json` is empty
|
|
8
|
+
|
|
9
|
+
The dashboard's `/transcripts` endpoint only read `.gsd-t/transcripts/.index.json` to populate the left-rail spawn list. The M45 D2 conversation-capture hook writes `in-session-{sessionId}.ndjson` directly to `transcripts/` but does NOT update the index — the index is owned by the spawn lifecycle, not the in-session hook. Result: the visualizer's left rail showed "no spawns yet" even when the in-session conversation was actively being captured to disk. Discovered when the M43 D1 + M45 D2 hooks were installed (v3.20.12) and the conversation NDJSONs were appearing on disk but invisible in the UI.
|
|
10
|
+
|
|
11
|
+
**Changes:**
|
|
12
|
+
- `scripts/gsd-t-dashboard-server.js`: new `listInSessionTranscripts(projectDir)` function scans `transcripts/` for `in-session-*.ndjson` files and returns spawn-shaped entries with `spawnId: in-session-{sessionId}`. Filenames are validated through `isValidSpawnId` for path-traversal safety. `handleTranscriptsList` merges these with the index entries (index takes precedence on dup `spawnId`). The viewer's existing `in-session-` prefix detection then applies the `💬 conversation` badge.
|
|
13
|
+
- `test/dashboard-server.test.js`: 5 new regression tests covering the empty-dir case, missing-dir case, find/filter behavior, mixed file types, and malformed-filename rejection.
|
|
14
|
+
|
|
15
|
+
**Migration:** existing dashboards picking up the new code will surface in-session conversations automatically on next refresh of `/transcripts`. No state migration needed; the index is read-only here.
|
|
16
|
+
|
|
17
|
+
**Suite:** 2047/2047 pass.
|
|
18
|
+
|
|
19
|
+
**Side cleanup:** killed 144 + 20 orphan `gsd-t-dashboard-server.js` processes (164 total) accumulated from prior detached spawns whose parents had exited and been reparented to launchd. 3 stale pidfiles cleaned. Per-project `transcripts/` directories pre-created in 15 GSD-T projects so the M45 D2 hook can write without first-run delay.
|
|
20
|
+
|
|
5
21
|
## [3.20.12] - 2026-05-05
|
|
6
22
|
|
|
7
23
|
### Fixed — install: auto-configure M43 D1 + M45 D2 in-session hooks
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "3.20.
|
|
3
|
+
"version": "3.20.13",
|
|
4
4
|
"description": "GSD-T: Contract-Driven Development for Claude Code — 54 slash commands with headless-by-default workflow spawning, unattended supervisor relay with event stream, graph-powered code analysis, real-time agent dashboard, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
|
|
5
5
|
"author": "Tekyz, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -176,9 +176,47 @@ function isValidSpawnId(id) {
|
|
|
176
176
|
return typeof id === "string" && /^[a-zA-Z0-9._-]+$/.test(id) && id.length <= 200;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
// M45 D2 follow-up: filesystem-walk fallback for in-session conversation NDJSONs.
|
|
180
|
+
// The conversation-capture hook writes `in-session-{sessionId}.ndjson` directly
|
|
181
|
+
// to `transcripts/`, but does NOT update `.index.json` (the index is owned by
|
|
182
|
+
// the spawn lifecycle, not by the in-session hook). Without this scan, the
|
|
183
|
+
// dashboard's left rail never shows in-session conversations even though the
|
|
184
|
+
// NDJSONs are on disk. Synthesizes a spawn-shaped entry per in-session file
|
|
185
|
+
// using filesystem timestamps; the viewer's `in-session-` prefix detection
|
|
186
|
+
// then labels it as `💬 conversation`.
|
|
187
|
+
function listInSessionTranscripts(projectDir) {
|
|
188
|
+
const dir = transcriptsDir(projectDir);
|
|
189
|
+
let files;
|
|
190
|
+
try { files = fs.readdirSync(dir); } catch { return []; }
|
|
191
|
+
const out = [];
|
|
192
|
+
for (const f of files) {
|
|
193
|
+
if (!f.startsWith("in-session-") || !f.endsWith(".ndjson")) continue;
|
|
194
|
+
const spawnId = f.slice(0, -".ndjson".length); // "in-session-{sessionId}"
|
|
195
|
+
if (!isValidSpawnId(spawnId)) continue;
|
|
196
|
+
let stat;
|
|
197
|
+
try { stat = fs.statSync(path.join(dir, f)); } catch { continue; }
|
|
198
|
+
out.push({
|
|
199
|
+
spawnId,
|
|
200
|
+
command: "in-session conversation",
|
|
201
|
+
startedAt: stat.birthtime ? stat.birthtime.toISOString() : stat.mtime.toISOString(),
|
|
202
|
+
lastUpdatedAt: stat.mtime.toISOString(),
|
|
203
|
+
status: "active", // best-effort; the viewer doesn't currently use this field
|
|
204
|
+
kind: "in-session",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
|
|
179
210
|
function handleTranscriptsList(req, res, projectDir, transcriptHtmlPath) {
|
|
180
211
|
const idx = readTranscriptsIndex(projectDir);
|
|
181
|
-
|
|
212
|
+
|
|
213
|
+
// Merge index entries with in-session NDJSONs from the filesystem.
|
|
214
|
+
// De-dupe by spawnId — index entries take precedence (richer metadata).
|
|
215
|
+
const known = new Set(idx.spawns.map((s) => s.spawnId));
|
|
216
|
+
const inSession = listInSessionTranscripts(projectDir).filter((s) => !known.has(s.spawnId));
|
|
217
|
+
const merged = idx.spawns.concat(inSession);
|
|
218
|
+
|
|
219
|
+
const sorted = merged
|
|
182
220
|
.slice()
|
|
183
221
|
.sort((a, b) => (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0));
|
|
184
222
|
|
|
@@ -723,6 +761,7 @@ module.exports = {
|
|
|
723
761
|
writeTranscriptsIndex,
|
|
724
762
|
readIndexEntry,
|
|
725
763
|
isValidSpawnId,
|
|
764
|
+
listInSessionTranscripts,
|
|
726
765
|
handleTranscriptsList,
|
|
727
766
|
handleTranscriptStream,
|
|
728
767
|
handleTranscriptPage,
|