@lingjingai/scriptctl 0.28.0 → 0.28.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/README.md +1 -1
- package/changes/0.27.2.md +13 -0
- package/changes/0.28.0.md +14 -0
- package/changes/0.28.1.md +13 -0
- package/changes/unreleased.md +2 -3
- package/dist/common.js +15 -1
- package/dist/common.js.map +1 -1
- package/dist/domain/direct-core.d.ts +31 -0
- package/dist/domain/direct-core.js +436 -65
- package/dist/domain/direct-core.js.map +1 -1
- package/dist/domain/script/validate.js +23 -1
- package/dist/domain/script/validate.js.map +1 -1
- package/dist/help-text.js +2 -5
- package/dist/help-text.js.map +1 -1
- package/dist/usecases/direct.js +107 -60
- package/dist/usecases/direct.js.map +1 -1
- package/dist/usecases/parse.js +14 -6
- package/dist/usecases/parse.js.map +1 -1
- package/dist/usecases/update.js +6 -6
- package/dist/usecases/update.js.map +1 -1
- package/package.json +1 -1
package/dist/usecases/direct.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { CliError, DEFAULT_BATCH_MAX_CHARS, DEFAULT_BATCH_MIN_LINES, DEFAULT_BATCH_MODE, DEFAULT_BATCH_TARGET_LINES, DEFAULT_CONCURRENCY, DEFAULT_MODEL, DEFAULT_PROVIDER, DEFAULT_SUMMARY_GROUP_SIZE, DEFAULT_SUMMARY_REDUCE_THRESHOLD_CHARS, EXIT_INPUT, EXIT_NEEDS_AGENT, EXIT_OK, EXIT_RUNTIME, EXIT_USAGE, SUPPORTED_EXTS, deletePath, directDir, exists, fmtId, readJson, readText, sha256Text, writeJson, } from "../common.js";
|
|
4
|
-
import { compactBatchResult, compactEpisodeResult, buildBatchPlan, buildEpisodePlan, DEFAULT_TITLE_CHUNK_SIZE, chunkEpisodesNeedingTitles, applyEpisodeTitlesToPlan, cleanSynopsisList, episodeTitleResponseMap, extractBatchWithRecovery,
|
|
4
|
+
import { compactBatchResult, compactEpisodeResult, buildBatchPlan, buildEpisodePlan, DEFAULT_TITLE_CHUNK_SIZE, chunkEpisodesNeedingTitles, applyEpisodeTitlesToPlan, cleanSynopsisList, episodeTitleResponseMap, extractBatchWithRecovery, mergeEpisodeResultsWithReport, normalizeEpisodeResult, planSynopsisReduction, readCarriedSynopsis, sortDeep, uniqueAdd, validateEpisodeExtractionQuality, _md_push_asset, curateScriptAssets, applyMetadataToScript, } from "../domain/direct-core.js";
|
|
5
5
|
import { validateScript } from "../domain/script/validate.js";
|
|
6
6
|
import { makeProvider, providerCapabilities } from "../infra/providers.js";
|
|
7
7
|
import { makeSourceManifest, prepareSource, } from "../infra/converters.js";
|
|
@@ -211,13 +211,11 @@ function statusFromBatchState(state) {
|
|
|
211
211
|
function stageSnapshot(name, status, completed, total, message) {
|
|
212
212
|
const boundedTotal = Math.max(0, total);
|
|
213
213
|
const boundedCompleted = Math.max(0, Math.min(completed, boundedTotal));
|
|
214
|
-
const percent = boundedTotal > 0 ? Math.round((boundedCompleted / boundedTotal) * 100) : status === "success" ? 100 : 0;
|
|
215
214
|
return {
|
|
216
215
|
name,
|
|
217
216
|
status,
|
|
218
217
|
completed: boundedCompleted,
|
|
219
218
|
total: boundedTotal,
|
|
220
|
-
percent,
|
|
221
219
|
pending: status === "skipped" ? 0 : Math.max(0, boundedTotal - boundedCompleted),
|
|
222
220
|
warning: status === "warning" ? 1 : 0,
|
|
223
221
|
error: status === "error" ? 1 : 0,
|
|
@@ -229,6 +227,17 @@ function fileStage(name, filePath, runningStage) {
|
|
|
229
227
|
return stageSnapshot(name, "success", 1, 1);
|
|
230
228
|
return stageSnapshot(name, runningStage === name ? "running" : "pending", 0, 1);
|
|
231
229
|
}
|
|
230
|
+
function orderedFileStage(name, filePath, runningStage, runStatus) {
|
|
231
|
+
if (!canTrustStageArtifacts(name, runningStage, runStatus)) {
|
|
232
|
+
return stageSnapshot(name, "pending", 0, 1);
|
|
233
|
+
}
|
|
234
|
+
return fileStage(name, filePath, runningStage ?? "");
|
|
235
|
+
}
|
|
236
|
+
function canTrustStageArtifacts(name, runningStage, runStatus) {
|
|
237
|
+
if (runStatus !== "init_running" || !runningStage)
|
|
238
|
+
return true;
|
|
239
|
+
return stageOrder(name) <= stageOrder(runningStage);
|
|
240
|
+
}
|
|
232
241
|
function readJsonFile(filePath, fallback) {
|
|
233
242
|
if (!exists(filePath))
|
|
234
243
|
return fallback;
|
|
@@ -296,15 +305,14 @@ function userStageRows(stages) {
|
|
|
296
305
|
}
|
|
297
306
|
const rows = [];
|
|
298
307
|
for (const items of byLabel.values()) {
|
|
299
|
-
const total = items.reduce((sum, item) => sum +
|
|
300
|
-
const completed = items.reduce((sum, item) => sum +
|
|
308
|
+
const total = items.reduce((sum, item) => sum + item.total, 0);
|
|
309
|
+
const completed = items.reduce((sum, item) => sum + (item.status === "skipped" ? item.total : item.completed), 0);
|
|
301
310
|
const status = items.reduce((max, item) => statusPriority(item.status) > statusPriority(max) ? item.status : max, "success");
|
|
302
311
|
rows.push({
|
|
303
312
|
name: items[0].name,
|
|
304
313
|
status,
|
|
305
314
|
completed: Math.round(completed),
|
|
306
315
|
total,
|
|
307
|
-
percent: total > 0 ? Math.round((completed / total) * 100) : 0,
|
|
308
316
|
pending: items.reduce((sum, item) => sum + item.pending, 0),
|
|
309
317
|
warning: items.reduce((sum, item) => sum + item.warning, 0),
|
|
310
318
|
error: items.reduce((sum, item) => sum + item.error, 0),
|
|
@@ -313,31 +321,33 @@ function userStageRows(stages) {
|
|
|
313
321
|
}
|
|
314
322
|
return rows;
|
|
315
323
|
}
|
|
316
|
-
function
|
|
324
|
+
function stageOrder(name) {
|
|
317
325
|
switch (name) {
|
|
318
|
-
case "source_prepare": return
|
|
319
|
-
case "episode_plan": return
|
|
320
|
-
case "
|
|
321
|
-
case "
|
|
322
|
-
case "batch_extract": return
|
|
323
|
-
case "episode_merge": return
|
|
324
|
-
case "episode_synopsis": return
|
|
326
|
+
case "source_prepare": return 0;
|
|
327
|
+
case "episode_plan": return 1;
|
|
328
|
+
case "episode_titles": return 2;
|
|
329
|
+
case "batch_plan": return 3;
|
|
330
|
+
case "batch_extract": return 4;
|
|
331
|
+
case "episode_merge": return 5;
|
|
332
|
+
case "episode_synopsis": return 6;
|
|
325
333
|
case "script_merge": return 7;
|
|
326
|
-
case "asset_curation": return
|
|
327
|
-
case "metadata_extract": return
|
|
328
|
-
case "script_synopsis": return
|
|
329
|
-
case "validate": return
|
|
334
|
+
case "asset_curation": return 8;
|
|
335
|
+
case "metadata_extract": return 9;
|
|
336
|
+
case "script_synopsis": return 10;
|
|
337
|
+
case "validate": return 11;
|
|
330
338
|
}
|
|
331
339
|
}
|
|
332
|
-
function
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
340
|
+
function stageRatio(stage) {
|
|
341
|
+
if (stage.status === "success" || stage.status === "skipped")
|
|
342
|
+
return 1;
|
|
343
|
+
if (stage.total <= 0)
|
|
344
|
+
return 0;
|
|
345
|
+
return Math.max(0, Math.min(1, stage.completed / stage.total));
|
|
346
|
+
}
|
|
347
|
+
function stageCountText(stage) {
|
|
348
|
+
if (stage.status === "skipped")
|
|
349
|
+
return "-";
|
|
350
|
+
return `${stage.completed}/${stage.total}`;
|
|
341
351
|
}
|
|
342
352
|
function useProgressColor() {
|
|
343
353
|
return Boolean(process.stderr.isTTY) && process.env.NO_COLOR === undefined;
|
|
@@ -363,7 +373,7 @@ function bold(text) {
|
|
|
363
373
|
}
|
|
364
374
|
function progressBar(stage) {
|
|
365
375
|
const width = 24;
|
|
366
|
-
const ratio = stage
|
|
376
|
+
const ratio = stageRatio(stage);
|
|
367
377
|
const filled = Math.round(ratio * width);
|
|
368
378
|
const bar = `${"█".repeat(filled)}${"░".repeat(width - filled)}`;
|
|
369
379
|
return colorStatus(stage.status, bar);
|
|
@@ -387,13 +397,12 @@ function statusMark(stage) {
|
|
|
387
397
|
}
|
|
388
398
|
function renderProgressLine(stage) {
|
|
389
399
|
const status = colorStatus(stage.status, stage.status.padEnd(7));
|
|
390
|
-
const percent = `${stage.percent}%`;
|
|
391
400
|
const bits = [
|
|
392
401
|
statusMark(stage),
|
|
393
402
|
bold(stageLabel(stage.name)),
|
|
394
403
|
status,
|
|
395
404
|
progressBar(stage),
|
|
396
|
-
`${
|
|
405
|
+
`${stageCountText(stage)} ${stageUnit(stage.name)}`,
|
|
397
406
|
];
|
|
398
407
|
if (stage.error > 0)
|
|
399
408
|
bits.push(ansi(31, `error ${stage.error}`));
|
|
@@ -406,7 +415,7 @@ function renderProgressLine(stage) {
|
|
|
406
415
|
return bits.join(" ");
|
|
407
416
|
}
|
|
408
417
|
function renderStageDetail(stage) {
|
|
409
|
-
const counts = [
|
|
418
|
+
const counts = [stageCountText(stage)];
|
|
410
419
|
if (stage.error > 0)
|
|
411
420
|
counts.push(ansi(31, `error ${stage.error}`));
|
|
412
421
|
if (stage.warning > 0)
|
|
@@ -416,9 +425,8 @@ function renderStageDetail(stage) {
|
|
|
416
425
|
return ` ${dim("stage")} ${stageLabel(stage.name)} ${colorStatus(stage.status, statusText(stage.status))} ${counts.join(", ")}`;
|
|
417
426
|
}
|
|
418
427
|
function renderEpisodeDetail(episode) {
|
|
419
|
-
const percent = episode.batches_total > 0 ? Math.round((episode.batches_done / episode.batches_total) * 100) : 0;
|
|
420
428
|
const errors = episode.error_batches.length > 0 ? ansi(31, " has errors") : "";
|
|
421
|
-
return ` ${dim("episode")} ${episode.episode_id} ${colorStatus(episode.status, statusText(episode.status))} ${
|
|
429
|
+
return ` ${dim("episode")} ${episode.episode_id} ${colorStatus(episode.status, statusText(episode.status))} ${episode.batches_done}/${episode.batches_total}, story ${colorStatus(episode.synopsis_status, statusText(episode.synopsis_status))}${errors}`;
|
|
422
430
|
}
|
|
423
431
|
function renderBatchDetail(batch) {
|
|
424
432
|
const reason = batch.error_code || batch.error_type || batch.message;
|
|
@@ -452,11 +460,11 @@ function renderProgressFrame() {
|
|
|
452
460
|
}
|
|
453
461
|
function progressStageWithStatus(stage, status) {
|
|
454
462
|
if (status === "success")
|
|
455
|
-
return { ...stage, status, completed: stage.total,
|
|
463
|
+
return { ...stage, status, completed: stage.total, pending: 0, warning: 0, error: 0 };
|
|
456
464
|
if (status === "warning")
|
|
457
|
-
return { ...stage, status, completed: stage.total,
|
|
465
|
+
return { ...stage, status, completed: stage.total, pending: 0, warning: Math.max(1, stage.warning), error: 0 };
|
|
458
466
|
if (status === "skipped")
|
|
459
|
-
return { ...stage, status,
|
|
467
|
+
return { ...stage, status, completed: stage.total, pending: 0, warning: 0, error: 0 };
|
|
460
468
|
if (status === "error")
|
|
461
469
|
return { ...stage, status, error: Math.max(1, stage.error) };
|
|
462
470
|
return { ...stage, status };
|
|
@@ -534,7 +542,6 @@ function emitInitProgress(workspace, stageName, fallbackStatus = "running") {
|
|
|
534
542
|
status: fallbackStatus,
|
|
535
543
|
completed: fallbackStatus === "success" ? 1 : 0,
|
|
536
544
|
total: 1,
|
|
537
|
-
percent: fallbackStatus === "success" ? 100 : 0,
|
|
538
545
|
pending: fallbackStatus === "running" || fallbackStatus === "pending" ? 1 : 0,
|
|
539
546
|
warning: fallbackStatus === "warning" ? 1 : 0,
|
|
540
547
|
error: fallbackStatus === "error" ? 1 : 0,
|
|
@@ -556,11 +563,14 @@ function buildDirectStatusSnapshot(workspace) {
|
|
|
556
563
|
const batches = asList(batchPlan["batches"]);
|
|
557
564
|
const state = readRunState(workspace);
|
|
558
565
|
const rawRunningStage = strOf(state["init_stage"]);
|
|
559
|
-
const runningStage = rawRunningStage && rawRunningStage !== "complete" ? rawRunningStage : null;
|
|
566
|
+
const runningStage = rawRunningStage && rawRunningStage !== "complete" && isDirectStageName(rawRunningStage) ? rawRunningStage : null;
|
|
560
567
|
const runStatus = strOf(state["status"]);
|
|
561
568
|
const failedStage = runStatus === "init_failed" ? runningStage : null;
|
|
562
569
|
const batchResultsDir = path.join(dd, "batch_results");
|
|
563
570
|
const episodeResultsDir = path.join(dd, "episode_results");
|
|
571
|
+
const trustEpisodeMergeArtifacts = canTrustStageArtifacts("episode_merge", runningStage, runStatus);
|
|
572
|
+
const trustEpisodeSynopsisArtifacts = canTrustStageArtifacts("episode_synopsis", runningStage, runStatus);
|
|
573
|
+
const trustScriptSynopsisArtifacts = canTrustStageArtifacts("script_synopsis", runningStage, runStatus);
|
|
564
574
|
const batchesByEpisode = new Map();
|
|
565
575
|
const batchDetails = [];
|
|
566
576
|
let doneBatches = 0;
|
|
@@ -601,7 +611,9 @@ function buildDirectStatusSnapshot(workspace) {
|
|
|
601
611
|
batchesByEpisode.get(detail.episode).push(detail);
|
|
602
612
|
}
|
|
603
613
|
const pendingBatches = batches.length - doneBatches - errorBatches;
|
|
604
|
-
const degradedSynopsisEpisodes =
|
|
614
|
+
const degradedSynopsisEpisodes = trustEpisodeSynopsisArtifacts
|
|
615
|
+
? new Set(asList(state["summary_degraded_episodes"]).map((n) => Number(n)))
|
|
616
|
+
: new Set();
|
|
605
617
|
const episodeSnapshots = [];
|
|
606
618
|
let completeEpisodes = 0;
|
|
607
619
|
let episodeResults = 0;
|
|
@@ -613,10 +625,10 @@ function buildDirectStatusSnapshot(workspace) {
|
|
|
613
625
|
const batchesError = epBatches.filter((b) => b.status === "error").length;
|
|
614
626
|
const batchesPending = epBatches.length - batchesDone - batchesError;
|
|
615
627
|
const epResultPath = episodeResultPath(episodeResultsDir, episode);
|
|
616
|
-
const hasEpisodeResult = readableJsonFile(epResultPath);
|
|
628
|
+
const hasEpisodeResult = trustEpisodeMergeArtifacts && readableJsonFile(epResultPath);
|
|
617
629
|
if (hasEpisodeResult)
|
|
618
630
|
episodeResults++;
|
|
619
|
-
const synopsis = readEpisodeSynopsis(episodeResultsDir, episode);
|
|
631
|
+
const synopsis = trustEpisodeSynopsisArtifacts ? readEpisodeSynopsis(episodeResultsDir, episode) : null;
|
|
620
632
|
if (synopsis)
|
|
621
633
|
episodeSynopses++;
|
|
622
634
|
const status = batchesError > 0
|
|
@@ -694,22 +706,23 @@ function buildDirectStatusSnapshot(workspace) {
|
|
|
694
706
|
...stageSnapshot("episode_synopsis", episodeSynopsisStatus, episodeSynopses, episodes.length),
|
|
695
707
|
warning: synopsisWarning,
|
|
696
708
|
};
|
|
697
|
-
const scriptStage =
|
|
698
|
-
const curationStage =
|
|
699
|
-
const metadataStage =
|
|
709
|
+
const scriptStage = orderedFileStage("script_merge", path.join(dd, "script.initial.json"), runningStage, runStatus);
|
|
710
|
+
const curationStage = orderedFileStage("asset_curation", path.join(dd, "asset_curation.json"), runningStage, runStatus);
|
|
711
|
+
const metadataStage = orderedFileStage("metadata_extract", path.join(dd, "asset_metadata.json"), runningStage, runStatus);
|
|
700
712
|
const synopsisPath = path.join(dd, "synopsis.json");
|
|
701
713
|
const summaryLine = formatSummaryStatusLine(Boolean(state["summary_skipped"]), Boolean(state["summary_degraded"]), asList(state["summary_degraded_episodes"]).map((n) => Number(n)));
|
|
702
|
-
const
|
|
714
|
+
const hasScriptSynopsis = trustScriptSynopsisArtifacts && exists(synopsisPath);
|
|
715
|
+
const scriptSynopsisStatus = hasScriptSynopsis
|
|
703
716
|
? Boolean(state["summary_degraded"]) ? "warning" : "success"
|
|
704
|
-
: Boolean(state["summary_skipped"])
|
|
717
|
+
: trustScriptSynopsisArtifacts && Boolean(state["summary_skipped"])
|
|
705
718
|
? "skipped"
|
|
706
719
|
: runningStage === "script_synopsis"
|
|
707
720
|
? "running"
|
|
708
721
|
: "pending";
|
|
709
|
-
const scriptSynopsisStage = stageSnapshot("script_synopsis", scriptSynopsisStatus,
|
|
722
|
+
const scriptSynopsisStage = stageSnapshot("script_synopsis", scriptSynopsisStatus, hasScriptSynopsis ? 1 : 0, 1, summaryLine ?? "");
|
|
710
723
|
const validationPath = path.join(dd, "validation.json");
|
|
711
|
-
let validationStage =
|
|
712
|
-
if (exists(validationPath)) {
|
|
724
|
+
let validationStage = orderedFileStage("validate", validationPath, runningStage, runStatus);
|
|
725
|
+
if (exists(validationPath) && !(runStatus === "init_running" && runningStage && stageOrder("validate") > stageOrder(runningStage))) {
|
|
713
726
|
const validation = readJson(validationPath);
|
|
714
727
|
validationStage = stageSnapshot("validate", Boolean(validation["passed"]) ? "success" : "warning", 1, 1);
|
|
715
728
|
}
|
|
@@ -740,7 +753,6 @@ function buildDirectStatusSnapshot(workspace) {
|
|
|
740
753
|
return {
|
|
741
754
|
overall_status: overallStatus,
|
|
742
755
|
running_stage: runningStage,
|
|
743
|
-
progress_percent: snapshotProgressPercent(stages),
|
|
744
756
|
stages,
|
|
745
757
|
episodes: episodeSnapshots,
|
|
746
758
|
batches: batchDetails,
|
|
@@ -750,18 +762,17 @@ function renderDirectStatusResult(snapshot) {
|
|
|
750
762
|
const completedEpisodes = snapshot.episodes.filter((episode) => episode.status === "success").length;
|
|
751
763
|
const result = [
|
|
752
764
|
"Overall",
|
|
753
|
-
`overall: ${statusText(snapshot.overall_status)}; current: ${snapshot.running_stage && isDirectStageName(snapshot.running_stage) ? stageLabel(snapshot.running_stage) : "-"}
|
|
754
|
-
snapshot.episodes.length > 0 ? `episodes: ${
|
|
765
|
+
`overall: ${statusText(snapshot.overall_status)}; current: ${snapshot.running_stage && isDirectStageName(snapshot.running_stage) ? stageLabel(snapshot.running_stage) : "-"}`,
|
|
766
|
+
snapshot.episodes.length > 0 ? `episodes: ${completedEpisodes}/${snapshot.episodes.length} 完成` : "episodes: 等待中",
|
|
755
767
|
"Stages",
|
|
756
768
|
];
|
|
757
769
|
for (const stage of userStageRows(snapshot.stages)) {
|
|
758
|
-
result.push(`${stageLabel(stage.name)}: ${statusText(stage.status)} ${stage
|
|
770
|
+
result.push(`${stageLabel(stage.name)}: ${statusText(stage.status)} ${stageCountText(stage)}${stage.error ? `, error ${stage.error}` : ""}${stage.warning ? `, warning ${stage.warning}` : ""}${stage.status === "pending" ? ", 等待中" : ""}${stage.message ? ` - ${stage.message}` : ""}`);
|
|
759
771
|
}
|
|
760
772
|
result.push("Episodes");
|
|
761
773
|
for (const episode of snapshot.episodes) {
|
|
762
|
-
const percent = episode.batches_total > 0 ? Math.round((episode.batches_done / episode.batches_total) * 100) : 0;
|
|
763
774
|
const errors = episode.error_batches.length > 0 ? `, error ids: ${episode.error_batches.join(",")}` : "";
|
|
764
|
-
result.push(`${episode.episode_id}: ${statusText(episode.status)} ${
|
|
775
|
+
result.push(`${episode.episode_id}: ${statusText(episode.status)} ${episode.batches_done}/${episode.batches_total}, story ${statusText(episode.synopsis_status)}${errors}`);
|
|
765
776
|
}
|
|
766
777
|
result.push("Details");
|
|
767
778
|
const noisyBatches = snapshot.batches.filter((batch) => batch.status !== "success");
|
|
@@ -1624,7 +1635,7 @@ async function commandInitImpl(opts) {
|
|
|
1624
1635
|
result: [
|
|
1625
1636
|
`episodes: ${results.length}/${totalEpisodes} complete`,
|
|
1626
1637
|
`incomplete episodes: ${incompleteEpisodes.join(", ")}`,
|
|
1627
|
-
`content
|
|
1638
|
+
`content: ${doneBatches}/${totalBatches} done, ${erroredBatches} error`,
|
|
1628
1639
|
...(titleDegraded.length > 0 ? [`titles degraded (deterministic): ${titleDegraded.join(", ")}`] : []),
|
|
1629
1640
|
`provider: ${providerName}`,
|
|
1630
1641
|
],
|
|
@@ -1656,9 +1667,14 @@ async function commandInitImpl(opts) {
|
|
|
1656
1667
|
last_error: null,
|
|
1657
1668
|
});
|
|
1658
1669
|
let script;
|
|
1670
|
+
const assetNormalizationPath = path.join(dd, "asset_normalization.json");
|
|
1671
|
+
let assetNormalization = null;
|
|
1659
1672
|
try {
|
|
1660
1673
|
emitInitProgress(workspace, "script_merge");
|
|
1661
|
-
|
|
1674
|
+
const merged = mergeEpisodeResultsWithReport(results, strOf(info["projectName"]) || path.basename(source, path.extname(source)));
|
|
1675
|
+
script = merged.script;
|
|
1676
|
+
assetNormalization = merged.assetNormalization;
|
|
1677
|
+
writeJson(assetNormalizationPath, assetNormalization);
|
|
1662
1678
|
}
|
|
1663
1679
|
catch (exc) {
|
|
1664
1680
|
const e = exc;
|
|
@@ -1815,6 +1831,12 @@ async function commandInitImpl(opts) {
|
|
|
1815
1831
|
: "INIT NEEDS AGENT: Initial script written with repair issues";
|
|
1816
1832
|
const stats = validation["stats"] ?? {};
|
|
1817
1833
|
const summaryLine = formatSummaryStatusLine(skipSummary, summaryDegraded, summaryDegradedEpisodes);
|
|
1834
|
+
const normalizationSummary = assetNormalization?.summary ?? {
|
|
1835
|
+
asset_merges: 0,
|
|
1836
|
+
location_states_from_names: 0,
|
|
1837
|
+
location_states_from_time: 0,
|
|
1838
|
+
review_items: 0,
|
|
1839
|
+
};
|
|
1818
1840
|
const report = {
|
|
1819
1841
|
title,
|
|
1820
1842
|
result: [
|
|
@@ -1824,7 +1846,8 @@ async function commandInitImpl(opts) {
|
|
|
1824
1846
|
`validation: ${passed ? "passed" : "needs repair"}`,
|
|
1825
1847
|
`provider: ${providerName}`,
|
|
1826
1848
|
`episodes: ${results.length}/${totalEpisodes} complete`,
|
|
1827
|
-
`content
|
|
1849
|
+
`content: ${doneBatches}/${totalBatches} done`,
|
|
1850
|
+
`normalization: merges=${normalizationSummary.asset_merges}, states=${normalizationSummary.location_states_from_names + normalizationSummary.location_states_from_time}, review=${normalizationSummary.review_items}`,
|
|
1828
1851
|
...(titleDegraded.length > 0 ? [`titles degraded (deterministic): ${titleDegraded.join(", ")}`] : []),
|
|
1829
1852
|
...(summaryLine ? [summaryLine] : []),
|
|
1830
1853
|
],
|
|
@@ -1835,6 +1858,7 @@ async function commandInitImpl(opts) {
|
|
|
1835
1858
|
path.join(dd, "batch_plan.json"),
|
|
1836
1859
|
batchResultsDir,
|
|
1837
1860
|
episodeResultsDir,
|
|
1861
|
+
assetNormalizationPath,
|
|
1838
1862
|
path.join(dd, "asset_curation.json"),
|
|
1839
1863
|
path.join(dd, "asset_metadata.json"),
|
|
1840
1864
|
path.join(dd, "synopsis.json"),
|
|
@@ -1883,7 +1907,6 @@ export function commandStatus(opts) {
|
|
|
1883
1907
|
artifacts: [path.join(dd, "batch_results"), path.join(dd, "episode_results"), path.join(dd, "run_state.json")],
|
|
1884
1908
|
overall_status: snapshot.overall_status,
|
|
1885
1909
|
running_stage: snapshot.running_stage,
|
|
1886
|
-
progress_percent: snapshot.progress_percent,
|
|
1887
1910
|
stages: snapshot.stages,
|
|
1888
1911
|
episodes: snapshot.episodes,
|
|
1889
1912
|
batches: snapshot.batches,
|
|
@@ -2114,6 +2137,24 @@ function renderAssetCurationSummary(curation) {
|
|
|
2114
2137
|
}
|
|
2115
2138
|
return lines;
|
|
2116
2139
|
}
|
|
2140
|
+
function renderAssetNormalizationSummary(normalization) {
|
|
2141
|
+
const summary = isDict(normalization["summary"]) ? normalization["summary"] : {};
|
|
2142
|
+
const lines = [
|
|
2143
|
+
`normalization: ` +
|
|
2144
|
+
`merges ${summary["asset_merges"] ?? 0}, ` +
|
|
2145
|
+
`location-states ${(Number(summary["location_states_from_names"] ?? 0) + Number(summary["location_states_from_time"] ?? 0))}, ` +
|
|
2146
|
+
`review ${summary["review_items"] ?? 0}`,
|
|
2147
|
+
];
|
|
2148
|
+
for (const decision of asList(normalization["decisions"])) {
|
|
2149
|
+
if (!isDict(decision))
|
|
2150
|
+
continue;
|
|
2151
|
+
const action = strOf(decision["action"]);
|
|
2152
|
+
if (action !== "canonical_merge" && action !== "location_state_from_name" && action !== "location_state_from_time")
|
|
2153
|
+
continue;
|
|
2154
|
+
lines.push(`normalization ${action}: ${decision["from"] || "-"} -> ${decision["into"] || "-"}${decision["state"] ? ` state=${decision["state"]}` : ""} — ${decision["reason"] || "-"}`);
|
|
2155
|
+
}
|
|
2156
|
+
return lines;
|
|
2157
|
+
}
|
|
2117
2158
|
// Collect batch extraction failures (batch_results/<key>.error.json) keyed by
|
|
2118
2159
|
// episode. Shared by both review views — these are direct-stage failures the
|
|
2119
2160
|
// final-script `issues` query can't see.
|
|
@@ -2208,6 +2249,12 @@ export function commandReview(opts) {
|
|
|
2208
2249
|
// (validation issues recomputed live, plus batch extraction failures).
|
|
2209
2250
|
const lines = [];
|
|
2210
2251
|
const curationPath = path.join(dd, "asset_curation.json");
|
|
2252
|
+
const normalizationPath = path.join(dd, "asset_normalization.json");
|
|
2253
|
+
if (exists(normalizationPath)) {
|
|
2254
|
+
const normalization = readJson(normalizationPath);
|
|
2255
|
+
if (isDict(normalization))
|
|
2256
|
+
lines.push(...renderAssetNormalizationSummary(normalization));
|
|
2257
|
+
}
|
|
2211
2258
|
if (exists(curationPath)) {
|
|
2212
2259
|
const curation = readJson(curationPath);
|
|
2213
2260
|
if (isDict(curation))
|
|
@@ -2236,7 +2283,7 @@ export function commandReview(opts) {
|
|
|
2236
2283
|
const report = {
|
|
2237
2284
|
title: "DIRECT REVIEW",
|
|
2238
2285
|
result: lines.length > 0 ? lines : ["No issues, curation decisions, or batch failures to review."],
|
|
2239
|
-
artifacts: [scriptPath, path.join(dd, "asset_curation.json"), batchResultsDir],
|
|
2286
|
+
artifacts: [scriptPath, normalizationPath, path.join(dd, "asset_curation.json"), batchResultsDir],
|
|
2240
2287
|
next: [
|
|
2241
2288
|
"Run `direct review --episode <n>` for source↔extract alignment.",
|
|
2242
2289
|
"Edit with `--draft` (e.g. `scriptctl rename … --draft`), then validate/export.",
|