miriad-viz 0.7.3 → 0.8.1
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/dist-cli/index.js +175 -45
- 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
|
|
247
|
-
const
|
|
248
|
-
const
|
|
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
|
|
253
|
-
const
|
|
254
|
-
const commits = countInWindow(commitMs,
|
|
255
|
-
const prs = countInWindow(prMs,
|
|
256
|
-
const messages = countInWindow(msgMs,
|
|
257
|
-
const
|
|
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
|
-
|
|
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
|
|
279
|
-
|
|
280
|
-
|
|
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(
|
|
294
|
+
` Your script covers ${formatTime(projectStart)} \u2192 ${formatTime(projectEnd)} (${Math.round(totalDuration / 36e5)}h)`
|
|
300
295
|
);
|
|
301
296
|
lines.push("");
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
)
|
|
311
|
-
|
|
312
|
-
|
|
297
|
+
const timingMap = /* @__PURE__ */ new Map();
|
|
298
|
+
if (timingLines) {
|
|
299
|
+
for (const tl of timingLines) timingMap.set(tl.id, tl);
|
|
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);
|
|
313
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(
|
|
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(
|
|
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,87 @@ function gatherDataSummary(projectDir, dataDir) {
|
|
|
1378
1411
|
data.chatActivity = tryRead("chat-activity.json");
|
|
1379
1412
|
return buildDataSummary(data);
|
|
1380
1413
|
}
|
|
1414
|
+
function gatherDensityData(projectDir, dataDir, outputDir) {
|
|
1415
|
+
const tryRead = (filename) => {
|
|
1416
|
+
const dataPath = resolve3(projectDir, dataDir, filename);
|
|
1417
|
+
if (existsSync3(dataPath)) {
|
|
1418
|
+
try {
|
|
1419
|
+
return JSON.parse(readFileSync2(dataPath, "utf-8"));
|
|
1420
|
+
} catch {
|
|
1421
|
+
return null;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
if (outputDir) {
|
|
1425
|
+
const outputPath = resolve3(projectDir, outputDir, filename);
|
|
1426
|
+
if (existsSync3(outputPath)) {
|
|
1427
|
+
try {
|
|
1428
|
+
return JSON.parse(readFileSync2(outputPath, "utf-8"));
|
|
1429
|
+
} catch {
|
|
1430
|
+
return null;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
return null;
|
|
1435
|
+
};
|
|
1436
|
+
const script = tryRead("script.json");
|
|
1437
|
+
if (!script?.lines || script.lines.length === 0) return null;
|
|
1438
|
+
const vizData = tryRead("viz-data.json");
|
|
1439
|
+
const messagesFull = tryRead("messages-full.json");
|
|
1440
|
+
const timing = tryRead("timing.json");
|
|
1441
|
+
const input = {
|
|
1442
|
+
commitDates: [],
|
|
1443
|
+
prMergeDates: [],
|
|
1444
|
+
messageDates: [],
|
|
1445
|
+
pillDates: [],
|
|
1446
|
+
fileDates: []
|
|
1447
|
+
};
|
|
1448
|
+
if (vizData?.events) {
|
|
1449
|
+
for (const evt of vizData.events) {
|
|
1450
|
+
const iso = new Date(evt.timestamp).toISOString();
|
|
1451
|
+
switch (evt.type) {
|
|
1452
|
+
case "commit":
|
|
1453
|
+
input.commitDates.push(iso);
|
|
1454
|
+
break;
|
|
1455
|
+
case "pr_created":
|
|
1456
|
+
case "pr_merged":
|
|
1457
|
+
input.prMergeDates.push(iso);
|
|
1458
|
+
break;
|
|
1459
|
+
case "message":
|
|
1460
|
+
input.pillDates.push(iso);
|
|
1461
|
+
break;
|
|
1462
|
+
case "artifact":
|
|
1463
|
+
input.fileDates.push(iso);
|
|
1464
|
+
break;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
if (Array.isArray(messagesFull)) {
|
|
1469
|
+
for (const msg of messagesFull) {
|
|
1470
|
+
if (msg.timestamp) input.messageDates.push(msg.timestamp);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
let projectStart = null;
|
|
1474
|
+
let projectEnd = null;
|
|
1475
|
+
if (vizData?.timeRange) {
|
|
1476
|
+
projectStart = new Date(vizData.timeRange.start).toISOString();
|
|
1477
|
+
projectEnd = new Date(vizData.timeRange.end).toISOString();
|
|
1478
|
+
}
|
|
1479
|
+
let timingLines = null;
|
|
1480
|
+
if (timing?.lines) {
|
|
1481
|
+
timingLines = timing.lines.map((l) => ({
|
|
1482
|
+
id: l.id,
|
|
1483
|
+
durationSec: l.durationSec,
|
|
1484
|
+
vizSpeed: l.vizSpeed
|
|
1485
|
+
}));
|
|
1486
|
+
}
|
|
1487
|
+
return {
|
|
1488
|
+
densityInput: input,
|
|
1489
|
+
scriptLines: script.lines,
|
|
1490
|
+
projectStart,
|
|
1491
|
+
projectEnd,
|
|
1492
|
+
timingLines
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1381
1495
|
async function runNext(flags) {
|
|
1382
1496
|
const { projectDir, progress } = requireProject();
|
|
1383
1497
|
const dataDir = resolve3(projectDir, progress.project.dataDir);
|
|
@@ -1437,7 +1551,20 @@ async function runNext(flags) {
|
|
|
1437
1551
|
const existingFiles = [...gatherExistingFiles(projectDir), ...gatherAudioFiles(projectDir)];
|
|
1438
1552
|
const logTails = gatherLogTails(projectDir);
|
|
1439
1553
|
const dataSummary = gatherDataSummary(projectDir, progress.project.dataDir);
|
|
1440
|
-
const
|
|
1554
|
+
const densityData = gatherDensityData(
|
|
1555
|
+
projectDir,
|
|
1556
|
+
progress.project.dataDir,
|
|
1557
|
+
progress.project.outputDir
|
|
1558
|
+
);
|
|
1559
|
+
const result = computeNext(
|
|
1560
|
+
progress,
|
|
1561
|
+
existingFiles,
|
|
1562
|
+
logTails,
|
|
1563
|
+
nextFlags,
|
|
1564
|
+
dataSummary,
|
|
1565
|
+
env,
|
|
1566
|
+
densityData
|
|
1567
|
+
);
|
|
1441
1568
|
for (const line of result.output) {
|
|
1442
1569
|
console.log(line);
|
|
1443
1570
|
}
|
|
@@ -1705,3 +1832,6 @@ main().catch((err) => {
|
|
|
1705
1832
|
console.error(err);
|
|
1706
1833
|
process.exit(1);
|
|
1707
1834
|
});
|
|
1835
|
+
export {
|
|
1836
|
+
gatherDensityData
|
|
1837
|
+
};
|