@lingjingai/scriptctl 0.28.0 → 0.28.2
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/0.28.2.md +13 -0
- package/changes/unreleased.md +2 -3
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/common.js +45 -7
- 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 +4 -6
- package/dist/help-text.js.map +1 -1
- package/dist/usecases/direct.js +151 -88
- 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,15 @@ 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 trustMetadataArtifacts = canTrustStageArtifacts("metadata_extract", runningStage, runStatus);
|
|
574
|
+
const trustScriptSynopsisArtifacts = canTrustStageArtifacts("script_synopsis", runningStage, runStatus);
|
|
564
575
|
const batchesByEpisode = new Map();
|
|
565
576
|
const batchDetails = [];
|
|
566
577
|
let doneBatches = 0;
|
|
@@ -601,7 +612,9 @@ function buildDirectStatusSnapshot(workspace) {
|
|
|
601
612
|
batchesByEpisode.get(detail.episode).push(detail);
|
|
602
613
|
}
|
|
603
614
|
const pendingBatches = batches.length - doneBatches - errorBatches;
|
|
604
|
-
const degradedSynopsisEpisodes =
|
|
615
|
+
const degradedSynopsisEpisodes = trustEpisodeSynopsisArtifacts
|
|
616
|
+
? new Set(asList(state["summary_degraded_episodes"]).map((n) => Number(n)))
|
|
617
|
+
: new Set();
|
|
605
618
|
const episodeSnapshots = [];
|
|
606
619
|
let completeEpisodes = 0;
|
|
607
620
|
let episodeResults = 0;
|
|
@@ -613,10 +626,10 @@ function buildDirectStatusSnapshot(workspace) {
|
|
|
613
626
|
const batchesError = epBatches.filter((b) => b.status === "error").length;
|
|
614
627
|
const batchesPending = epBatches.length - batchesDone - batchesError;
|
|
615
628
|
const epResultPath = episodeResultPath(episodeResultsDir, episode);
|
|
616
|
-
const hasEpisodeResult = readableJsonFile(epResultPath);
|
|
629
|
+
const hasEpisodeResult = trustEpisodeMergeArtifacts && readableJsonFile(epResultPath);
|
|
617
630
|
if (hasEpisodeResult)
|
|
618
631
|
episodeResults++;
|
|
619
|
-
const synopsis = readEpisodeSynopsis(episodeResultsDir, episode);
|
|
632
|
+
const synopsis = trustEpisodeSynopsisArtifacts ? readEpisodeSynopsis(episodeResultsDir, episode) : null;
|
|
620
633
|
if (synopsis)
|
|
621
634
|
episodeSynopses++;
|
|
622
635
|
const status = batchesError > 0
|
|
@@ -694,22 +707,25 @@ function buildDirectStatusSnapshot(workspace) {
|
|
|
694
707
|
...stageSnapshot("episode_synopsis", episodeSynopsisStatus, episodeSynopses, episodes.length),
|
|
695
708
|
warning: synopsisWarning,
|
|
696
709
|
};
|
|
697
|
-
const scriptStage =
|
|
698
|
-
const curationStage =
|
|
699
|
-
const metadataStage =
|
|
710
|
+
const scriptStage = orderedFileStage("script_merge", path.join(dd, "script.initial.json"), runningStage, runStatus);
|
|
711
|
+
const curationStage = orderedFileStage("asset_curation", path.join(dd, "asset_curation.json"), runningStage, runStatus);
|
|
712
|
+
const metadataStage = Boolean(state["metadata_skipped"]) && trustMetadataArtifacts
|
|
713
|
+
? stageSnapshot("metadata_extract", "skipped", 0, 1, "metadata: skipped (--skip-metadata)")
|
|
714
|
+
: orderedFileStage("metadata_extract", path.join(dd, "asset_metadata.json"), runningStage, runStatus);
|
|
700
715
|
const synopsisPath = path.join(dd, "synopsis.json");
|
|
701
716
|
const summaryLine = formatSummaryStatusLine(Boolean(state["summary_skipped"]), Boolean(state["summary_degraded"]), asList(state["summary_degraded_episodes"]).map((n) => Number(n)));
|
|
702
|
-
const
|
|
717
|
+
const hasScriptSynopsis = trustScriptSynopsisArtifacts && exists(synopsisPath);
|
|
718
|
+
const scriptSynopsisStatus = hasScriptSynopsis
|
|
703
719
|
? Boolean(state["summary_degraded"]) ? "warning" : "success"
|
|
704
|
-
: Boolean(state["summary_skipped"])
|
|
720
|
+
: trustScriptSynopsisArtifacts && Boolean(state["summary_skipped"])
|
|
705
721
|
? "skipped"
|
|
706
722
|
: runningStage === "script_synopsis"
|
|
707
723
|
? "running"
|
|
708
724
|
: "pending";
|
|
709
|
-
const scriptSynopsisStage = stageSnapshot("script_synopsis", scriptSynopsisStatus,
|
|
725
|
+
const scriptSynopsisStage = stageSnapshot("script_synopsis", scriptSynopsisStatus, hasScriptSynopsis ? 1 : 0, 1, summaryLine ?? "");
|
|
710
726
|
const validationPath = path.join(dd, "validation.json");
|
|
711
|
-
let validationStage =
|
|
712
|
-
if (exists(validationPath)) {
|
|
727
|
+
let validationStage = orderedFileStage("validate", validationPath, runningStage, runStatus);
|
|
728
|
+
if (exists(validationPath) && !(runStatus === "init_running" && runningStage && stageOrder("validate") > stageOrder(runningStage))) {
|
|
713
729
|
const validation = readJson(validationPath);
|
|
714
730
|
validationStage = stageSnapshot("validate", Boolean(validation["passed"]) ? "success" : "warning", 1, 1);
|
|
715
731
|
}
|
|
@@ -735,12 +751,11 @@ function buildDirectStatusSnapshot(workspace) {
|
|
|
735
751
|
overallStatus = "running";
|
|
736
752
|
else if (maxStage === "warning")
|
|
737
753
|
overallStatus = "warning";
|
|
738
|
-
else if (stages.filter((s) => s.name !== "script_synopsis").every((s) => s.status === "success" || s.status === "warning"))
|
|
754
|
+
else if (stages.filter((s) => s.name !== "script_synopsis").every((s) => s.status === "success" || s.status === "warning" || s.status === "skipped"))
|
|
739
755
|
overallStatus = "success";
|
|
740
756
|
return {
|
|
741
757
|
overall_status: overallStatus,
|
|
742
758
|
running_stage: runningStage,
|
|
743
|
-
progress_percent: snapshotProgressPercent(stages),
|
|
744
759
|
stages,
|
|
745
760
|
episodes: episodeSnapshots,
|
|
746
761
|
batches: batchDetails,
|
|
@@ -750,18 +765,17 @@ function renderDirectStatusResult(snapshot) {
|
|
|
750
765
|
const completedEpisodes = snapshot.episodes.filter((episode) => episode.status === "success").length;
|
|
751
766
|
const result = [
|
|
752
767
|
"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: ${
|
|
768
|
+
`overall: ${statusText(snapshot.overall_status)}; current: ${snapshot.running_stage && isDirectStageName(snapshot.running_stage) ? stageLabel(snapshot.running_stage) : "-"}`,
|
|
769
|
+
snapshot.episodes.length > 0 ? `episodes: ${completedEpisodes}/${snapshot.episodes.length} 完成` : "episodes: 等待中",
|
|
755
770
|
"Stages",
|
|
756
771
|
];
|
|
757
772
|
for (const stage of userStageRows(snapshot.stages)) {
|
|
758
|
-
result.push(`${stageLabel(stage.name)}: ${statusText(stage.status)} ${stage
|
|
773
|
+
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
774
|
}
|
|
760
775
|
result.push("Episodes");
|
|
761
776
|
for (const episode of snapshot.episodes) {
|
|
762
|
-
const percent = episode.batches_total > 0 ? Math.round((episode.batches_done / episode.batches_total) * 100) : 0;
|
|
763
777
|
const errors = episode.error_batches.length > 0 ? `, error ids: ${episode.error_batches.join(",")}` : "";
|
|
764
|
-
result.push(`${episode.episode_id}: ${statusText(episode.status)} ${
|
|
778
|
+
result.push(`${episode.episode_id}: ${statusText(episode.status)} ${episode.batches_done}/${episode.batches_total}, story ${statusText(episode.synopsis_status)}${errors}`);
|
|
765
779
|
}
|
|
766
780
|
result.push("Details");
|
|
767
781
|
const noisyBatches = snapshot.batches.filter((batch) => batch.status !== "success");
|
|
@@ -1288,6 +1302,7 @@ async function commandInitImpl(opts) {
|
|
|
1288
1302
|
// --resummarize forces the summary LLM to re-run even when structure is
|
|
1289
1303
|
// reused (e.g. after a prompt change).
|
|
1290
1304
|
const skipSummary = Boolean(opts["skip_summary"]);
|
|
1305
|
+
const skipMetadata = Boolean(opts["skip_metadata"]);
|
|
1291
1306
|
const resummarize = Boolean(opts["resummarize"]);
|
|
1292
1307
|
const summaryThresholdChars = parsePositiveIntOption(opts["summary_threshold_chars"], "--summary-threshold-chars", "USAGE ERROR: Invalid summary threshold", DEFAULT_SUMMARY_REDUCE_THRESHOLD_CHARS);
|
|
1293
1308
|
const concurrency = parsePositiveIntOption(opts["concurrency"], "--concurrency", "USAGE ERROR: Invalid concurrency", DEFAULT_CONCURRENCY);
|
|
@@ -1339,6 +1354,7 @@ async function commandInitImpl(opts) {
|
|
|
1339
1354
|
model,
|
|
1340
1355
|
concurrency,
|
|
1341
1356
|
source_path: path.resolve(source),
|
|
1357
|
+
metadata_skipped: skipMetadata,
|
|
1342
1358
|
});
|
|
1343
1359
|
emitInitProgress(workspace, "source_prepare");
|
|
1344
1360
|
let info;
|
|
@@ -1624,7 +1640,7 @@ async function commandInitImpl(opts) {
|
|
|
1624
1640
|
result: [
|
|
1625
1641
|
`episodes: ${results.length}/${totalEpisodes} complete`,
|
|
1626
1642
|
`incomplete episodes: ${incompleteEpisodes.join(", ")}`,
|
|
1627
|
-
`content
|
|
1643
|
+
`content: ${doneBatches}/${totalBatches} done, ${erroredBatches} error`,
|
|
1628
1644
|
...(titleDegraded.length > 0 ? [`titles degraded (deterministic): ${titleDegraded.join(", ")}`] : []),
|
|
1629
1645
|
`provider: ${providerName}`,
|
|
1630
1646
|
],
|
|
@@ -1656,9 +1672,15 @@ async function commandInitImpl(opts) {
|
|
|
1656
1672
|
last_error: null,
|
|
1657
1673
|
});
|
|
1658
1674
|
let script;
|
|
1675
|
+
const assetNormalizationPath = path.join(dd, "asset_normalization.json");
|
|
1676
|
+
const assetMetadataPath = path.join(dd, "asset_metadata.json");
|
|
1677
|
+
let assetNormalization = null;
|
|
1659
1678
|
try {
|
|
1660
1679
|
emitInitProgress(workspace, "script_merge");
|
|
1661
|
-
|
|
1680
|
+
const merged = mergeEpisodeResultsWithReport(results, strOf(info["projectName"]) || path.basename(source, path.extname(source)));
|
|
1681
|
+
script = merged.script;
|
|
1682
|
+
assetNormalization = merged.assetNormalization;
|
|
1683
|
+
writeJson(assetNormalizationPath, assetNormalization);
|
|
1662
1684
|
}
|
|
1663
1685
|
catch (exc) {
|
|
1664
1686
|
const e = exc;
|
|
@@ -1695,39 +1717,47 @@ async function commandInitImpl(opts) {
|
|
|
1695
1717
|
});
|
|
1696
1718
|
}
|
|
1697
1719
|
emitInitProgress(workspace, "asset_curation", "success");
|
|
1698
|
-
|
|
1699
|
-
updateRunState(workspace, { status: "init_running", init_stage: "metadata_extract" });
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
metadata = {};
|
|
1704
|
-
writeJson(path.join(dd, "asset_metadata.json"), metadata);
|
|
1705
|
-
applyMetadataToScript(script, metadata);
|
|
1720
|
+
if (skipMetadata) {
|
|
1721
|
+
updateRunState(workspace, { status: "init_running", init_stage: "metadata_extract", metadata_skipped: true });
|
|
1722
|
+
if (exists(assetMetadataPath))
|
|
1723
|
+
deletePath(assetMetadataPath);
|
|
1724
|
+
emitInitProgress(workspace, "metadata_extract", "skipped");
|
|
1706
1725
|
}
|
|
1707
|
-
|
|
1708
|
-
|
|
1726
|
+
else {
|
|
1727
|
+
try {
|
|
1728
|
+
updateRunState(workspace, { status: "init_running", init_stage: "metadata_extract", metadata_skipped: false });
|
|
1729
|
+
emitInitProgress(workspace, "metadata_extract");
|
|
1730
|
+
let metadata = provider.extractMetadata ? await provider.extractMetadata(sourceText, script) : {};
|
|
1731
|
+
if (!isDict(metadata))
|
|
1732
|
+
metadata = {};
|
|
1733
|
+
writeJson(assetMetadataPath, metadata);
|
|
1734
|
+
applyMetadataToScript(script, metadata);
|
|
1735
|
+
}
|
|
1736
|
+
catch (exc) {
|
|
1737
|
+
if (exc instanceof CliError) {
|
|
1738
|
+
throw initFailedReport(workspace, {
|
|
1739
|
+
title: exc.title,
|
|
1740
|
+
stage: "metadata_extract",
|
|
1741
|
+
exitCode: exc.exitCode,
|
|
1742
|
+
errorCode: exc.errorCode,
|
|
1743
|
+
required: exc.required.length > 0 ? exc.required : ["metadata JSON matching final script contract"],
|
|
1744
|
+
received: exc.received.length > 0 ? exc.received : [String(exc.message).slice(0, 160)],
|
|
1745
|
+
nextSteps: exc.nextSteps.length > 0 ? exc.nextSteps : ["Rerun init; extraction checkpoints will be reused and metadata will retry."],
|
|
1746
|
+
updates: { episode_completed: results.length },
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
const e = exc;
|
|
1709
1750
|
throw initFailedReport(workspace, {
|
|
1710
|
-
title:
|
|
1751
|
+
title: "INIT FAILED: Metadata extraction failed",
|
|
1711
1752
|
stage: "metadata_extract",
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
received: exc.received.length > 0 ? exc.received : [String(exc.message).slice(0, 160)],
|
|
1716
|
-
nextSteps: exc.nextSteps.length > 0 ? exc.nextSteps : ["Rerun init; extraction checkpoints will be reused and metadata will retry."],
|
|
1753
|
+
required: ["provider metadata for worldview, role_type, and asset descriptions"],
|
|
1754
|
+
received: [`${e?.name ?? "Error"}: ${(e?.message ?? "").slice(0, 160)}`],
|
|
1755
|
+
nextSteps: ["Rerun init; extraction checkpoints will be reused and metadata will retry."],
|
|
1717
1756
|
updates: { episode_completed: results.length },
|
|
1718
1757
|
});
|
|
1719
1758
|
}
|
|
1720
|
-
|
|
1721
|
-
throw initFailedReport(workspace, {
|
|
1722
|
-
title: "INIT FAILED: Metadata extraction failed",
|
|
1723
|
-
stage: "metadata_extract",
|
|
1724
|
-
required: ["provider metadata for worldview, role_type, and asset descriptions"],
|
|
1725
|
-
received: [`${e?.name ?? "Error"}: ${(e?.message ?? "").slice(0, 160)}`],
|
|
1726
|
-
nextSteps: ["Rerun init; extraction checkpoints will be reused and metadata will retry."],
|
|
1727
|
-
updates: { episode_completed: results.length },
|
|
1728
|
-
});
|
|
1759
|
+
emitInitProgress(workspace, "metadata_extract", "success");
|
|
1729
1760
|
}
|
|
1730
|
-
emitInitProgress(workspace, "metadata_extract", "success");
|
|
1731
1761
|
// Whole-script overview (reduce-2). Soft: a non-essential concept enhancement,
|
|
1732
1762
|
// the opposite of batch extraction's hard-fail policy — failures degrade the
|
|
1733
1763
|
// overview (recorded as summary_degraded + a sanitized reason) and NEVER block init.
|
|
@@ -1807,6 +1837,7 @@ async function commandInitImpl(opts) {
|
|
|
1807
1837
|
summary_degraded: summaryDegraded,
|
|
1808
1838
|
summary_degraded_episodes: summaryDegradedEpisodes,
|
|
1809
1839
|
summary_degraded_reason: summaryDegradeReason || null,
|
|
1840
|
+
metadata_skipped: skipMetadata,
|
|
1810
1841
|
last_error: null,
|
|
1811
1842
|
});
|
|
1812
1843
|
emitInitProgress(workspace, "validate", passed ? "success" : "warning");
|
|
@@ -1815,6 +1846,12 @@ async function commandInitImpl(opts) {
|
|
|
1815
1846
|
: "INIT NEEDS AGENT: Initial script written with repair issues";
|
|
1816
1847
|
const stats = validation["stats"] ?? {};
|
|
1817
1848
|
const summaryLine = formatSummaryStatusLine(skipSummary, summaryDegraded, summaryDegradedEpisodes);
|
|
1849
|
+
const normalizationSummary = assetNormalization?.summary ?? {
|
|
1850
|
+
asset_merges: 0,
|
|
1851
|
+
location_states_from_names: 0,
|
|
1852
|
+
location_states_from_time: 0,
|
|
1853
|
+
review_items: 0,
|
|
1854
|
+
};
|
|
1818
1855
|
const report = {
|
|
1819
1856
|
title,
|
|
1820
1857
|
result: [
|
|
@@ -1824,7 +1861,9 @@ async function commandInitImpl(opts) {
|
|
|
1824
1861
|
`validation: ${passed ? "passed" : "needs repair"}`,
|
|
1825
1862
|
`provider: ${providerName}`,
|
|
1826
1863
|
`episodes: ${results.length}/${totalEpisodes} complete`,
|
|
1827
|
-
`content
|
|
1864
|
+
`content: ${doneBatches}/${totalBatches} done`,
|
|
1865
|
+
`normalization: merges=${normalizationSummary.asset_merges}, states=${normalizationSummary.location_states_from_names + normalizationSummary.location_states_from_time}, review=${normalizationSummary.review_items}`,
|
|
1866
|
+
...(skipMetadata ? ["metadata: skipped"] : []),
|
|
1828
1867
|
...(titleDegraded.length > 0 ? [`titles degraded (deterministic): ${titleDegraded.join(", ")}`] : []),
|
|
1829
1868
|
...(summaryLine ? [summaryLine] : []),
|
|
1830
1869
|
],
|
|
@@ -1835,8 +1874,9 @@ async function commandInitImpl(opts) {
|
|
|
1835
1874
|
path.join(dd, "batch_plan.json"),
|
|
1836
1875
|
batchResultsDir,
|
|
1837
1876
|
episodeResultsDir,
|
|
1877
|
+
assetNormalizationPath,
|
|
1838
1878
|
path.join(dd, "asset_curation.json"),
|
|
1839
|
-
|
|
1879
|
+
...(skipMetadata ? [] : [assetMetadataPath]),
|
|
1840
1880
|
path.join(dd, "synopsis.json"),
|
|
1841
1881
|
scriptPath,
|
|
1842
1882
|
path.join(dd, "validation.json"),
|
|
@@ -1883,7 +1923,6 @@ export function commandStatus(opts) {
|
|
|
1883
1923
|
artifacts: [path.join(dd, "batch_results"), path.join(dd, "episode_results"), path.join(dd, "run_state.json")],
|
|
1884
1924
|
overall_status: snapshot.overall_status,
|
|
1885
1925
|
running_stage: snapshot.running_stage,
|
|
1886
|
-
progress_percent: snapshot.progress_percent,
|
|
1887
1926
|
stages: snapshot.stages,
|
|
1888
1927
|
episodes: snapshot.episodes,
|
|
1889
1928
|
batches: snapshot.batches,
|
|
@@ -2114,6 +2153,24 @@ function renderAssetCurationSummary(curation) {
|
|
|
2114
2153
|
}
|
|
2115
2154
|
return lines;
|
|
2116
2155
|
}
|
|
2156
|
+
function renderAssetNormalizationSummary(normalization) {
|
|
2157
|
+
const summary = isDict(normalization["summary"]) ? normalization["summary"] : {};
|
|
2158
|
+
const lines = [
|
|
2159
|
+
`normalization: ` +
|
|
2160
|
+
`merges ${summary["asset_merges"] ?? 0}, ` +
|
|
2161
|
+
`location-states ${(Number(summary["location_states_from_names"] ?? 0) + Number(summary["location_states_from_time"] ?? 0))}, ` +
|
|
2162
|
+
`review ${summary["review_items"] ?? 0}`,
|
|
2163
|
+
];
|
|
2164
|
+
for (const decision of asList(normalization["decisions"])) {
|
|
2165
|
+
if (!isDict(decision))
|
|
2166
|
+
continue;
|
|
2167
|
+
const action = strOf(decision["action"]);
|
|
2168
|
+
if (action !== "canonical_merge" && action !== "location_state_from_name" && action !== "location_state_from_time")
|
|
2169
|
+
continue;
|
|
2170
|
+
lines.push(`normalization ${action}: ${decision["from"] || "-"} -> ${decision["into"] || "-"}${decision["state"] ? ` state=${decision["state"]}` : ""} — ${decision["reason"] || "-"}`);
|
|
2171
|
+
}
|
|
2172
|
+
return lines;
|
|
2173
|
+
}
|
|
2117
2174
|
// Collect batch extraction failures (batch_results/<key>.error.json) keyed by
|
|
2118
2175
|
// episode. Shared by both review views — these are direct-stage failures the
|
|
2119
2176
|
// final-script `issues` query can't see.
|
|
@@ -2208,6 +2265,12 @@ export function commandReview(opts) {
|
|
|
2208
2265
|
// (validation issues recomputed live, plus batch extraction failures).
|
|
2209
2266
|
const lines = [];
|
|
2210
2267
|
const curationPath = path.join(dd, "asset_curation.json");
|
|
2268
|
+
const normalizationPath = path.join(dd, "asset_normalization.json");
|
|
2269
|
+
if (exists(normalizationPath)) {
|
|
2270
|
+
const normalization = readJson(normalizationPath);
|
|
2271
|
+
if (isDict(normalization))
|
|
2272
|
+
lines.push(...renderAssetNormalizationSummary(normalization));
|
|
2273
|
+
}
|
|
2211
2274
|
if (exists(curationPath)) {
|
|
2212
2275
|
const curation = readJson(curationPath);
|
|
2213
2276
|
if (isDict(curation))
|
|
@@ -2236,7 +2299,7 @@ export function commandReview(opts) {
|
|
|
2236
2299
|
const report = {
|
|
2237
2300
|
title: "DIRECT REVIEW",
|
|
2238
2301
|
result: lines.length > 0 ? lines : ["No issues, curation decisions, or batch failures to review."],
|
|
2239
|
-
artifacts: [scriptPath, path.join(dd, "asset_curation.json"), batchResultsDir],
|
|
2302
|
+
artifacts: [scriptPath, normalizationPath, path.join(dd, "asset_curation.json"), batchResultsDir],
|
|
2240
2303
|
next: [
|
|
2241
2304
|
"Run `direct review --episode <n>` for source↔extract alignment.",
|
|
2242
2305
|
"Edit with `--draft` (e.g. `scriptctl rename … --draft`), then validate/export.",
|