miriad-viz 0.7.3 → 0.8.0

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.
Files changed (2) hide show
  1. package/dist-cli/index.js +156 -45
  2. package/package.json +1 -1
package/dist-cli/index.js CHANGED
@@ -216,18 +216,6 @@ function classifyIntensity(total) {
216
216
  if (total <= INTENSITY_THRESHOLDS.busy) return "busy";
217
217
  return "intense";
218
218
  }
219
- function getSuggestion(intensity) {
220
- switch (intensity) {
221
- case "quiet":
222
- return "high gapVizSpeed (skip through quiet period)";
223
- case "moderate":
224
- return "default vizSpeed (normal pacing)";
225
- case "busy":
226
- return "low vizSpeed (let viewer see the activity)";
227
- case "intense":
228
- return "low vizSpeed (let viewer see the frenzy)";
229
- }
230
- }
231
219
  function countInWindow(sortedTimestamps, start, end) {
232
220
  let count = 0;
233
221
  for (const t of sortedTimestamps) {
@@ -243,18 +231,23 @@ function computeEventDensity(input, lines, projectStart, projectEnd) {
243
231
  if (Number.isNaN(startMs) || Number.isNaN(endMs) || endMs <= startMs) {
244
232
  return [];
245
233
  }
246
- const commitMs = input.commitDates.map((d) => new Date(d).getTime()).filter((t) => !Number.isNaN(t)).sort((a, b) => a - b);
247
- const prMs = input.prMergeDates.map((d) => new Date(d).getTime()).filter((t) => !Number.isNaN(t)).sort((a, b) => a - b);
248
- const msgMs = input.messageDates.map((d) => new Date(d).getTime()).filter((t) => !Number.isNaN(t)).sort((a, b) => a - b);
234
+ const toSortedMs = (dates) => dates.map((d) => new Date(d).getTime()).filter((t) => !Number.isNaN(t)).sort((a, b) => a - b);
235
+ const commitMs = toSortedMs(input.commitDates);
236
+ const prMs = toSortedMs(input.prMergeDates);
237
+ const msgMs = toSortedMs(input.messageDates);
238
+ const pillMs = toSortedMs(input.pillDates);
239
+ const fileMs = toSortedMs(input.fileDates);
249
240
  const totalDuration = endMs - startMs;
250
241
  const sliceDuration = totalDuration / lines.length;
251
242
  return lines.map((line, i) => {
252
- const windowStart = startMs + i * sliceDuration;
253
- const windowEnd = startMs + (i + 1) * sliceDuration;
254
- const commits = countInWindow(commitMs, windowStart, windowEnd);
255
- const prs = countInWindow(prMs, windowStart, windowEnd);
256
- const messages = countInWindow(msgMs, windowStart, windowEnd);
257
- const total = commits + prs + messages;
243
+ const wStart = startMs + i * sliceDuration;
244
+ const wEnd = startMs + (i + 1) * sliceDuration;
245
+ const commits = countInWindow(commitMs, wStart, wEnd);
246
+ const prs = countInWindow(prMs, wStart, wEnd);
247
+ const messages = countInWindow(msgMs, wStart, wEnd);
248
+ const pills = countInWindow(pillMs, wStart, wEnd);
249
+ const files = countInWindow(fileMs, wStart, wEnd);
250
+ const total = commits + prs + messages + pills + files;
258
251
  const intensity = classifyIntensity(total);
259
252
  return {
260
253
  lineId: line.id,
@@ -263,22 +256,23 @@ function computeEventDensity(input, lines, projectStart, projectEnd) {
263
256
  commits,
264
257
  prs,
265
258
  messages,
259
+ pills,
260
+ files,
266
261
  total,
267
262
  intensity,
268
- suggestion: getSuggestion(intensity)
263
+ windowStart: new Date(wStart).toISOString(),
264
+ windowEnd: new Date(wEnd).toISOString()
269
265
  };
270
266
  });
271
267
  }
272
- function formatDensityReport(reports, projectStart, projectEnd) {
268
+ function formatDensityReport(reports, projectStart, projectEnd, timingLines) {
273
269
  if (reports.length === 0) return [];
274
- const lines = [];
275
270
  const startMs = new Date(projectStart).getTime();
276
271
  const endMs = new Date(projectEnd).getTime();
277
272
  const totalDuration = endMs - startMs;
278
- const sliceDuration = totalDuration / reports.length;
279
- lines.push(" \u{1F4CA} Event density (from extracted data):");
280
- const formatTime = (ms) => {
281
- const d = new Date(ms);
273
+ const lines = [];
274
+ const formatTime = (iso) => {
275
+ const d = new Date(iso);
282
276
  const months = [
283
277
  "Jan",
284
278
  "Feb",
@@ -295,22 +289,50 @@ function formatDensityReport(reports, projectStart, projectEnd) {
295
289
  ];
296
290
  return `${months[d.getUTCMonth()]} ${d.getUTCDate()} ${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}`;
297
291
  };
292
+ lines.push(" \u{1F4CA} Activity density per narration line:");
298
293
  lines.push(
299
- ` Your script covers ${formatTime(startMs)} \u2192 ${formatTime(endMs)} (${Math.round(totalDuration / 36e5)}h)`
294
+ ` Your script covers ${formatTime(projectStart)} \u2192 ${formatTime(projectEnd)} (${Math.round(totalDuration / 36e5)}h)`
300
295
  );
301
296
  lines.push("");
302
- for (let i = 0; i < reports.length; i++) {
303
- const r = reports[i];
304
- const windowStart = startMs + i * sliceDuration;
305
- const windowEnd = startMs + (i + 1) * sliceDuration;
306
- const intensityLabel = r.intensity === "intense" ? "INTENSE" : r.intensity;
307
- lines.push(` ${r.lineId} "${r.text}"`);
308
- lines.push(
309
- ` covers: ${formatTime(windowStart)}-${formatTime(windowEnd)} | ${r.prs} PRs, ${r.messages} messages (${intensityLabel})`
310
- );
311
- lines.push(` \u2192 suggestion: ${r.suggestion}`);
312
- lines.push("");
297
+ const timingMap = /* @__PURE__ */ new Map();
298
+ if (timingLines) {
299
+ for (const tl of timingLines) timingMap.set(tl.id, tl);
313
300
  }
301
+ const hasTiming = timingMap.size > 0;
302
+ const idW = Math.max(7, ...reports.map((r) => r.lineId.length));
303
+ const pad = (s, w) => s.padEnd(w);
304
+ const rpad = (s, w) => s.padStart(w);
305
+ let header = ` ${pad("Line ID", idW)}`;
306
+ if (hasTiming) header += ` | ${rpad("Dur", 6)} | ${rpad("Speed", 5)}`;
307
+ header += ` | ${rpad("Pills", 5)} | ${rpad("Msgs", 5)} | ${rpad("Cmts", 5)} | ${rpad("PRs", 4)} | ${rpad("Files", 5)} | Time Range`;
308
+ lines.push(header);
309
+ let sep = ` ${"\u2500".repeat(idW)}`;
310
+ if (hasTiming) sep += `\u2500\u253C${"\u2500".repeat(8)}\u2500\u253C${"\u2500".repeat(7)}`;
311
+ sep += `\u2500\u253C${"\u2500".repeat(7)}\u2500\u253C${"\u2500".repeat(7)}\u2500\u253C${"\u2500".repeat(7)}\u2500\u253C${"\u2500".repeat(6)}\u2500\u253C${"\u2500".repeat(7)}\u2500\u253C${"\u2500".repeat(20)}`;
312
+ lines.push(sep);
313
+ for (const r of reports) {
314
+ const tl = timingMap.get(r.lineId);
315
+ let row = ` ${pad(r.lineId, idW)}`;
316
+ if (hasTiming) {
317
+ const dur = tl ? `${tl.durationSec.toFixed(1)}s` : "\u2014";
318
+ const spd = tl ? `${tl.vizSpeed.toFixed(1)}x` : "\u2014";
319
+ row += ` | ${rpad(dur, 6)} | ${rpad(spd, 5)}`;
320
+ }
321
+ row += ` | ${rpad(String(r.pills), 5)} | ${rpad(String(r.messages), 5)} | ${rpad(String(r.commits), 5)} | ${rpad(String(r.prs), 4)} | ${rpad(String(r.files), 5)}`;
322
+ row += ` | ${formatTime(r.windowStart)}-${formatTime(r.windowEnd)}`;
323
+ lines.push(row);
324
+ }
325
+ lines.push("");
326
+ lines.push(" \u{1F4CA} Pacing guidance:");
327
+ lines.push(" - High pill count \u2192 lower vizSpeed so pills appear one-by-one like dialog");
328
+ lines.push(" - Zero activity \u2192 raise vizSpeed to skip dead time");
329
+ lines.push(
330
+ " - Messages \u2260 pills: messages = raw activity density, pills = curated selections that actually render"
331
+ );
332
+ lines.push(
333
+ " - Commits/PRs/Files are visual events too \u2014 a section can be busy even if chat is quiet"
334
+ );
335
+ lines.push(" - Goal: narration should match what the viewer shows on screen");
314
336
  return lines;
315
337
  }
316
338
 
@@ -329,14 +351,16 @@ function buildVizTimingPending(_progress) {
329
351
  output.push(" (once pacing.json exists, you'll get the iterate loop instructions)");
330
352
  return output;
331
353
  }
332
- function buildVizTimingInProgress(_progress, densityInput, scriptLines, projectStart, projectEnd) {
354
+ function buildVizTimingInProgress(_progress, densityInput, scriptLines, projectStart, projectEnd, timingLines) {
333
355
  const output = [];
334
356
  output.push("", "\u23F1\uFE0F Viz Timing \u2014 Iterate Loop");
335
357
  if (densityInput && scriptLines && scriptLines.length > 0 && projectStart && projectEnd) {
336
358
  const reports = computeEventDensity(densityInput, scriptLines, projectStart, projectEnd);
337
359
  if (reports.length > 0) {
338
360
  output.push("");
339
- output.push(...formatDensityReport(reports, projectStart, projectEnd));
361
+ output.push(
362
+ ...formatDensityReport(reports, projectStart, projectEnd, timingLines ?? void 0)
363
+ );
340
364
  }
341
365
  }
342
366
  output.push("");
@@ -458,7 +482,7 @@ function buildCommand(step, progress, flags) {
458
482
  }
459
483
  return parts.join(" ");
460
484
  }
461
- function computeNext(progress, existingFiles, logTails, flags = {}, dataSummary, env = {}) {
485
+ function computeNext(progress, existingFiles, logTails, flags = {}, dataSummary, env = {}, densityData) {
462
486
  const fileSet = new Set(existingFiles);
463
487
  const step = getNextStep(progress);
464
488
  if (!step) {
@@ -845,7 +869,16 @@ function computeNext(progress, existingFiles, logTails, flags = {}, dataSummary,
845
869
  };
846
870
  }
847
871
  if (state.status === "in_progress") {
848
- output.push(...buildVizTimingInProgress(progress, null, null, null, null));
872
+ output.push(
873
+ ...buildVizTimingInProgress(
874
+ progress,
875
+ densityData?.densityInput ?? null,
876
+ densityData?.scriptLines ?? null,
877
+ densityData?.projectStart ?? null,
878
+ densityData?.projectEnd ?? null,
879
+ densityData?.timingLines ?? null
880
+ )
881
+ );
849
882
  return { action: "waiting_for_edit", step, progress, output };
850
883
  }
851
884
  output.push(...buildVizTimingPending(progress));
@@ -1378,6 +1411,75 @@ function gatherDataSummary(projectDir, dataDir) {
1378
1411
  data.chatActivity = tryRead("chat-activity.json");
1379
1412
  return buildDataSummary(data);
1380
1413
  }
1414
+ function gatherDensityData(projectDir, dataDir) {
1415
+ const tryRead = (filename) => {
1416
+ const path = resolve3(projectDir, dataDir, filename);
1417
+ if (!existsSync3(path)) return null;
1418
+ try {
1419
+ return JSON.parse(readFileSync2(path, "utf-8"));
1420
+ } catch {
1421
+ return null;
1422
+ }
1423
+ };
1424
+ const script = tryRead("script.json");
1425
+ if (!script?.lines || script.lines.length === 0) return null;
1426
+ const vizData = tryRead("viz-data.json");
1427
+ const messagesFull = tryRead("messages-full.json");
1428
+ const timing = tryRead("timing.json");
1429
+ const input = {
1430
+ commitDates: [],
1431
+ prMergeDates: [],
1432
+ messageDates: [],
1433
+ pillDates: [],
1434
+ fileDates: []
1435
+ };
1436
+ if (vizData?.events) {
1437
+ for (const evt of vizData.events) {
1438
+ const iso = new Date(evt.timestamp).toISOString();
1439
+ switch (evt.type) {
1440
+ case "commit":
1441
+ input.commitDates.push(iso);
1442
+ break;
1443
+ case "pr_created":
1444
+ case "pr_merged":
1445
+ input.prMergeDates.push(iso);
1446
+ break;
1447
+ case "message":
1448
+ input.pillDates.push(iso);
1449
+ break;
1450
+ case "artifact":
1451
+ input.fileDates.push(iso);
1452
+ break;
1453
+ }
1454
+ }
1455
+ }
1456
+ if (Array.isArray(messagesFull)) {
1457
+ for (const msg of messagesFull) {
1458
+ if (msg.timestamp) input.messageDates.push(msg.timestamp);
1459
+ }
1460
+ }
1461
+ let projectStart = null;
1462
+ let projectEnd = null;
1463
+ if (vizData?.timeRange) {
1464
+ projectStart = new Date(vizData.timeRange.start).toISOString();
1465
+ projectEnd = new Date(vizData.timeRange.end).toISOString();
1466
+ }
1467
+ let timingLines = null;
1468
+ if (timing?.lines) {
1469
+ timingLines = timing.lines.map((l) => ({
1470
+ id: l.id,
1471
+ durationSec: l.durationSec,
1472
+ vizSpeed: l.vizSpeed
1473
+ }));
1474
+ }
1475
+ return {
1476
+ densityInput: input,
1477
+ scriptLines: script.lines,
1478
+ projectStart,
1479
+ projectEnd,
1480
+ timingLines
1481
+ };
1482
+ }
1381
1483
  async function runNext(flags) {
1382
1484
  const { projectDir, progress } = requireProject();
1383
1485
  const dataDir = resolve3(projectDir, progress.project.dataDir);
@@ -1437,7 +1539,16 @@ async function runNext(flags) {
1437
1539
  const existingFiles = [...gatherExistingFiles(projectDir), ...gatherAudioFiles(projectDir)];
1438
1540
  const logTails = gatherLogTails(projectDir);
1439
1541
  const dataSummary = gatherDataSummary(projectDir, progress.project.dataDir);
1440
- const result = computeNext(progress, existingFiles, logTails, nextFlags, dataSummary, env);
1542
+ const densityData = gatherDensityData(projectDir, progress.project.dataDir);
1543
+ const result = computeNext(
1544
+ progress,
1545
+ existingFiles,
1546
+ logTails,
1547
+ nextFlags,
1548
+ dataSummary,
1549
+ env,
1550
+ densityData
1551
+ );
1441
1552
  for (const line of result.output) {
1442
1553
  console.log(line);
1443
1554
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miriad-viz",
3
- "version": "0.7.3",
3
+ "version": "0.8.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/snorrees/miriad-viz.git"