@lingjingai/scriptctl 0.31.0 → 0.32.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/help-text.js CHANGED
@@ -115,6 +115,7 @@ Usage
115
115
 
116
116
  Commands
117
117
  - init Build source manifest, episode plan, initial script, validation.
118
+ - run Re-run one direct stage, e.g. state-binding or metadata.
118
119
  - validate Validate script.initial.json.
119
120
  - review Proofread the draft: curation/issues, or --episode N for source↔extract.
120
121
  - export Export script.initial.json to the final script store.
@@ -181,11 +182,14 @@ Writes
181
182
  - workspace/draft/scriptctl/direct/batch_results/*.json
182
183
  - workspace/draft/scriptctl/direct/batch_results/*.error.json
183
184
  - workspace/draft/scriptctl/direct/episode_results/*.json
185
+ - workspace/draft/scriptctl/direct/asset_grouping.json
184
186
  - workspace/draft/scriptctl/direct/asset_curation.json
187
+ - workspace/draft/scriptctl/direct/state_binding.json
185
188
  - workspace/draft/scriptctl/direct/asset_metadata.json (unless --skip-metadata)
186
189
  - workspace/draft/scriptctl/direct/script.initial.json
187
190
  - workspace/draft/scriptctl/direct/validation.json
188
191
  - workspace/draft/scriptctl/direct/run_state.json
192
+ - workspace/draft/scriptctl/direct/timings.json
189
193
 
190
194
  Exit codes
191
195
  - 0 initial script ready
@@ -193,6 +197,54 @@ Exit codes
193
197
  - 66 source file unavailable
194
198
  - 70 provider or runtime failure
195
199
  - 78 initial script written but agent repair is needed
200
+ `,
201
+ ],
202
+ [
203
+ ["direct", "run"],
204
+ `Re-run one direct pipeline stage against an existing workspace.
205
+
206
+ Usage
207
+ - scriptctl direct run <stage> [options]
208
+
209
+ Stages
210
+ - source-prepare
211
+ - episode-plan
212
+ - episode-titles
213
+ - batch-plan
214
+ - batch-extract
215
+ - episode-merge
216
+ - episode-synopsis
217
+ - script-merge
218
+ - asset-curation
219
+ - state-binding
220
+ - metadata
221
+ - script-synopsis
222
+ - validate
223
+
224
+ Options
225
+ - --workspace-path Workspace directory. Default: workspace
226
+ - --force Re-run the stage even when its primary artifact exists.
227
+ - --provider LLM provider for provider-backed stages. Default: anthropic
228
+ - --model Provider model. Default: claude-sonnet-4-6-aihubmix
229
+ - --episodes <list> Force re-run these episodes' batches where relevant.
230
+ - --batches <list> Force re-run these batch keys where relevant.
231
+ - --retry-errors Force re-run batches with existing error markers.
232
+ - --skip-metadata When running metadata, delete asset_metadata.json and mark metadata skipped.
233
+ - --json Print machine-readable JSON.
234
+ - --help Show this help.
235
+
236
+ Notes
237
+ - Script-mutating stages persist script.initial.json when run standalone:
238
+ script-merge, asset-curation, state-binding, metadata.
239
+ - state-binding writes state_binding.json and updates scene state refs /
240
+ action state_changes using existing assets and states only.
241
+
242
+ Exit codes
243
+ - 0 stage completed
244
+ - 64 usage or arguments error
245
+ - 66 required artifact unavailable
246
+ - 70 provider or runtime failure
247
+ - 78 stage wrote diagnostics but agent repair is needed
196
248
  `,
197
249
  ],
198
250
  [
@@ -254,7 +306,7 @@ Exit codes
254
306
  `Proofread the direct draft. Read-only; no state side effects.
255
307
 
256
308
  Usage
257
- - scriptctl direct review [options] # curation decisions + issues (validation + batch failures)
309
+ - scriptctl direct review [options] # curation/state-binding decisions + issues
258
310
  - scriptctl direct review --episode <n>[,<n>...] # source.txt ↔ script.initial.json side-by-side per episode
259
311
 
260
312
  Options
@@ -271,7 +323,9 @@ Notes
271
323
 
272
324
  Reads
273
325
  - workspace/draft/scriptctl/direct/script.initial.json
326
+ - workspace/draft/scriptctl/direct/asset_grouping.json
274
327
  - workspace/draft/scriptctl/direct/asset_curation.json
328
+ - workspace/draft/scriptctl/direct/state_binding.json
275
329
  - workspace/draft/scriptctl/direct/batch_results/*.error.json
276
330
  - workspace/draft/scriptctl/direct/episode_plan.json (--episode)
277
331
  - workspace/source.txt (--episode)
@@ -1 +1 @@
1
- {"version":3,"file":"help-text.js","sourceRoot":"","sources":["../src/help-text.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"help-text.js","sourceRoot":"","sources":["../src/help-text.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,9 @@ export interface ProviderCapabilities {
5
5
  extractBatch: boolean;
6
6
  extractEpisode: boolean;
7
7
  extractMetadata: boolean;
8
+ groupAssets: boolean;
9
+ curateAssets: boolean;
10
+ bindStates: boolean;
8
11
  extractEpisodeTitles: boolean;
9
12
  extractEpisodeSynopsis: boolean;
10
13
  extractScriptSynopsis: boolean;
@@ -22,6 +25,9 @@ export declare class MockProvider implements ProviderLike {
22
25
  extractEpisode(sourceText: string, episodePlan: Dict): Dict;
23
26
  extractBatch(sourceText: string, batchPlan: Dict): Dict;
24
27
  extractMetadata(_sourceText: string, script: Dict): Dict;
28
+ groupAssets(_script: Dict, contextText: string): string;
29
+ curateAssets(script: Dict, contextText: string): string;
30
+ bindStates(_script: Dict, context: Dict): Dict;
25
31
  extractEpisodeSynopsis(synopses: string[], _ctx: Dict): Dict;
26
32
  extractScriptSynopsis(synopses: string[], _ctx: Dict): Dict;
27
33
  extractEpisodeTitles(sourceText: string, episodePlan: Dict): Dict;
@@ -60,6 +66,9 @@ export declare class AnthropicProvider implements ProviderLike {
60
66
  }>;
61
67
  extractBatch(sourceText: string, batchPlan: Dict): Promise<Dict>;
62
68
  extractEpisodeTitles(sourceText: string, episodePlan: Dict): Promise<Dict>;
69
+ groupAssets(_script: Dict, contextText: string): Promise<string>;
70
+ curateAssets(_script: Dict, contextText: string): Promise<string>;
71
+ bindStates(_script: Dict, context: Dict): Promise<Dict>;
63
72
  extractMetadata(_sourceText: string, script: Dict): Promise<Dict>;
64
73
  private runMetadataMarkdown;
65
74
  private collectResponseTextWithRetry;
@@ -1,12 +1,15 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
- import { CliError, DEFAULT_BATCH_MAX_TOKENS, DEFAULT_MAX_TOKENS, DEFAULT_THINKING_BUDGET_TOKENS, EPISODE_SYNOPSIS_SCHEMA, EPISODE_TITLE_EXTRACTION_SCHEMA, EXIT_RUNTIME, EXIT_USAGE, DEFAULT_METADATA_ASSET_CHUNK, MARKDOWN_BATCH_PROMPT_SPEC, METADATA_DESC_MD_SPEC, METADATA_GLOBAL_MD_SPEC, SCRIPT_SYNOPSIS_SCHEMA, } from "../common.js";
3
- import { _md_push_asset, _normalize_speaker_list, assembleMetadataFromParts, cleanSynopsisList, buildEpisodeTitleContext, buildMetadataContext, deterministicEpisodeShortTitle, deterministicExtractEpisode, deterministicExtractMetadata, episodesNeedingGeneratedTitles, formatBatchSource, isRetryableProviderError, parseAssetDescriptionsMd, parseGlobalMetadataMd, parseMarkdownBatch, providerAttempts, } from "../domain/direct-core.js";
2
+ import { CliError, DEFAULT_BATCH_MAX_TOKENS, DEFAULT_MAX_TOKENS, DEFAULT_THINKING_BUDGET_TOKENS, EPISODE_SYNOPSIS_SCHEMA, EPISODE_TITLE_EXTRACTION_SCHEMA, EXIT_RUNTIME, EXIT_USAGE, DEFAULT_METADATA_ASSET_CHUNK, MARKDOWN_BATCH_PROMPT_SPEC, METADATA_DESC_MD_SPEC, METADATA_GLOBAL_MD_SPEC, SCRIPT_SYNOPSIS_SCHEMA, STATE_BINDING_SCHEMA, } from "../common.js";
3
+ import { ASSET_GROUP_MAX_ITEMS, _md_push_asset, _normalize_speaker_list, assembleMetadataFromParts, cleanSynopsisList, buildEpisodeTitleContext, buildMetadataContext, deterministicEpisodeShortTitle, deterministicExtractEpisode, deterministicExtractMetadata, episodesNeedingGeneratedTitles, formatBatchSource, formatAssetCurationDecisionExpressions, isRetryableProviderError, parseAssetDescriptionsMd, parseGlobalMetadataMd, parseMarkdownBatch, providerAttempts, } from "../domain/direct-core.js";
4
4
  const PROVIDER_CAPABILITIES = {
5
5
  mock: {
6
6
  completeText: true,
7
7
  extractBatch: true,
8
8
  extractEpisode: true,
9
9
  extractMetadata: true,
10
+ groupAssets: true,
11
+ curateAssets: true,
12
+ bindStates: true,
10
13
  extractEpisodeTitles: true,
11
14
  extractEpisodeSynopsis: true,
12
15
  extractScriptSynopsis: true,
@@ -16,6 +19,9 @@ const PROVIDER_CAPABILITIES = {
16
19
  extractBatch: true,
17
20
  extractEpisode: true,
18
21
  extractMetadata: true,
22
+ groupAssets: true,
23
+ curateAssets: true,
24
+ bindStates: true,
19
25
  extractEpisodeTitles: true,
20
26
  extractEpisodeSynopsis: true,
21
27
  extractScriptSynopsis: true,
@@ -25,6 +31,9 @@ const PROVIDER_CAPABILITIES = {
25
31
  extractBatch: false,
26
32
  extractEpisode: false,
27
33
  extractMetadata: false,
34
+ groupAssets: false,
35
+ curateAssets: false,
36
+ bindStates: false,
28
37
  extractEpisodeTitles: false,
29
38
  extractEpisodeSynopsis: false,
30
39
  extractScriptSynopsis: false,
@@ -169,6 +178,157 @@ export class MockProvider {
169
178
  extractMetadata(_sourceText, script) {
170
179
  return deterministicExtractMetadata(script);
171
180
  }
181
+ groupAssets(_script, contextText) {
182
+ const kind = strOf(/^kind:\s*(actor|location|prop)$/m.exec(contextText)?.[1]);
183
+ const rows = contextText
184
+ .split("\n")
185
+ .map((line) => line.trim())
186
+ .filter((line) => /^(?:A|L|P)\s+\|/.test(line))
187
+ .map((line) => {
188
+ const parts = line.split("|").map((part) => part.trim());
189
+ return { id: parts[1] ?? "", name: parts[2] ?? "" };
190
+ })
191
+ .filter((row) => row.id && row.name);
192
+ const seenNames = new Set();
193
+ const keptIds = [];
194
+ for (const row of rows) {
195
+ const key = row.name.replace(/[\s_\-–—·.,,、::;;'"“”‘’()[\]{}()【】《》<>]+/g, "").toLowerCase();
196
+ if (seenNames.has(key))
197
+ continue;
198
+ seenNames.add(key);
199
+ keptIds.push(row.id);
200
+ }
201
+ const expressions = [];
202
+ for (let index = 0; index < keptIds.length; index += ASSET_GROUP_MAX_ITEMS) {
203
+ expressions.push(`<group>${kind} ${keptIds.slice(index, index + ASSET_GROUP_MAX_ITEMS).join(" ")} # mock name-order group</group>`);
204
+ }
205
+ return expressions.length > 0 ? expressions.join("\n") : `<group>${kind} __empty__ # mock empty group</group>`;
206
+ }
207
+ curateAssets(script, contextText) {
208
+ if (contextText.includes("# Asset Curation Audit Context") || contextText.includes("# Location Canonicalization Audit Context")) {
209
+ return "<exp>NOOP # no asset curation changes</exp>";
210
+ }
211
+ const decisions = [];
212
+ const actors = asList(script["actors"]).filter(isDict);
213
+ const locations = asList(script["locations"]).filter(isDict);
214
+ const props = asList(script["props"]).filter(isDict);
215
+ const actorByName = new Map();
216
+ for (const actor of actors) {
217
+ const name = mockAssetName(actor, "actor");
218
+ if (name)
219
+ actorByName.set(name, actor);
220
+ }
221
+ for (const actor of actors) {
222
+ const id = mockAssetId(actor, "actor");
223
+ const name = mockAssetName(actor, "actor");
224
+ if (!id)
225
+ continue;
226
+ const variant = actorVisualVariant(name);
227
+ const base = variant ? actorByName.get(variant.baseName) : null;
228
+ if (variant && base && mockAssetId(base, "actor") !== id) {
229
+ decisions.push({
230
+ kind: "actor",
231
+ source_id: id,
232
+ decision: "move_to_state",
233
+ target_kind: "actor",
234
+ target_id: mockAssetId(base, "actor"),
235
+ state_name: variant.stateName,
236
+ reason: "mock: visual form variant belongs to the base actor state catalog.",
237
+ });
238
+ continue;
239
+ }
240
+ if (looksLikeGroupSpeaker(name)) {
241
+ decisions.push({
242
+ kind: "actor",
243
+ source_id: id,
244
+ decision: "move_to_speaker",
245
+ speaker_kind: "group",
246
+ new_name: name,
247
+ reason: "mock: crowd/extra label is a speaker source, not a reusable actor asset.",
248
+ });
249
+ }
250
+ else {
251
+ decisions.push({
252
+ kind: "actor",
253
+ source_id: id,
254
+ decision: "keep",
255
+ reason: "mock: named actor remains an actor asset.",
256
+ });
257
+ }
258
+ }
259
+ for (const loc of locations) {
260
+ const id = mockAssetId(loc, "location");
261
+ const name = mockAssetName(loc, "location");
262
+ if (!id)
263
+ continue;
264
+ const target = locations.find((candidate) => {
265
+ const candidateId = mockAssetId(candidate, "location");
266
+ const candidateName = mockAssetName(candidate, "location");
267
+ return Boolean(candidateId && candidateId !== id && candidateName && candidateName.length < name.length && name.includes(candidateName) && isMicroLocationName(name));
268
+ });
269
+ if (target) {
270
+ decisions.push({
271
+ kind: "location",
272
+ source_id: id,
273
+ decision: "merge",
274
+ target_kind: "location",
275
+ target_id: mockAssetId(target, "location"),
276
+ reason: "mock: micro-location folds into the stable parent location.",
277
+ });
278
+ }
279
+ else {
280
+ decisions.push({
281
+ kind: "location",
282
+ source_id: id,
283
+ decision: "keep",
284
+ reason: "mock: stable location remains a production set.",
285
+ });
286
+ }
287
+ }
288
+ for (const prop of props) {
289
+ const id = mockAssetId(prop, "prop");
290
+ const name = mockAssetName(prop, "prop");
291
+ if (!id)
292
+ continue;
293
+ if (isDisposableOrUiProp(name)) {
294
+ decisions.push({
295
+ kind: "prop",
296
+ source_id: id,
297
+ decision: "drop",
298
+ reason: "mock: disposable UI/screen detail stays in action text.",
299
+ });
300
+ }
301
+ else {
302
+ decisions.push({
303
+ kind: "prop",
304
+ source_id: id,
305
+ decision: "keep",
306
+ reason: "category=signature_instance; mock: hero prop remains explicit.",
307
+ });
308
+ }
309
+ }
310
+ return formatAssetCurationDecisionExpressions({ decisions });
311
+ }
312
+ bindStates(_script, context) {
313
+ const scenes = [];
314
+ for (const scene of asList(context["scenes"])) {
315
+ if (!isDict(scene))
316
+ continue;
317
+ const actions = asList(scene["actions"]);
318
+ const actionText = actions.map((action) => strOf(action["content"])).join("\n");
319
+ const actors = mockBindingRefs(scene, "actors", "actor_id", actionText);
320
+ const locations = mockBindingRefs(scene, "locations", "location_id", actionText);
321
+ const props = mockBindingRefs(scene, "props", "prop_id", actionText);
322
+ scenes.push({
323
+ scene_id: scene["scene_id"],
324
+ actors,
325
+ locations,
326
+ props,
327
+ state_changes: mockStateChanges(scene, actions),
328
+ });
329
+ }
330
+ return { scenes };
331
+ }
172
332
  extractEpisodeSynopsis(synopses, _ctx) {
173
333
  // Prefix marks that this deterministic STITCH ran — lets e2e tests tell a
174
334
  // real reduce-step call apart from the plain-join fallback (which has no
@@ -409,6 +569,165 @@ export class AnthropicProvider {
409
569
  nextSteps: ["Rerun init; title generation will retry before batch extraction."],
410
570
  });
411
571
  }
572
+ async groupAssets(_script, contextText) {
573
+ const prompt = "You plan small asset review groups for `scriptctl direct init`.\n" +
574
+ "You receive only asset ids and names. Do not make final keep/drop/merge/state decisions.\n" +
575
+ "\n" +
576
+ "Rules:\n" +
577
+ `- Output only <group>...</group> lines, with at most ${ASSET_GROUP_MAX_ITEMS} ids per group.\n` +
578
+ "- Group ids whose names suggest they should be reviewed together later: same identity/form, alias-like names, same place family, or same prop family.\n" +
579
+ "- Omit an id only when it is an obvious duplicate of a retained id with the same normalized name. Do not omit merely because an asset seems unimportant.\n" +
580
+ "- Every non-duplicate id must appear exactly once. Do not invent ids.\n" +
581
+ "- No JSON, markdown, headings, bullets, or prose outside group tags.\n" +
582
+ "\n" +
583
+ "Expression grammar:\n" +
584
+ "- `<group><kind> <source_id> [source_id...] # reason</group>`\n" +
585
+ "- `<kind>` is the context kind: actor, location, or prop.\n" +
586
+ "\n" +
587
+ "Context:\n" +
588
+ `${contextText}\n`;
589
+ const request = this.messageRequest(prompt, 8000);
590
+ const [raw, stopReason] = await this.collectResponseTextWithRetry(request);
591
+ if (stopReason === "max_tokens") {
592
+ throw new CliError("INIT FAILED: Asset grouping output truncated", "Asset grouping output truncated.", {
593
+ exitCode: EXIT_RUNTIME,
594
+ required: ["complete asset grouping expressions within provider max_tokens"],
595
+ received: [`stop_reason: ${stopReason}`, `max_tokens: ${request.max_tokens}`, `raw chars: ${raw.length}`, `tail: ${raw.slice(-160) || "<empty response>"}`],
596
+ nextSteps: ["Rerun init; completed extraction checkpoints will be reused and asset grouping will retry."],
597
+ errorCode: "PROVIDER_OUTPUT_TRUNCATED",
598
+ });
599
+ }
600
+ if (!raw.trim()) {
601
+ throw new CliError("INIT FAILED: Provider returned empty asset grouping", "Provider returned an empty asset grouping response.", {
602
+ exitCode: EXIT_RUNTIME,
603
+ required: ["one or more <group> asset grouping expressions"],
604
+ received: [`stop_reason: ${stopReason || "unknown"}`, "raw chars: 0"],
605
+ nextSteps: ["Rerun init; completed extraction checkpoints will be reused and asset grouping will retry."],
606
+ errorCode: "PROVIDER_EMPTY_RESPONSE",
607
+ });
608
+ }
609
+ return raw;
610
+ }
611
+ async curateAssets(_script, contextText) {
612
+ const prompt = "You curate assets for `scriptctl direct init` after batch merge.\n" +
613
+ "Return only asset curation operation expressions. The caller will parse and apply them deterministically.\n" +
614
+ "\n" +
615
+ "Policy:\n" +
616
+ "- Target profile: align with a human-refined asset boundary for AI short-drama production.\n" +
617
+ "- Actors: keep named, speaking, visually identifiable, or plot-functional roles even when they appear only once.\n" +
618
+ "- Move only anonymous crowds, system/broadcast sources, or pure voice labels into speakers.\n" +
619
+ "- Move Q版 / 幻影 / 兽形 / 本体 / 真身 / 伪装 / alternate visual form actors into states on the base actor.\n" +
620
+ "- Use `identity=` hints to merge same-identity aliases/forms/titles when evidence supports it; do not keep duplicate base/form actors apart.\n" +
621
+ "- Merge duplicate names, aliases, typo variants, and same-identity titles into stable canonical assets.\n" +
622
+ "- Locations: keep visually distinct production sets; do not blindly merge interiors, doors, rooftops, underground levels, warehouses, fields, pens, sky/air variants, or other shootable sub-spaces.\n" +
623
+ "- Use `parent=` and `gran=` hints to MERGE non-distinct doorway/transition/nearby/overhead/angle-only micro locations into a parent; KEEP distinct shootable subspaces.\n" +
624
+ "- Location canonical names should preserve world/domain scope when the context distinguishes worlds or realms.\n" +
625
+ "- If a location co-occurs with a scope marker such as a world/realm/domain location, prefer `renameHint=` scoped canonical names via RENAME when the unscoped name would be ambiguous.\n" +
626
+ "- Props: use a strict refined prop boundary. Most visible or plot-mentioned objects should stay in action text or actor/location state, not standalone props.\n" +
627
+ "- Default prop decision is DROP unless standalone continuity is clear.\n" +
628
+ "- KEEP a prop when it is a named/signature physical instance whose visual identity must be reused, the same object is exchanged/carried/inspected across scenes, it carries readable evidence/rules/secrets, drives a transformation/conflict, functions as recurring character/faction equipment, or is a functional set device with continuity.\n" +
629
+ "- Every KEEP prop reason must start with one category: category=readable_evidence; category=container_device; category=transformation_key; category=exchanged_object; category=signature_instance; category=character_bound_core; category=signature_weapon; category=visual_symbol; or category=set_device; then cite concise evidence.\n" +
630
+ "- Do not KEEP merely because an object appears multiple times, is plot-functional, held/used by a character, or is UI/display content, an ordinary document/bill, food, or generic set dressing.\n" +
631
+ "- Move actor body marks and worn appearance into actor states; detachable named devices/weapons/tools/accessories with standalone continuity may remain props.\n" +
632
+ "- When a prop line has `ownerA=actor_id:name`, distinguish body/worn styling (STATE) from detachable recurring core objects (KEEP with category=character_bound_core or category=signature_weapon).\n" +
633
+ "- In long scripts, keep a compact prop catalog; prefer the strongest standalone hero props and DROP/STATE borderline objects.\n" +
634
+ "- For each duplicate group, choose one final canonical target (prefer higher scene_count, then shorter stable name); merge every other source directly into that final target.\n" +
635
+ "- MERGE is for same-kind duplicates. For actor/location entries that are really props, use PROP instead of cross-kind MERGE.\n" +
636
+ "- Never create reciprocal merges or merge cycles. Do not merge A->B and B->A, and do not create A->B->C chains when A can point to C.\n" +
637
+ "- Every asset line with `req=Y:*` must receive an expression, including KEEP if it should stay.\n" +
638
+ "- Every prop line must receive an expression. Do not omit props.\n" +
639
+ "- Use `required_decision_ids` as the coverage checklist before final output.\n" +
640
+ "- Assets with `req=-` should be omitted when unchanged; do not spend output on KEEP for req=- assets.\n" +
641
+ "- If output is long, prioritize every req=Y decision over any optional req=- expression.\n" +
642
+ "- If the context header is `# Asset Curation Audit Context`, output only corrective expressions for listed candidates, or NOOP if no correction is needed.\n" +
643
+ "- If the context header is `# Location Canonicalization Audit Context`, output only RENAME/MERGE location expressions for listed candidates, or NOOP if no location canonicalization is needed.\n" +
644
+ "- If the context header is `# Asset Curation Repair Context`, output replacement expressions only for `repair_required_source_keys`; do not repeat unrelated accepted decisions.\n" +
645
+ "- In audit mode, the first character of your response must be `<`. Do not write candidate reviews, headings, bullets, or prose outside expressions.\n" +
646
+ "- In audit mode, primary KEEP actor candidates may be corrected to SPEAKER, MERGE, or RENAME when they are generic numbered roles, anonymous crowds, pure voice labels, or duplicate identities.\n" +
647
+ "- In audit mode, primary KEEP location candidates may be corrected to RENAME or MERGE when scoped canonical naming or duplicate/parent-child granularity is wrong.\n" +
648
+ "- In audit mode, primary KEEP prop candidates may output KEEP only to add/replace a valid category reason; otherwise output DROP/MERGE/STATE/RENAME when KEEP is too broad.\n" +
649
+ "- In audit mode, primary KEEP props with `ownerA=` should become STATE only for body marks or worn appearance; keep detachable recurring core objects when evidence supports it.\n" +
650
+ "- In audit mode, primary DROP/MERGE/STATE prop candidates may be corrected to KEEP/RENAME/MERGE when the evidence shows standalone hero-prop continuity.\n" +
651
+ "- In location canonicalization mode, do not output KEEP, DROP, STATE, PROP, SPEAKER, actor, or prop operations.\n" +
652
+ "- In location canonicalization mode, omit unchanged candidates; KEEP is invalid and will fail validation.\n" +
653
+ "- In location canonicalization mode, use `scopeHint=`/`renameHint=` to RENAME ambiguous unscoped locations into scoped canonical names; use `parent=`/`gran=` to MERGE only non-distinct micro/transition/angle locations into a parent; leave distinct shootable subspaces unchanged.\n" +
654
+ "- For STATE/PROP/MERGE targets, use only existing target ids visible in the context line, such as `identity=`, `parent=`, `same=`, `coA=`, `coL=`, or related cluster ids. If no valid target id is visible, choose DROP instead of guessing.\n" +
655
+ "- Reference existing ids only. Do not invent source_id or target_id values.\n" +
656
+ "- For every actionable merge/drop/move/rename, give a short concrete reason from the context.\n" +
657
+ "- For every KEEP expression, give a short concrete reason from the context; KEEP prop reasons must begin like `category=signature_instance; ...` using one allowed category.\n" +
658
+ "- Keep reasons short. Prefer one precise phrase over long explanations.\n" +
659
+ "\n" +
660
+ "Output format:\n" +
661
+ "- Output plain text only. Do not output JSON, markdown tables, fenced code blocks, or explanations outside expressions.\n" +
662
+ "- Each expression must be one line wrapped exactly as `<exp>...</exp>`.\n" +
663
+ "- Every expression must include `# reason` at the end; the reason is required for audit.\n" +
664
+ "- If there are no changes, output exactly `<exp>NOOP # no asset curation changes</exp>`.\n" +
665
+ "\n" +
666
+ "Expression grammar:\n" +
667
+ "- `<exp>KEEP <kind> <source_id> # reason</exp>`\n" +
668
+ "- `<exp>MERGE <kind> <source_id> -> <target_id> # reason</exp>`\n" +
669
+ "- `<exp>DROP <kind> <source_id> # reason</exp>`\n" +
670
+ "- `<exp>RENAME <kind> <source_id> \"<new_name>\" # reason</exp>`\n" +
671
+ "- `<exp>STATE <kind> <source_id> -> <target_kind> <target_id> \"<state_name>\" # reason</exp>`\n" +
672
+ "- `<exp>SPEAKER <kind> <source_id> -> <speaker_kind> \"<display_name>\" # reason</exp>`\n" +
673
+ "- `<exp>PROP <kind> <source_id> -> <target_prop_id|NEW> \"<prop_name>\" # reason</exp>`\n" +
674
+ "- `<kind>` is one of actor, location, prop. `<target_kind>` is one of actor, location, prop.\n" +
675
+ "- `<speaker_kind>` is one of system, broadcast, group, other.\n" +
676
+ "- Use double quotes for names. Escape only `\"` and `\\\\` inside quoted strings.\n" +
677
+ "- Do not write natural-language operations. If an operation cannot fit this grammar, omit it.\n" +
678
+ "\n" +
679
+ "Context:\n" +
680
+ `${contextText}\n`;
681
+ const request = this.messageRequest(prompt, 20000);
682
+ const [raw, stopReason] = await this.collectResponseTextWithRetry(request);
683
+ if (stopReason === "max_tokens") {
684
+ throw new CliError("INIT FAILED: Asset curation output truncated", "Asset curation output truncated.", {
685
+ exitCode: EXIT_RUNTIME,
686
+ required: ["complete asset curation expressions within provider max_tokens"],
687
+ received: [`stop_reason: ${stopReason}`, `max_tokens: ${request.max_tokens}`, `raw chars: ${raw.length}`, `tail: ${raw.slice(-160) || "<empty response>"}`],
688
+ nextSteps: ["Rerun init; completed extraction checkpoints will be reused and asset curation will retry."],
689
+ errorCode: "PROVIDER_OUTPUT_TRUNCATED",
690
+ });
691
+ }
692
+ if (!raw.trim()) {
693
+ throw new CliError("INIT FAILED: Provider returned empty asset curation", "Provider returned an empty asset curation response.", {
694
+ exitCode: EXIT_RUNTIME,
695
+ required: ["one or more <exp> asset curation expressions"],
696
+ received: [`stop_reason: ${stopReason || "unknown"}`, "raw chars: 0"],
697
+ nextSteps: ["Rerun init; completed extraction checkpoints will be reused and asset curation will retry."],
698
+ errorCode: "PROVIDER_EMPTY_RESPONSE",
699
+ });
700
+ }
701
+ return raw;
702
+ }
703
+ async bindStates(_script, context) {
704
+ const prompt = "You bind scene asset refs to existing visual states for `scriptctl direct init`.\n" +
705
+ "Return JSON through the forced tool call. The caller will reject unknown scene, asset, or state ids.\n" +
706
+ "\n" +
707
+ "Policy:\n" +
708
+ "- Use only state_id values listed under that exact scene ref's `states` array. Never borrow a state_id from another asset.\n" +
709
+ "- Each ref already has `current_state_id` from deterministic default/unique binding when available.\n" +
710
+ "- Omit refs that should keep `current_state_id`; output a ref only when the scene/actions prove it should change to another existing state.\n" +
711
+ "- Do not output null for refs; omit the ref instead.\n" +
712
+ "- Emit state_changes only for explicit durable transitions: transformation/form change, outfit change, breakage, repair, damage state, or location condition change.\n" +
713
+ "- Do not emit state_changes for emotions, poses, camera effects, dialogue tone, or momentary actions.\n" +
714
+ "- Do not include reason or explanation fields; return only the schema fields needed for deterministic apply.\n" +
715
+ "- Include each scene from the input chunk, even if its arrays are empty.\n" +
716
+ "- `action_index` is the zero-based index from the scene actions array.\n" +
717
+ "\n" +
718
+ "Context:\n" +
719
+ `${JSON.stringify(context)}\n`;
720
+ return this.runToolUse({
721
+ prompt,
722
+ maxTokens: 16000,
723
+ toolName: "submit_state_binding",
724
+ description: "Return scene state refs and explicit state changes for deterministic application.",
725
+ schema: STATE_BINDING_SCHEMA,
726
+ truncatedTitle: "INIT FAILED: State binding output truncated",
727
+ invalidTitle: "INIT FAILED: Provider returned invalid state binding JSON",
728
+ nextSteps: ["Rerun init; completed extraction and curation checkpoints will be reused and state binding will retry."],
729
+ });
730
+ }
412
731
  // Metadata is extracted as MANY small markdown requests instead of one big one:
413
732
  // a single GLOBAL call (worldview + protagonists, tiny input/output) plus
414
733
  // per-asset-chunk DESCRIPTION calls (≤ DEFAULT_METADATA_ASSET_CHUNK assets each).
@@ -558,7 +877,7 @@ export class AnthropicProvider {
558
877
  `${JSON.stringify(episodeSynopses)}\n`;
559
878
  return this.runToolUse({
560
879
  prompt,
561
- maxTokens: 4096,
880
+ maxTokens: 2048,
562
881
  toolName: "submit_script_synopsis",
563
882
  description: "Return the whole-script overview (synopsis, theme, logline, style, main_characters).",
564
883
  schema: SCRIPT_SYNOPSIS_SCHEMA,
@@ -669,6 +988,126 @@ function firstNonEmptySourceLine(text) {
669
988
  const lines = text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
670
989
  return lines.find((line) => !line.endsWith(":") && !line.endsWith(":")) ?? lines[0] ?? "";
671
990
  }
991
+ function mockAssetId(asset, kind) {
992
+ if (kind === "actor")
993
+ return strOf(asset["actor_id"]).trim();
994
+ if (kind === "location")
995
+ return strOf(asset["location_id"]).trim();
996
+ return strOf(asset["prop_id"]).trim();
997
+ }
998
+ function mockAssetName(asset, kind) {
999
+ if (kind === "actor")
1000
+ return strOf(asset["actor_name"]).trim();
1001
+ if (kind === "location")
1002
+ return strOf(asset["location_name"]).trim();
1003
+ return strOf(asset["prop_name"]).trim();
1004
+ }
1005
+ function actorVisualVariant(name) {
1006
+ const prefix = /^(Q版|幻影|兽形|本体|灵体|黑化|幼年|少年|老年)[\s·::、-]*(.+)$/.exec(name);
1007
+ if (prefix)
1008
+ return { baseName: prefix[2].trim(), stateName: prefix[1].trim() };
1009
+ const suffix = /^(.+?)[\s·::、-]*(Q版|幻影|兽形|本体|灵体|黑化|幼年|少年|老年)$/.exec(name);
1010
+ if (suffix)
1011
+ return { baseName: suffix[1].trim(), stateName: suffix[2].trim() };
1012
+ return null;
1013
+ }
1014
+ function looksLikeGroupSpeaker(name) {
1015
+ return /众人|群众|围观者|围观群众|路人们|大家|人群|村民们|族人们|员工们|同事们/.test(name) ||
1016
+ /^路人[甲乙丙丁ABCD]$/.test(name);
1017
+ }
1018
+ function isMicroLocationName(name) {
1019
+ return /门口|门前|门边|角落|墙边|窗边|桌边|床边|车旁|车前|车后|楼道|走廊/.test(name);
1020
+ }
1021
+ function isDisposableOrUiProp(name) {
1022
+ return /一次性|手机画面|屏幕|界面|弹窗|提示框|聊天框|字幕条/.test(name);
1023
+ }
1024
+ function mockBindingRefs(scene, refKey, idKey, actionText) {
1025
+ const refs = isDict(scene["refs"]) ? scene["refs"] : {};
1026
+ const out = [];
1027
+ for (const ref of asList(refs[refKey])) {
1028
+ if (!isDict(ref))
1029
+ continue;
1030
+ const id = strOf(ref["id"]).trim();
1031
+ const states = asList(ref["states"]);
1032
+ if (!id || states.length === 0)
1033
+ continue;
1034
+ const stateId = pickMockStateId(states, actionText);
1035
+ out.push({ [idKey]: id, state_id: stateId });
1036
+ }
1037
+ return out;
1038
+ }
1039
+ function pickMockStateId(states, actionText) {
1040
+ for (const state of states) {
1041
+ const name = strOf(state["state_name"]).trim();
1042
+ const stateId = strOf(state["state_id"]).trim();
1043
+ if (name && name !== "默认" && stateId && actionText.includes(name))
1044
+ return stateId;
1045
+ }
1046
+ for (const state of states) {
1047
+ if (strOf(state["state_name"]).trim() === "默认")
1048
+ return strOf(state["state_id"]).trim() || null;
1049
+ }
1050
+ if (states.length === 1)
1051
+ return strOf(states[0]["state_id"]).trim() || null;
1052
+ return null;
1053
+ }
1054
+ function mockStateChanges(scene, actions) {
1055
+ const refs = isDict(scene["refs"]) ? scene["refs"] : {};
1056
+ const changeCues = /变成|变身|化作|恢复|变回|换上|穿上|脱下|破损|碎裂|修复|烧毁|亮起|熄灭/;
1057
+ const out = [];
1058
+ const seen = new Set();
1059
+ for (let actionIndex = 0; actionIndex < actions.length; actionIndex++) {
1060
+ const content = strOf(actions[actionIndex]["content"]);
1061
+ if (!changeCues.test(content))
1062
+ continue;
1063
+ for (const [refKey, targetKind] of [
1064
+ ["actors", "actor"],
1065
+ ["locations", "location"],
1066
+ ["props", "prop"],
1067
+ ]) {
1068
+ for (const ref of asList(refs[refKey])) {
1069
+ if (!isDict(ref))
1070
+ continue;
1071
+ const targetId = strOf(ref["id"]).trim();
1072
+ const states = asList(ref["states"]);
1073
+ const toStateId = pickExplicitMockStateId(states, content);
1074
+ if (!targetId || !toStateId)
1075
+ continue;
1076
+ const key = `${actionIndex}:${targetKind}:${targetId}`;
1077
+ if (seen.has(key))
1078
+ continue;
1079
+ seen.add(key);
1080
+ const defaultStateId = mockDefaultStateId(states);
1081
+ out.push({
1082
+ action_index: actionIndex,
1083
+ target_kind: targetKind,
1084
+ target_id: targetId,
1085
+ from_state_id: defaultStateId || null,
1086
+ to_state_id: toStateId,
1087
+ effective_from: "after_action",
1088
+ reason: "mock: action contains an explicit durable visual state change.",
1089
+ });
1090
+ }
1091
+ }
1092
+ }
1093
+ return out;
1094
+ }
1095
+ function pickExplicitMockStateId(states, content) {
1096
+ for (const state of states) {
1097
+ const name = strOf(state["state_name"]).trim();
1098
+ const stateId = strOf(state["state_id"]).trim();
1099
+ if (name && name !== "默认" && stateId && content.includes(name))
1100
+ return stateId;
1101
+ }
1102
+ return "";
1103
+ }
1104
+ function mockDefaultStateId(states) {
1105
+ for (const state of states) {
1106
+ if (strOf(state["state_name"]).trim() === "默认")
1107
+ return strOf(state["state_id"]).trim();
1108
+ }
1109
+ return "";
1110
+ }
672
1111
  /**
673
1112
  * Find a forced tool_use block by name in a Message's content array.
674
1113
  * Returns its `input` (already parsed by the SDK) or `null` when missing/non-object.