@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.
@@ -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, REVIEW_TARGETS, SUPPORTED_EXTS, deletePath, deleteTree, directDir, exists, fmtId, readJson, readText, sha256Text, writeJson, } from "../common.js";
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: "direct init",
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: "direct init",
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
- "Run direct inspect --target issue to read failed batch details.",
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
- "Run direct inspect --target issue to review failed batches.",
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: "direct init",
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
- "agent_review: pending",
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
- "Run inspect for issue, episode, and asset; apply patches if needed; then validate/export.",
1283
- "Do not export mock-provider results for delivery.",
1238
+ "Do not push mock-provider results for delivery.",
1239
+ "Rerun init with --provider anthropic, then run scriptctl import push.",
1284
1240
  ]
1285
- : ["Run inspect for issue, episode, and asset; apply patches if needed; then validate/export."],
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