@lingjingai/scriptctl 0.9.8 → 0.10.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.
- package/dist/cli.js +11 -23
- package/dist/cli.js.map +1 -1
- package/dist/common.d.ts +0 -3
- package/dist/common.js +0 -7
- package/dist/common.js.map +1 -1
- package/dist/domain/direct-core.d.ts +1 -1
- package/dist/domain/script-core.js +4 -4
- package/dist/domain/script-core.js.map +1 -1
- package/dist/help-text.js +35 -94
- package/dist/help-text.js.map +1 -1
- package/dist/infra/default-writing-prompt.d.ts +1 -1
- package/dist/infra/default-writing-prompt.js +1 -1
- package/dist/usecases/direct.d.ts +0 -5
- package/dist/usecases/direct.js +12 -456
- package/dist/usecases/direct.js.map +1 -1
- package/dist/usecases/episode.js +2 -2
- package/dist/usecases/script.d.ts +1 -2
- package/dist/usecases/script.js +34 -144
- package/dist/usecases/script.js.map +1 -1
- package/package.json +2 -2
package/dist/usecases/direct.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { CliError, DEFAULT_BATCH_MAX_CHARS, DEFAULT_BATCH_MIN_LINES, DEFAULT_BATCH_MODE, DEFAULT_BATCH_TARGET_LINES, DEFAULT_CONCURRENCY, DEFAULT_MODEL, DEFAULT_PROVIDER, DIRECT_CONTRACT_VERSION, EXIT_INPUT, EXIT_NEEDS_AGENT, EXIT_OK, EXIT_RUNTIME, EXIT_USAGE,
|
|
3
|
+
import { CliError, DEFAULT_BATCH_MAX_CHARS, DEFAULT_BATCH_MIN_LINES, DEFAULT_BATCH_MODE, DEFAULT_BATCH_TARGET_LINES, DEFAULT_CONCURRENCY, DEFAULT_MODEL, DEFAULT_PROVIDER, DIRECT_CONTRACT_VERSION, EXIT_INPUT, EXIT_NEEDS_AGENT, EXIT_OK, EXIT_RUNTIME, EXIT_USAGE, SUPPORTED_EXTS, deletePath, deleteTree, directDir, exists, readJson, readText, sha256Text, writeJson, } from "../common.js";
|
|
4
4
|
import { compactBatchResult, compactEpisodeResult, buildBatchPlan, buildEpisodePlan, enrichEpisodePlanTitles, extractBatchWithRecovery, mergeEpisodeResults, normalizeEpisodeResult, normalizeInt, recoverBatchFromSource, uniqueAdd, validateBatchExtractionQuality, validateEpisodeExtractionQuality, _md_push_asset, curateScriptAssets, applyMetadataToScript, } from "../domain/direct-core.js";
|
|
5
5
|
import { validateScript } from "../domain/script-core.js";
|
|
6
6
|
import { makeProvider } from "../infra/providers.js";
|
|
@@ -66,38 +66,6 @@ function failureSignature(items) {
|
|
|
66
66
|
out.sort();
|
|
67
67
|
return out;
|
|
68
68
|
}
|
|
69
|
-
export function addInspectedTarget(workspace, target) {
|
|
70
|
-
const state = readRunState(workspace);
|
|
71
|
-
const targets = [];
|
|
72
|
-
for (const item of asList(state["inspected_targets"])) {
|
|
73
|
-
const s = strOf(item);
|
|
74
|
-
if (s)
|
|
75
|
-
targets.push(s);
|
|
76
|
-
}
|
|
77
|
-
if (!targets.includes(target))
|
|
78
|
-
targets.push(target);
|
|
79
|
-
const missing = [];
|
|
80
|
-
for (const t of REVIEW_TARGETS) {
|
|
81
|
-
if (!targets.includes(t))
|
|
82
|
-
missing.push(t);
|
|
83
|
-
}
|
|
84
|
-
const reviewStatus = missing.length === 0 ? "reviewed" : "in_progress";
|
|
85
|
-
return updateRunState(workspace, {
|
|
86
|
-
inspected_targets: targets,
|
|
87
|
-
review_status: reviewStatus,
|
|
88
|
-
review_missing: missing,
|
|
89
|
-
last_inspect_target: target,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
export function markPatched(workspace, count) {
|
|
93
|
-
const state = readRunState(workspace);
|
|
94
|
-
const patchCount = Number(state["patch_count"] ?? 0) + count;
|
|
95
|
-
return updateRunState(workspace, {
|
|
96
|
-
patch_count: patchCount,
|
|
97
|
-
review_status: "patched",
|
|
98
|
-
review_missing: [],
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
69
|
export function markMetadataConfidenceReviewed(workspace, operations) {
|
|
102
70
|
const metadataOps = new Set(["meta.worldview.set", "asset.role.set", "asset.describe"]);
|
|
103
71
|
if (!operations.some((op) => isDict(op) && metadataOps.has(strOf(op["op"]))))
|
|
@@ -118,17 +86,6 @@ export function markMetadataConfidenceReviewed(workspace, operations) {
|
|
|
118
86
|
metadata["confidence_reviewed_at"] = checkpointTimestamp();
|
|
119
87
|
writeJson(p, metadata);
|
|
120
88
|
}
|
|
121
|
-
export function reviewBlockers(state) {
|
|
122
|
-
if (Number(state["patch_count"] ?? 0) > 0)
|
|
123
|
-
return [];
|
|
124
|
-
const inspected = new Set();
|
|
125
|
-
for (const item of asList(state["inspected_targets"])) {
|
|
126
|
-
const s = strOf(item);
|
|
127
|
-
if (s)
|
|
128
|
-
inspected.add(s);
|
|
129
|
-
}
|
|
130
|
-
return REVIEW_TARGETS.filter((t) => !inspected.has(t));
|
|
131
|
-
}
|
|
132
89
|
// ---------------------------------------------------------------------------
|
|
133
90
|
// Paths for episode/batch results
|
|
134
91
|
// ---------------------------------------------------------------------------
|
|
@@ -585,7 +542,7 @@ function writeBatchFailure(dir, batch, exc) {
|
|
|
585
542
|
function initFailedReport(workspace, opts) {
|
|
586
543
|
const payload = {
|
|
587
544
|
status: "init_failed",
|
|
588
|
-
command: "
|
|
545
|
+
command: "import init",
|
|
589
546
|
init_stage: opts.stage,
|
|
590
547
|
last_error: { title: opts.title, received: opts.received, failed_at: checkpointTimestamp() },
|
|
591
548
|
};
|
|
@@ -720,7 +677,7 @@ export async function commandInit(opts) {
|
|
|
720
677
|
const previousStateBeforeInit = readRunState(workspace);
|
|
721
678
|
updateRunState(workspace, {
|
|
722
679
|
status: "init_running",
|
|
723
|
-
command: "
|
|
680
|
+
command: "import init",
|
|
724
681
|
init_stage: "source_prepare",
|
|
725
682
|
provider: providerName,
|
|
726
683
|
model,
|
|
@@ -973,11 +930,11 @@ export async function commandInit(opts) {
|
|
|
973
930
|
: "INIT INCOMPLETE: Batch extraction failed";
|
|
974
931
|
const nextSteps = sameFailuresRepeated
|
|
975
932
|
? [
|
|
976
|
-
"
|
|
933
|
+
"Read failed batch details in draft/scriptctl/direct/batch_results/*.error.json (run_state.json lists failed_batches).",
|
|
977
934
|
"Do not rerun the same init command again until source, batch options, provider, or failed content has changed.",
|
|
978
935
|
]
|
|
979
936
|
: [
|
|
980
|
-
"
|
|
937
|
+
"Read failed batches in draft/scriptctl/direct/batch_results/*.error.json (run_state.json lists failed_batches).",
|
|
981
938
|
"Rerun the same init once if failures look transient; completed checkpoints will be reused.",
|
|
982
939
|
];
|
|
983
940
|
const failedEpisodeSet = new Set(failedEpisodes);
|
|
@@ -1211,7 +1168,7 @@ export async function commandInit(opts) {
|
|
|
1211
1168
|
const status = passed ? "ready_for_agent" : "needs_agent_repair";
|
|
1212
1169
|
updateRunState(workspace, {
|
|
1213
1170
|
status,
|
|
1214
|
-
command: "
|
|
1171
|
+
command: "import init",
|
|
1215
1172
|
init_stage: "complete",
|
|
1216
1173
|
checkpoint,
|
|
1217
1174
|
batch_checkpoint: batchCheckpoint,
|
|
@@ -1240,10 +1197,9 @@ export async function commandInit(opts) {
|
|
|
1240
1197
|
failure_signature: [],
|
|
1241
1198
|
failure_streak: 0,
|
|
1242
1199
|
last_error: null,
|
|
1200
|
+
// Informational signal only — no longer a push gate. The review subagent
|
|
1201
|
+
// flips this once it has self-reviewed the published DB script.
|
|
1243
1202
|
review_status: "pending",
|
|
1244
|
-
review_missing: [...REVIEW_TARGETS],
|
|
1245
|
-
inspected_targets: [],
|
|
1246
|
-
patch_count: 0,
|
|
1247
1203
|
exportable: providerName !== "mock",
|
|
1248
1204
|
});
|
|
1249
1205
|
const title = passed
|
|
@@ -1261,7 +1217,7 @@ export async function commandInit(opts) {
|
|
|
1261
1217
|
`episode checkpoint reused: ${skipped.length}`,
|
|
1262
1218
|
`batches: ${completedBatches}/${asList(batchPlan["batches"]).length} completed`,
|
|
1263
1219
|
`batch checkpoint reused: ${skippedEpisodeBatchCount + skippedBatches.length}`,
|
|
1264
|
-
"
|
|
1220
|
+
"review: pending (post-push self-review)",
|
|
1265
1221
|
],
|
|
1266
1222
|
artifacts: [
|
|
1267
1223
|
path.join(workspace, "source.txt"),
|
|
@@ -1279,10 +1235,10 @@ export async function commandInit(opts) {
|
|
|
1279
1235
|
issues: summarizeIssues(asList(validation["issues"])),
|
|
1280
1236
|
next: providerName === "mock"
|
|
1281
1237
|
? [
|
|
1282
|
-
"
|
|
1283
|
-
"
|
|
1238
|
+
"Do not push mock-provider results for delivery.",
|
|
1239
|
+
"Rerun init with --provider anthropic, then run scriptctl import push.",
|
|
1284
1240
|
]
|
|
1285
|
-
: ["Run
|
|
1241
|
+
: ["Run scriptctl import push to publish the script to the DB."],
|
|
1286
1242
|
};
|
|
1287
1243
|
return [report, passed ? EXIT_OK : EXIT_NEEDS_AGENT];
|
|
1288
1244
|
}
|
|
@@ -1298,404 +1254,4 @@ export function summarizeIssues(issues) {
|
|
|
1298
1254
|
const first = issues[0];
|
|
1299
1255
|
return [parts.join("; "), `first: ${first["code"]} - ${first["summary"]}`];
|
|
1300
1256
|
}
|
|
1301
|
-
// ---------------------------------------------------------------------------
|
|
1302
|
-
// command_validate
|
|
1303
|
-
// ---------------------------------------------------------------------------
|
|
1304
|
-
export function commandValidate(opts) {
|
|
1305
|
-
const workspace = strOf(opts["workspace_path"] || "workspace");
|
|
1306
|
-
const scriptPath = opts["script_path"] ? strOf(opts["script_path"]) : null;
|
|
1307
|
-
const validation = validateScript(workspace, scriptPath);
|
|
1308
|
-
const stats = validation["stats"] ?? {};
|
|
1309
|
-
const passed = Boolean(validation["passed"]);
|
|
1310
|
-
const report = {
|
|
1311
|
-
title: passed ? "VALIDATE PASSED: Script is ready" : "VALIDATE NEEDS AGENT: Repair issues found",
|
|
1312
|
-
result: [
|
|
1313
|
-
`episodes: ${stats["episodes"] ?? 0}`,
|
|
1314
|
-
`scenes: ${stats["scenes"] ?? 0}`,
|
|
1315
|
-
`actions: ${stats["actions"] ?? 0}`,
|
|
1316
|
-
],
|
|
1317
|
-
artifacts: [path.join(directDir(workspace), "validation.json")],
|
|
1318
|
-
issues: summarizeIssues(asList(validation["issues"])),
|
|
1319
|
-
next: [passed ? "Export script.json." : "Inspect issues and apply structured patches."],
|
|
1320
|
-
};
|
|
1321
|
-
return [report, passed ? EXIT_OK : EXIT_NEEDS_AGENT];
|
|
1322
|
-
}
|
|
1323
|
-
// ---------------------------------------------------------------------------
|
|
1324
|
-
// command_inspect (incl. review rendering)
|
|
1325
|
-
// ---------------------------------------------------------------------------
|
|
1326
|
-
function renderReviewEpisode(sourceText, episodePlan, script, episodeNum) {
|
|
1327
|
-
const epId = fmtId("ep", episodeNum);
|
|
1328
|
-
const scriptEp = asList(script["episodes"]).find((ep) => ep["episode_id"] === epId);
|
|
1329
|
-
const planEp = asList(episodePlan["episodes"]).find((ep) => Number(ep["episode"] ?? 0) === episodeNum);
|
|
1330
|
-
const lines = [];
|
|
1331
|
-
if (!planEp && !scriptEp) {
|
|
1332
|
-
lines.push(`⚠ Episode ${episodeNum} not found in episode_plan.json or script.initial.json.`);
|
|
1333
|
-
const available = [...new Set(asList(episodePlan["episodes"]).map((ep) => Number(ep["episode"] ?? 0)))].sort((a, b) => a - b);
|
|
1334
|
-
if (available.length > 0)
|
|
1335
|
-
lines.push(`Available episodes: ${available.join(", ")}`);
|
|
1336
|
-
return lines;
|
|
1337
|
-
}
|
|
1338
|
-
const title = (scriptEp?.["title"] ?? planEp?.["title"]) || "(无标题)";
|
|
1339
|
-
lines.push("=".repeat(72));
|
|
1340
|
-
lines.push(`EPISODE ${episodeNum} / ${epId} — ${title}`);
|
|
1341
|
-
lines.push("=".repeat(72));
|
|
1342
|
-
lines.push("");
|
|
1343
|
-
lines.push("--- 原文 source.txt ---");
|
|
1344
|
-
if (planEp) {
|
|
1345
|
-
const span = isDict(planEp["source_span"]) ? planEp["source_span"] : {};
|
|
1346
|
-
const start = Number(span["start"] ?? 0);
|
|
1347
|
-
const end = Number(span["end"] ?? sourceText.length);
|
|
1348
|
-
const snippet = sourceText.slice(start, end).replace(/\s+$/, "");
|
|
1349
|
-
lines.push(snippet || "(empty)");
|
|
1350
|
-
}
|
|
1351
|
-
else {
|
|
1352
|
-
lines.push("(no episode_plan entry; source span unavailable)");
|
|
1353
|
-
}
|
|
1354
|
-
lines.push("");
|
|
1355
|
-
lines.push("--- 抽取 script.initial.json ---");
|
|
1356
|
-
if (scriptEp) {
|
|
1357
|
-
const actorIdToName = new Map();
|
|
1358
|
-
for (const a of asList(script["actors"]))
|
|
1359
|
-
actorIdToName.set(strOf(a["actor_id"]), strOf(a["actor_name"]));
|
|
1360
|
-
const speakerIdToName = new Map();
|
|
1361
|
-
for (const s of asList(script["speakers"]))
|
|
1362
|
-
speakerIdToName.set(strOf(s["speaker_id"]), strOf(s["display_name"]));
|
|
1363
|
-
const locationIdToName = new Map();
|
|
1364
|
-
for (const l of asList(script["locations"]))
|
|
1365
|
-
locationIdToName.set(strOf(l["location_id"]), strOf(l["location_name"]));
|
|
1366
|
-
const propIdToName = new Map();
|
|
1367
|
-
for (const p of asList(script["props"]))
|
|
1368
|
-
propIdToName.set(strOf(p["prop_id"]), strOf(p["prop_name"]));
|
|
1369
|
-
for (const scene of asList(scriptEp["scenes"])) {
|
|
1370
|
-
const env = isDict(scene["environment"]) ? scene["environment"] : {};
|
|
1371
|
-
const space = strOf(env["space"]) || "?";
|
|
1372
|
-
const timeOfDay = strOf(env["time"]) || "?";
|
|
1373
|
-
const sceneId = strOf(scene["scene_id"]) || "?";
|
|
1374
|
-
let locName = "";
|
|
1375
|
-
for (const ref of asList(scene["locations"])) {
|
|
1376
|
-
locName = locationIdToName.get(strOf(ref["location_id"])) || locName;
|
|
1377
|
-
break;
|
|
1378
|
-
}
|
|
1379
|
-
const actorNames = [];
|
|
1380
|
-
for (const ref of asList(scene["actors"])) {
|
|
1381
|
-
const n = actorIdToName.get(strOf(ref["actor_id"]));
|
|
1382
|
-
if (n)
|
|
1383
|
-
actorNames.push(n);
|
|
1384
|
-
}
|
|
1385
|
-
const propNames = [];
|
|
1386
|
-
for (const ref of asList(scene["props"])) {
|
|
1387
|
-
const n = propIdToName.get(strOf(ref["prop_id"]));
|
|
1388
|
-
if (n)
|
|
1389
|
-
propNames.push(n);
|
|
1390
|
-
}
|
|
1391
|
-
lines.push("");
|
|
1392
|
-
lines.push(`Scene ${sceneId} [${space} ${timeOfDay}] ${locName || "(未知场景)"}`);
|
|
1393
|
-
if (actorNames.length > 0)
|
|
1394
|
-
lines.push(` Actors: ${actorNames.join(", ")}`);
|
|
1395
|
-
if (propNames.length > 0)
|
|
1396
|
-
lines.push(` Props: ${propNames.join(", ")}`);
|
|
1397
|
-
for (const action of asList(scene["actions"])) {
|
|
1398
|
-
const kind = strOf(action["type"]);
|
|
1399
|
-
const content = strOf(action["content"]).replace(/\n/g, "\n ");
|
|
1400
|
-
let tag;
|
|
1401
|
-
if (kind === "dialogue") {
|
|
1402
|
-
const speaker = actorIdToName.get(strOf(action["actor_id"])) || speakerIdToName.get(strOf(action["speaker_id"])) || strOf(action["speaker"]) || "?";
|
|
1403
|
-
const emotion = strOf(action["emotion"]);
|
|
1404
|
-
tag = `dlg|${speaker}` + (emotion ? `|${emotion}` : "");
|
|
1405
|
-
}
|
|
1406
|
-
else if (kind === "inner_thought") {
|
|
1407
|
-
const speaker = actorIdToName.get(strOf(action["actor_id"])) || speakerIdToName.get(strOf(action["speaker_id"])) || strOf(action["speaker"]) || "?";
|
|
1408
|
-
const emotion = strOf(action["emotion"]);
|
|
1409
|
-
tag = `think|${speaker}` + (emotion ? `|${emotion}` : "");
|
|
1410
|
-
}
|
|
1411
|
-
else {
|
|
1412
|
-
tag = "act";
|
|
1413
|
-
}
|
|
1414
|
-
lines.push(` [${tag}] ${content}`);
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
else {
|
|
1419
|
-
lines.push("(script.initial.json contains no entry for this episode)");
|
|
1420
|
-
}
|
|
1421
|
-
if (scriptEp) {
|
|
1422
|
-
const actorIdsInEp = new Set();
|
|
1423
|
-
const locationIdsInEp = new Set();
|
|
1424
|
-
const propIdsInEp = new Set();
|
|
1425
|
-
for (const scene of asList(scriptEp["scenes"])) {
|
|
1426
|
-
for (const ref of asList(scene["actors"])) {
|
|
1427
|
-
if (ref["actor_id"])
|
|
1428
|
-
actorIdsInEp.add(strOf(ref["actor_id"]));
|
|
1429
|
-
}
|
|
1430
|
-
for (const ref of asList(scene["locations"])) {
|
|
1431
|
-
if (ref["location_id"])
|
|
1432
|
-
locationIdsInEp.add(strOf(ref["location_id"]));
|
|
1433
|
-
}
|
|
1434
|
-
for (const ref of asList(scene["props"])) {
|
|
1435
|
-
if (ref["prop_id"])
|
|
1436
|
-
propIdsInEp.add(strOf(ref["prop_id"]));
|
|
1437
|
-
}
|
|
1438
|
-
for (const action of asList(scene["actions"])) {
|
|
1439
|
-
if (action["actor_id"])
|
|
1440
|
-
actorIdsInEp.add(strOf(action["actor_id"]));
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
if (actorIdsInEp.size > 0 || locationIdsInEp.size > 0 || propIdsInEp.size > 0) {
|
|
1444
|
-
lines.push("");
|
|
1445
|
-
lines.push(`--- Episode ${episodeNum} 资产 ---`);
|
|
1446
|
-
}
|
|
1447
|
-
if (actorIdsInEp.size > 0) {
|
|
1448
|
-
lines.push("Actors:");
|
|
1449
|
-
for (const actor of asList(script["actors"])) {
|
|
1450
|
-
if (!actorIdsInEp.has(strOf(actor["actor_id"])))
|
|
1451
|
-
continue;
|
|
1452
|
-
const desc = strOf(actor["description"]).trim() || "(无描述)";
|
|
1453
|
-
const role = strOf(actor["role_type"]) || "?";
|
|
1454
|
-
lines.push(` - ${actor["actor_id"]} ${actor["actor_name"]} [${role}] — ${desc}`);
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
if (locationIdsInEp.size > 0) {
|
|
1458
|
-
lines.push("Locations:");
|
|
1459
|
-
for (const loc of asList(script["locations"])) {
|
|
1460
|
-
if (!locationIdsInEp.has(strOf(loc["location_id"])))
|
|
1461
|
-
continue;
|
|
1462
|
-
const desc = strOf(loc["description"]).trim() || "(无描述)";
|
|
1463
|
-
lines.push(` - ${loc["location_id"]} ${loc["location_name"]} — ${desc}`);
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
if (propIdsInEp.size > 0) {
|
|
1467
|
-
lines.push("Props:");
|
|
1468
|
-
for (const prop of asList(script["props"])) {
|
|
1469
|
-
if (!propIdsInEp.has(strOf(prop["prop_id"])))
|
|
1470
|
-
continue;
|
|
1471
|
-
const desc = strOf(prop["description"]).trim() || "(无描述)";
|
|
1472
|
-
lines.push(` - ${prop["prop_id"]} ${prop["prop_name"]} — ${desc}`);
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
return lines;
|
|
1477
|
-
}
|
|
1478
|
-
function renderAssetCurationSummary(curation) {
|
|
1479
|
-
const summary = isDict(curation["summary"]) ? curation["summary"] : {};
|
|
1480
|
-
const lines = [
|
|
1481
|
-
`curation: ` +
|
|
1482
|
-
`actors ${summary["actors_before"] ?? 0}->${summary["actors_after"] ?? 0} ` +
|
|
1483
|
-
`(removed ${summary["actors_removed"] ?? 0}), ` +
|
|
1484
|
-
`props ${summary["props_before"] ?? 0}->${summary["props_after"] ?? 0} ` +
|
|
1485
|
-
`(removed ${summary["props_removed"] ?? 0}), ` +
|
|
1486
|
-
`locations ${summary["locations_before"] ?? 0}->${summary["locations_after"] ?? 0} ` +
|
|
1487
|
-
`(merged ${summary["locations_merged"] ?? 0})`,
|
|
1488
|
-
];
|
|
1489
|
-
for (const actor of asList(curation["actors"])) {
|
|
1490
|
-
if (!isDict(actor) || actor["decision"] !== "remove")
|
|
1491
|
-
continue;
|
|
1492
|
-
lines.push(`curation actor ${actor["actor_id"]}: remove ${actor["name"] || "-"} (scenes=${actor["scene_count"] ?? 0}) — ${actor["reason"] || "-"}`);
|
|
1493
|
-
}
|
|
1494
|
-
for (const prop of asList(curation["props"])) {
|
|
1495
|
-
if (!isDict(prop) || prop["decision"] !== "remove")
|
|
1496
|
-
continue;
|
|
1497
|
-
lines.push(`curation prop ${prop["prop_id"]}: remove ${prop["name"] || "-"} (scenes=${prop["scene_count"] ?? 0}) — ${prop["reason"] || "-"}`);
|
|
1498
|
-
}
|
|
1499
|
-
for (const loc of asList(curation["locations"])) {
|
|
1500
|
-
if (!isDict(loc) || loc["decision"] !== "merge")
|
|
1501
|
-
continue;
|
|
1502
|
-
lines.push(`curation location ${loc["location_id"]}: merge ${loc["name"] || "-"} -> ${loc["target_location_id"] || "-"} — ${loc["reason"] || "-"}`);
|
|
1503
|
-
}
|
|
1504
|
-
return lines;
|
|
1505
|
-
}
|
|
1506
|
-
export function commandInspect(opts) {
|
|
1507
|
-
const workspace = strOf(opts["workspace_path"] || "workspace");
|
|
1508
|
-
const target = strOf(opts["target"]);
|
|
1509
|
-
const itemId = opts["id"] ? strOf(opts["id"]) : null;
|
|
1510
|
-
const dd = directDir(workspace);
|
|
1511
|
-
const scriptPath = path.join(dd, "script.initial.json");
|
|
1512
|
-
const validationPath = path.join(dd, "validation.json");
|
|
1513
|
-
if (!exists(scriptPath) && target === "asset") {
|
|
1514
|
-
throw new CliError("INSPECT BLOCKED: script.initial.json not found", "script.initial.json not found.", {
|
|
1515
|
-
exitCode: EXIT_INPUT,
|
|
1516
|
-
required: ["workspace/draft/scriptctl/direct/script.initial.json"],
|
|
1517
|
-
received: [scriptPath],
|
|
1518
|
-
nextSteps: ["Run scriptctl direct init first."],
|
|
1519
|
-
});
|
|
1520
|
-
}
|
|
1521
|
-
const script = exists(scriptPath)
|
|
1522
|
-
? readJson(scriptPath)
|
|
1523
|
-
: { episodes: [], actors: [], locations: [], props: [] };
|
|
1524
|
-
let validation = exists(validationPath) ? readJson(validationPath) : { issues: [] };
|
|
1525
|
-
if (target === "issue" && exists(scriptPath)) {
|
|
1526
|
-
validation = validateScript(workspace, scriptPath, { requireSource: false });
|
|
1527
|
-
}
|
|
1528
|
-
const batchPlanPath = path.join(dd, "batch_plan.json");
|
|
1529
|
-
const batchPlan = exists(batchPlanPath) ? readJson(batchPlanPath) : { batches: [] };
|
|
1530
|
-
const batchCounts = new Map();
|
|
1531
|
-
for (const batch of asList(batchPlan["batches"])) {
|
|
1532
|
-
if (!isDict(batch))
|
|
1533
|
-
continue;
|
|
1534
|
-
const ep = Number(batch["episode"] ?? 0);
|
|
1535
|
-
batchCounts.set(ep, (batchCounts.get(ep) ?? 0) + 1);
|
|
1536
|
-
}
|
|
1537
|
-
const failedByEpisode = new Map();
|
|
1538
|
-
const batchResultsDir = path.join(dd, "batch_results");
|
|
1539
|
-
if (exists(batchResultsDir)) {
|
|
1540
|
-
const files = fs.readdirSync(batchResultsDir).filter((n) => n.endsWith(".error.json")).sort();
|
|
1541
|
-
for (const name of files) {
|
|
1542
|
-
const errorFile = path.join(batchResultsDir, name);
|
|
1543
|
-
try {
|
|
1544
|
-
const error = readJson(errorFile);
|
|
1545
|
-
const episodeNum = Number(error["episode"] ?? 0);
|
|
1546
|
-
if (!failedByEpisode.has(episodeNum))
|
|
1547
|
-
failedByEpisode.set(episodeNum, []);
|
|
1548
|
-
failedByEpisode.get(episodeNum).push(strOf(error["batch_id"]) || name.replace(/\.error\.json$/, ""));
|
|
1549
|
-
}
|
|
1550
|
-
catch {
|
|
1551
|
-
// ignore
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
const lines = [];
|
|
1556
|
-
if (target === "episode") {
|
|
1557
|
-
if (exists(scriptPath)) {
|
|
1558
|
-
for (const ep of asList(script["episodes"])) {
|
|
1559
|
-
if (itemId && itemId !== strOf(ep["episode_id"]))
|
|
1560
|
-
continue;
|
|
1561
|
-
const scenes = asList(ep["scenes"]);
|
|
1562
|
-
const sceneCount = scenes.length;
|
|
1563
|
-
let actionCount = 0;
|
|
1564
|
-
for (const scene of scenes)
|
|
1565
|
-
actionCount += asList(scene["actions"]).length;
|
|
1566
|
-
const rawNum = normalizeInt(strOf(ep["episode_id"]), lines.length + 1);
|
|
1567
|
-
const failed = (failedByEpisode.get(rawNum) ?? []).join(",") || "-";
|
|
1568
|
-
lines.push(`${ep["episode_id"]}: scenes=${sceneCount}, actions=${actionCount}, batches=${batchCounts.get(rawNum) ?? 0}, failed_batches=${failed}, title=${ep["title"] || "-"}`);
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
else {
|
|
1572
|
-
const planPath = path.join(dd, "episode_plan.json");
|
|
1573
|
-
const plan = exists(planPath) ? readJson(planPath) : { episodes: [] };
|
|
1574
|
-
for (const ep of asList(plan["episodes"])) {
|
|
1575
|
-
const epId = fmtId("ep", Number(ep["episode"] ?? 0));
|
|
1576
|
-
if (itemId && itemId !== epId && itemId !== strOf(ep["episode"]))
|
|
1577
|
-
continue;
|
|
1578
|
-
const episodeNum = Number(ep["episode"] ?? 0);
|
|
1579
|
-
const failed = (failedByEpisode.get(episodeNum) ?? []).join(",") || "-";
|
|
1580
|
-
lines.push(`${epId}: batches=${batchCounts.get(episodeNum) ?? 0}, failed_batches=${failed}, title=${ep["title"] || "-"}`);
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
else if (target === "asset") {
|
|
1585
|
-
const curationPath = path.join(dd, "asset_curation.json");
|
|
1586
|
-
if (!itemId && exists(curationPath)) {
|
|
1587
|
-
const curation = readJson(curationPath);
|
|
1588
|
-
if (isDict(curation))
|
|
1589
|
-
lines.push(...renderAssetCurationSummary(curation));
|
|
1590
|
-
}
|
|
1591
|
-
for (const [key, idKey, nameKey] of [["actors", "actor_id", "actor_name"], ["locations", "location_id", "location_name"], ["props", "prop_id", "prop_name"]]) {
|
|
1592
|
-
for (const asset of asList(script[key])) {
|
|
1593
|
-
if (itemId && itemId !== strOf(asset[idKey]) && itemId !== strOf(asset[nameKey]))
|
|
1594
|
-
continue;
|
|
1595
|
-
const aliases = asList(asset["aliases"]).length;
|
|
1596
|
-
const states = asList(asset["states"]).length;
|
|
1597
|
-
const role = key === "actors" ? `, role_type=${asset["role_type"]}` : "";
|
|
1598
|
-
const description = strOf(asset["description"]).trim() ? "yes" : "missing";
|
|
1599
|
-
const singular = key.slice(0, -1);
|
|
1600
|
-
lines.push(`${singular} ${asset[idKey]}: ${asset[nameKey]} aliases=${aliases}, states=${states}${role}, description=${description}`);
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
else if (target === "issue") {
|
|
1605
|
-
for (const errors of failedByEpisode.values()) {
|
|
1606
|
-
for (const batchId of errors) {
|
|
1607
|
-
const errorPath = path.join(batchResultsDir, `${batchId}.error.json`);
|
|
1608
|
-
if (!exists(errorPath))
|
|
1609
|
-
continue;
|
|
1610
|
-
let error;
|
|
1611
|
-
try {
|
|
1612
|
-
error = readJson(errorPath);
|
|
1613
|
-
}
|
|
1614
|
-
catch {
|
|
1615
|
-
continue;
|
|
1616
|
-
}
|
|
1617
|
-
if (itemId && itemId !== strOf(error["batch_id"]) && itemId !== strOf(error["episode"]) && itemId !== strOf(error["error_type"]))
|
|
1618
|
-
continue;
|
|
1619
|
-
lines.push(`error BATCH_FAILED: ${error["batch_id"]} episode ${error["episode"]} part ${error["part"]} - ${error["message"]}`);
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
for (const issue of asList(validation["issues"])) {
|
|
1623
|
-
if (itemId && itemId !== strOf(issue["code"]) && itemId !== strOf(issue["severity"]))
|
|
1624
|
-
continue;
|
|
1625
|
-
const whereParts = [];
|
|
1626
|
-
for (const k of ["episode", "scene", "action_index"]) {
|
|
1627
|
-
if (issue[k] !== null && issue[k] !== undefined)
|
|
1628
|
-
whereParts.push(strOf(issue[k]));
|
|
1629
|
-
}
|
|
1630
|
-
const where = whereParts.join(" ");
|
|
1631
|
-
lines.push(`${issue["severity"]} ${issue["code"]}: ${issue["summary"]}${where ? ` [${where}]` : ""}`);
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
else if (target === "review") {
|
|
1635
|
-
const episodeArg = strOf(opts["episode"]).trim();
|
|
1636
|
-
if (!episodeArg) {
|
|
1637
|
-
throw new CliError("INSPECT BLOCKED: --episode required for review", "--episode required for review.", {
|
|
1638
|
-
exitCode: EXIT_USAGE,
|
|
1639
|
-
required: ["--episode <n>[,<n>...]"],
|
|
1640
|
-
received: ["--episode not provided"],
|
|
1641
|
-
nextSteps: ["Pass --episode 1 (single) or --episode 1,15,30 (multiple)."],
|
|
1642
|
-
});
|
|
1643
|
-
}
|
|
1644
|
-
let episodeFilters;
|
|
1645
|
-
try {
|
|
1646
|
-
episodeFilters = episodeArg.split(",").map((s) => s.trim()).filter((s) => s).map((s) => {
|
|
1647
|
-
const n = parseInt(s, 10);
|
|
1648
|
-
if (Number.isNaN(n))
|
|
1649
|
-
throw new Error("nan");
|
|
1650
|
-
return n;
|
|
1651
|
-
});
|
|
1652
|
-
}
|
|
1653
|
-
catch {
|
|
1654
|
-
throw new CliError("INSPECT BLOCKED: --episode must be integers", "--episode must be integers.", {
|
|
1655
|
-
exitCode: EXIT_USAGE,
|
|
1656
|
-
required: ["--episode <n>[,<n>...]"],
|
|
1657
|
-
received: [`--episode ${episodeArg}`],
|
|
1658
|
-
nextSteps: ["Pass episode numbers like --episode 1,15,30."],
|
|
1659
|
-
});
|
|
1660
|
-
}
|
|
1661
|
-
const sourcePath = path.join(workspace, "source.txt");
|
|
1662
|
-
if (!exists(sourcePath)) {
|
|
1663
|
-
throw new CliError("INSPECT BLOCKED: source.txt not found", "source.txt not found.", {
|
|
1664
|
-
exitCode: EXIT_INPUT,
|
|
1665
|
-
required: [sourcePath],
|
|
1666
|
-
received: ["source.txt missing"],
|
|
1667
|
-
nextSteps: ["Run scriptctl direct init first."],
|
|
1668
|
-
});
|
|
1669
|
-
}
|
|
1670
|
-
const sourceText = readText(sourcePath);
|
|
1671
|
-
const episodePlanPath = path.join(dd, "episode_plan.json");
|
|
1672
|
-
const episodePlan = exists(episodePlanPath) ? readJson(episodePlanPath) : { episodes: [] };
|
|
1673
|
-
for (let idx = 0; idx < episodeFilters.length; idx++) {
|
|
1674
|
-
if (idx > 0) {
|
|
1675
|
-
lines.push("");
|
|
1676
|
-
lines.push("");
|
|
1677
|
-
}
|
|
1678
|
-
lines.push(...renderReviewEpisode(sourceText, episodePlan, script, episodeFilters[idx]));
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
else {
|
|
1682
|
-
throw new CliError("INSPECT BLOCKED: Invalid target", "Invalid inspect target.", {
|
|
1683
|
-
exitCode: EXIT_USAGE,
|
|
1684
|
-
required: ["--target: episode, asset, issue, or review"],
|
|
1685
|
-
received: [`--target: ${target}`],
|
|
1686
|
-
nextSteps: ["Use one of the supported inspect targets."],
|
|
1687
|
-
});
|
|
1688
|
-
}
|
|
1689
|
-
const state = addInspectedTarget(workspace, target);
|
|
1690
|
-
const missingReview = reviewBlockers(state);
|
|
1691
|
-
const report = {
|
|
1692
|
-
title: `INSPECT: ${target}`,
|
|
1693
|
-
result: lines.length > 0 ? lines : ["No matching items."],
|
|
1694
|
-
next: [
|
|
1695
|
-
"Use inspect details to prepare a structured patch, or continue required review.",
|
|
1696
|
-
missingReview.length === 0 ? "Review complete; run validate/export." : `Still inspect: ${missingReview.join(", ")}.`,
|
|
1697
|
-
],
|
|
1698
|
-
};
|
|
1699
|
-
return [report, EXIT_OK];
|
|
1700
|
-
}
|
|
1701
1257
|
//# sourceMappingURL=direct.js.map
|