@lingjingai/scriptctl 0.35.0 → 0.36.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.
Files changed (367) hide show
  1. package/README.md +21 -5
  2. package/changes/0.36.0.md +22 -0
  3. package/dist/bin.js +0 -0
  4. package/dist/cli.d.ts +2 -1
  5. package/dist/cli.js +52 -128
  6. package/dist/cli.js.map +1 -1
  7. package/dist/common.d.ts +0 -16
  8. package/dist/common.js +2 -342
  9. package/dist/common.js.map +1 -1
  10. package/dist/domain/ingest/apply-plan.d.ts +23 -0
  11. package/dist/domain/ingest/apply-plan.js +210 -0
  12. package/dist/domain/ingest/apply-plan.js.map +1 -0
  13. package/dist/domain/ingest/assembler.d.ts +57 -0
  14. package/dist/domain/ingest/assembler.js +181 -0
  15. package/dist/domain/ingest/assembler.js.map +1 -0
  16. package/dist/domain/ingest/chunker.d.ts +9 -0
  17. package/dist/domain/ingest/chunker.js +38 -0
  18. package/dist/domain/ingest/chunker.js.map +1 -0
  19. package/dist/domain/ingest/evidence.d.ts +5 -0
  20. package/dist/domain/ingest/evidence.js +66 -0
  21. package/dist/domain/ingest/evidence.js.map +1 -0
  22. package/dist/domain/ingest/govern.d.ts +3 -0
  23. package/dist/domain/ingest/govern.js +127 -0
  24. package/dist/domain/ingest/govern.js.map +1 -0
  25. package/dist/domain/ingest/jsonl-parser.d.ts +6 -0
  26. package/dist/domain/ingest/jsonl-parser.js +187 -0
  27. package/dist/domain/ingest/jsonl-parser.js.map +1 -0
  28. package/dist/domain/ingest/markdown-parser.d.ts +6 -0
  29. package/dist/domain/ingest/markdown-parser.js +186 -0
  30. package/dist/domain/ingest/markdown-parser.js.map +1 -0
  31. package/dist/domain/ingest/records.d.ts +71 -0
  32. package/dist/domain/ingest/records.js +9 -0
  33. package/dist/domain/ingest/records.js.map +1 -0
  34. package/dist/domain/ingest/renumber.d.ts +14 -0
  35. package/dist/domain/ingest/renumber.js +130 -0
  36. package/dist/domain/ingest/renumber.js.map +1 -0
  37. package/dist/domain/ingest/script-v3.d.ts +94 -0
  38. package/dist/domain/ingest/script-v3.js +7 -0
  39. package/dist/domain/ingest/script-v3.js.map +1 -0
  40. package/dist/domain/ingest/state-reconcile.d.ts +11 -0
  41. package/dist/domain/ingest/state-reconcile.js +90 -0
  42. package/dist/domain/ingest/state-reconcile.js.map +1 -0
  43. package/dist/domain/ingest/validation.d.ts +11 -0
  44. package/dist/domain/ingest/validation.js +168 -0
  45. package/dist/domain/ingest/validation.js.map +1 -0
  46. package/dist/domain/ingest/video-aggregate.d.ts +23 -0
  47. package/dist/domain/ingest/video-aggregate.js +89 -0
  48. package/dist/domain/ingest/video-aggregate.js.map +1 -0
  49. package/dist/domain/ingest/video-apply.d.ts +37 -0
  50. package/dist/domain/ingest/video-apply.js +236 -0
  51. package/dist/domain/ingest/video-apply.js.map +1 -0
  52. package/dist/domain/ingest/video-md-parser.d.ts +2 -0
  53. package/dist/domain/ingest/video-md-parser.js +233 -0
  54. package/dist/domain/ingest/video-md-parser.js.map +1 -0
  55. package/dist/domain/ingest/video-records.d.ts +56 -0
  56. package/dist/domain/ingest/video-records.js +9 -0
  57. package/dist/domain/ingest/video-records.js.map +1 -0
  58. package/dist/domain/loom/index.d.ts +2 -1
  59. package/dist/domain/loom/index.js +12 -10
  60. package/dist/domain/loom/index.js.map +1 -1
  61. package/dist/domain/loom/types.d.ts +3 -2
  62. package/dist/domain/script/lookups.d.ts +3 -2
  63. package/dist/domain/script/lookups.js +25 -8
  64. package/dist/domain/script/lookups.js.map +1 -1
  65. package/dist/domain/script/patch/apply.js +2 -2
  66. package/dist/domain/script/patch/apply.js.map +1 -1
  67. package/dist/domain/script/patch/helpers.d.ts +3 -7
  68. package/dist/domain/script/patch/helpers.js +23 -96
  69. package/dist/domain/script/patch/helpers.js.map +1 -1
  70. package/dist/domain/script/patch/ops-action.d.ts +0 -2
  71. package/dist/domain/script/patch/ops-action.js +34 -100
  72. package/dist/domain/script/patch/ops-action.js.map +1 -1
  73. package/dist/domain/script/patch/ops-asset.d.ts +1 -0
  74. package/dist/domain/script/patch/ops-asset.js +102 -155
  75. package/dist/domain/script/patch/ops-asset.js.map +1 -1
  76. package/dist/domain/script/patch/ops-dialogue.d.ts +1 -4
  77. package/dist/domain/script/patch/ops-dialogue.js +43 -220
  78. package/dist/domain/script/patch/ops-dialogue.js.map +1 -1
  79. package/dist/domain/script/patch/ops-extend.js +8 -2
  80. package/dist/domain/script/patch/ops-extend.js.map +1 -1
  81. package/dist/domain/script/patch/ops-meta.js.map +1 -1
  82. package/dist/domain/script/patch/ops-scene.js +26 -39
  83. package/dist/domain/script/patch/ops-scene.js.map +1 -1
  84. package/dist/domain/script/patch/ops-state.d.ts +3 -3
  85. package/dist/domain/script/patch/ops-state.js +33 -49
  86. package/dist/domain/script/patch/ops-state.js.map +1 -1
  87. package/dist/domain/script/patch/registry.js +9 -13
  88. package/dist/domain/script/patch/registry.js.map +1 -1
  89. package/dist/domain/script/refs.js +23 -111
  90. package/dist/domain/script/refs.js.map +1 -1
  91. package/dist/domain/script/scene-refs.d.ts +3 -0
  92. package/dist/domain/script/scene-refs.js +35 -0
  93. package/dist/domain/script/scene-refs.js.map +1 -0
  94. package/dist/domain/script/schema.js +17 -21
  95. package/dist/domain/script/schema.js.map +1 -1
  96. package/dist/domain/script/text-util.d.ts +7 -0
  97. package/dist/domain/script/text-util.js +91 -0
  98. package/dist/domain/script/text-util.js.map +1 -0
  99. package/dist/help-text.js +117 -410
  100. package/dist/help-text.js.map +1 -1
  101. package/dist/infra/converters.js +1 -1
  102. package/dist/infra/converters.js.map +1 -1
  103. package/dist/infra/episode-title.d.ts +4 -0
  104. package/dist/infra/episode-title.js +129 -0
  105. package/dist/infra/episode-title.js.map +1 -0
  106. package/dist/infra/ingest/review-renderer.d.ts +1 -0
  107. package/dist/infra/ingest/review-renderer.js +431 -0
  108. package/dist/infra/ingest/review-renderer.js.map +1 -0
  109. package/dist/infra/ingest/source-resolver.d.ts +21 -0
  110. package/dist/infra/ingest/source-resolver.js +51 -0
  111. package/dist/infra/ingest/source-resolver.js.map +1 -0
  112. package/dist/infra/ingest/text-reader.d.ts +3 -0
  113. package/dist/infra/ingest/text-reader.js +27 -0
  114. package/dist/infra/ingest/text-reader.js.map +1 -0
  115. package/dist/infra/ingest/video-discovery.d.ts +8 -0
  116. package/dist/infra/ingest/video-discovery.js +60 -0
  117. package/dist/infra/ingest/video-discovery.js.map +1 -0
  118. package/dist/infra/ingest/workspace-store.d.ts +18 -0
  119. package/dist/infra/ingest/workspace-store.js +70 -0
  120. package/dist/infra/ingest/workspace-store.js.map +1 -0
  121. package/dist/infra/llm/anthropic-model.d.ts +11 -0
  122. package/dist/infra/llm/anthropic-model.js +109 -0
  123. package/dist/infra/llm/anthropic-model.js.map +1 -0
  124. package/dist/infra/llm/gemini-upload.d.ts +9 -0
  125. package/dist/infra/llm/gemini-upload.js +94 -0
  126. package/dist/infra/llm/gemini-upload.js.map +1 -0
  127. package/dist/infra/llm/gemini-video-model.d.ts +47 -0
  128. package/dist/infra/llm/gemini-video-model.js +167 -0
  129. package/dist/infra/llm/gemini-video-model.js.map +1 -0
  130. package/dist/infra/llm/openai-compatible-model.d.ts +13 -0
  131. package/dist/infra/llm/openai-compatible-model.js +143 -0
  132. package/dist/infra/llm/openai-compatible-model.js.map +1 -0
  133. package/dist/infra/llm/proxy.d.ts +1 -0
  134. package/dist/infra/llm/proxy.js +30 -0
  135. package/dist/infra/llm/proxy.js.map +1 -0
  136. package/dist/infra/llm/retry.d.ts +5 -0
  137. package/dist/infra/llm/retry.js +20 -0
  138. package/dist/infra/llm/retry.js.map +1 -0
  139. package/dist/llm/composition.d.ts +5 -0
  140. package/dist/llm/composition.js +5 -0
  141. package/dist/llm/composition.js.map +1 -0
  142. package/dist/llm/config.d.ts +26 -0
  143. package/dist/llm/config.js +201 -0
  144. package/dist/llm/config.js.map +1 -0
  145. package/dist/llm/registry.d.ts +44 -0
  146. package/dist/llm/registry.js +48 -0
  147. package/dist/llm/registry.js.map +1 -0
  148. package/dist/llm/router.d.ts +23 -0
  149. package/dist/llm/router.js +0 -0
  150. package/dist/llm/router.js.map +1 -0
  151. package/dist/llm/tasks/ingest/asset-curate.d.ts +13 -0
  152. package/dist/llm/tasks/ingest/asset-curate.js +45 -0
  153. package/dist/llm/tasks/ingest/asset-curate.js.map +1 -0
  154. package/dist/llm/tasks/ingest/asset-extract.d.ts +12 -0
  155. package/dist/llm/tasks/ingest/asset-extract.js +51 -0
  156. package/dist/llm/tasks/ingest/asset-extract.js.map +1 -0
  157. package/dist/llm/tasks/ingest/asset-govern.d.ts +22 -0
  158. package/dist/llm/tasks/ingest/asset-govern.js +143 -0
  159. package/dist/llm/tasks/ingest/asset-govern.js.map +1 -0
  160. package/dist/llm/tasks/ingest/mention-resolve.d.ts +9 -0
  161. package/dist/llm/tasks/ingest/mention-resolve.js +39 -0
  162. package/dist/llm/tasks/ingest/mention-resolve.js.map +1 -0
  163. package/dist/llm/tasks/ingest/plot-speaker.d.ts +11 -0
  164. package/dist/llm/tasks/ingest/plot-speaker.js +72 -0
  165. package/dist/llm/tasks/ingest/plot-speaker.js.map +1 -0
  166. package/dist/llm/tasks/ingest/roster-merge.d.ts +46 -0
  167. package/dist/llm/tasks/ingest/roster-merge.js +235 -0
  168. package/dist/llm/tasks/ingest/roster-merge.js.map +1 -0
  169. package/dist/llm/tasks/ingest/state-merge.d.ts +10 -0
  170. package/dist/llm/tasks/ingest/state-merge.js +84 -0
  171. package/dist/llm/tasks/ingest/state-merge.js.map +1 -0
  172. package/dist/llm/tasks/ingest/state-reconcile.d.ts +8 -0
  173. package/dist/llm/tasks/ingest/state-reconcile.js +44 -0
  174. package/dist/llm/tasks/ingest/state-reconcile.js.map +1 -0
  175. package/dist/llm/tasks/ingest/text-normalize.d.ts +10 -0
  176. package/dist/llm/tasks/ingest/text-normalize.js +89 -0
  177. package/dist/llm/tasks/ingest/text-normalize.js.map +1 -0
  178. package/dist/llm/tasks/ingest/video-correct.d.ts +30 -0
  179. package/dist/llm/tasks/ingest/video-correct.js +144 -0
  180. package/dist/llm/tasks/ingest/video-correct.js.map +1 -0
  181. package/dist/llm/tasks/ingest/video-transcribe.d.ts +4 -0
  182. package/dist/llm/tasks/ingest/video-transcribe.js +105 -0
  183. package/dist/llm/tasks/ingest/video-transcribe.js.map +1 -0
  184. package/dist/llm/tasks/schemas.d.ts +5 -0
  185. package/dist/llm/tasks/schemas.js +107 -0
  186. package/dist/llm/tasks/schemas.js.map +1 -0
  187. package/dist/llm/types.d.ts +97 -0
  188. package/dist/llm/types.js +13 -0
  189. package/dist/llm/types.js.map +1 -0
  190. package/dist/usecases/doctor.d.ts +1 -1
  191. package/dist/usecases/doctor.js +36 -9
  192. package/dist/usecases/doctor.js.map +1 -1
  193. package/dist/usecases/ingest/command.d.ts +5 -0
  194. package/dist/usecases/ingest/command.js +57 -0
  195. package/dist/usecases/ingest/command.js.map +1 -0
  196. package/dist/usecases/ingest/fanout.d.ts +10 -0
  197. package/dist/usecases/ingest/fanout.js +23 -0
  198. package/dist/usecases/ingest/fanout.js.map +1 -0
  199. package/dist/usecases/ingest/pass4-mention-resolve.d.ts +11 -0
  200. package/dist/usecases/ingest/pass4-mention-resolve.js +52 -0
  201. package/dist/usecases/ingest/pass4-mention-resolve.js.map +1 -0
  202. package/dist/usecases/ingest/pass5-extract.d.ts +7 -0
  203. package/dist/usecases/ingest/pass5-extract.js +50 -0
  204. package/dist/usecases/ingest/pass5-extract.js.map +1 -0
  205. package/dist/usecases/ingest/pass5-govern.d.ts +13 -0
  206. package/dist/usecases/ingest/pass5-govern.js +155 -0
  207. package/dist/usecases/ingest/pass5-govern.js.map +1 -0
  208. package/dist/usecases/ingest/pipeline.d.ts +28 -0
  209. package/dist/usecases/ingest/pipeline.js +144 -0
  210. package/dist/usecases/ingest/pipeline.js.map +1 -0
  211. package/dist/usecases/ingest/publish-command.d.ts +4 -0
  212. package/dist/usecases/ingest/publish-command.js +77 -0
  213. package/dist/usecases/ingest/publish-command.js.map +1 -0
  214. package/dist/usecases/ingest/status.d.ts +39 -0
  215. package/dist/usecases/ingest/status.js +155 -0
  216. package/dist/usecases/ingest/status.js.map +1 -0
  217. package/dist/usecases/ingest/video-pass3.d.ts +26 -0
  218. package/dist/usecases/ingest/video-pass3.js +0 -0
  219. package/dist/usecases/ingest/video-pass3.js.map +1 -0
  220. package/dist/usecases/ingest/video-pipeline.d.ts +32 -0
  221. package/dist/usecases/ingest/video-pipeline.js +202 -0
  222. package/dist/usecases/ingest/video-pipeline.js.map +1 -0
  223. package/dist/usecases/ingest/view-command.d.ts +4 -0
  224. package/dist/usecases/ingest/view-command.js +51 -0
  225. package/dist/usecases/ingest/view-command.js.map +1 -0
  226. package/dist/usecases/script/actions.js +2 -2
  227. package/dist/usecases/script/actions.js.map +1 -1
  228. package/dist/usecases/script/actor.js.map +1 -1
  229. package/dist/usecases/script/actors.js.map +1 -1
  230. package/dist/usecases/script/add-actor.js.map +1 -1
  231. package/dist/usecases/script/add-episode.js.map +1 -1
  232. package/dist/usecases/script/add-location.js.map +1 -1
  233. package/dist/usecases/script/add-prop.js.map +1 -1
  234. package/dist/usecases/script/alias.js.map +1 -1
  235. package/dist/usecases/script/assets.js.map +1 -1
  236. package/dist/usecases/script/context.js.map +1 -1
  237. package/dist/usecases/script/create.js +3 -6
  238. package/dist/usecases/script/create.js.map +1 -1
  239. package/dist/usecases/script/delete.js.map +1 -1
  240. package/dist/usecases/script/describe.js.map +1 -1
  241. package/dist/usecases/script/dialogue.js +6 -13
  242. package/dist/usecases/script/dialogue.js.map +1 -1
  243. package/dist/usecases/script/do.js +1 -1
  244. package/dist/usecases/script/do.js.map +1 -1
  245. package/dist/usecases/script/episodes.js.map +1 -1
  246. package/dist/usecases/script/{add-speaker.d.ts → importance.d.ts} +1 -1
  247. package/dist/usecases/script/importance.js +17 -0
  248. package/dist/usecases/script/importance.js.map +1 -0
  249. package/dist/usecases/script/insert.js.map +1 -1
  250. package/dist/usecases/script/issues.js.map +1 -1
  251. package/dist/usecases/script/lib.d.ts +8 -6
  252. package/dist/usecases/script/lib.js +81 -97
  253. package/dist/usecases/script/lib.js.map +1 -1
  254. package/dist/usecases/script/locations.js.map +1 -1
  255. package/dist/usecases/script/merge.js.map +1 -1
  256. package/dist/usecases/script/move.js.map +1 -1
  257. package/dist/usecases/script/props.js.map +1 -1
  258. package/dist/usecases/script/refs.js.map +1 -1
  259. package/dist/usecases/script/rename.js.map +1 -1
  260. package/dist/usecases/script/replace.js.map +1 -1
  261. package/dist/usecases/script/role.js.map +1 -1
  262. package/dist/usecases/script/scenes.js +8 -15
  263. package/dist/usecases/script/scenes.js.map +1 -1
  264. package/dist/usecases/script/script-patch.js +1 -1
  265. package/dist/usecases/script/script-patch.js.map +1 -1
  266. package/dist/usecases/script/script-validate.js +1 -1
  267. package/dist/usecases/script/script-validate.js.map +1 -1
  268. package/dist/usecases/script/session.d.ts +4 -4
  269. package/dist/usecases/script/session.js +57 -78
  270. package/dist/usecases/script/session.js.map +1 -1
  271. package/dist/usecases/script/split.js.map +1 -1
  272. package/dist/usecases/script/state-add.js.map +1 -1
  273. package/dist/usecases/script/state-delete.js.map +1 -1
  274. package/dist/usecases/script/state-rename.js.map +1 -1
  275. package/dist/usecases/script/summary.js.map +1 -1
  276. package/dist/usecases/script/synopsis-generate.d.ts +4 -5
  277. package/dist/usecases/script/synopsis-generate.js +30 -46
  278. package/dist/usecases/script/synopsis-generate.js.map +1 -1
  279. package/dist/usecases/script/transition.js.map +1 -1
  280. package/dist/usecases/script/type.js.map +1 -1
  281. package/dist/usecases/script/worldview.js.map +1 -1
  282. package/package.json +22 -17
  283. package/skills/scriptctl/SKILL.md +140 -120
  284. package/skills/scriptctl/references/atomic-write-workflow.md +12 -9
  285. package/skills/scriptctl/references/ingest-workflow.md +85 -0
  286. package/skills/scriptctl/references/state-reference-repair.md +26 -39
  287. package/dist/domain/direct/runner.d.ts +0 -25
  288. package/dist/domain/direct/runner.js +0 -88
  289. package/dist/domain/direct/runner.js.map +0 -1
  290. package/dist/domain/direct/stage.d.ts +0 -108
  291. package/dist/domain/direct/stage.js +0 -134
  292. package/dist/domain/direct/stage.js.map +0 -1
  293. package/dist/domain/direct/stages/asset-curation.d.ts +0 -2
  294. package/dist/domain/direct/stages/asset-curation.js +0 -10
  295. package/dist/domain/direct/stages/asset-curation.js.map +0 -1
  296. package/dist/domain/direct/stages/batch-extract.d.ts +0 -2
  297. package/dist/domain/direct/stages/batch-extract.js +0 -12
  298. package/dist/domain/direct/stages/batch-extract.js.map +0 -1
  299. package/dist/domain/direct/stages/batch-plan.d.ts +0 -2
  300. package/dist/domain/direct/stages/batch-plan.js +0 -9
  301. package/dist/domain/direct/stages/batch-plan.js.map +0 -1
  302. package/dist/domain/direct/stages/episode-merge.d.ts +0 -2
  303. package/dist/domain/direct/stages/episode-merge.js +0 -11
  304. package/dist/domain/direct/stages/episode-merge.js.map +0 -1
  305. package/dist/domain/direct/stages/episode-plan.d.ts +0 -2
  306. package/dist/domain/direct/stages/episode-plan.js +0 -9
  307. package/dist/domain/direct/stages/episode-plan.js.map +0 -1
  308. package/dist/domain/direct/stages/episode-synopsis.d.ts +0 -2
  309. package/dist/domain/direct/stages/episode-synopsis.js +0 -12
  310. package/dist/domain/direct/stages/episode-synopsis.js.map +0 -1
  311. package/dist/domain/direct/stages/episode-titles.d.ts +0 -2
  312. package/dist/domain/direct/stages/episode-titles.js +0 -12
  313. package/dist/domain/direct/stages/episode-titles.js.map +0 -1
  314. package/dist/domain/direct/stages/index.d.ts +0 -19
  315. package/dist/domain/direct/stages/index.js +0 -41
  316. package/dist/domain/direct/stages/index.js.map +0 -1
  317. package/dist/domain/direct/stages/metadata.d.ts +0 -2
  318. package/dist/domain/direct/stages/metadata.js +0 -11
  319. package/dist/domain/direct/stages/metadata.js.map +0 -1
  320. package/dist/domain/direct/stages/script-merge.d.ts +0 -2
  321. package/dist/domain/direct/stages/script-merge.js +0 -15
  322. package/dist/domain/direct/stages/script-merge.js.map +0 -1
  323. package/dist/domain/direct/stages/script-synopsis.d.ts +0 -2
  324. package/dist/domain/direct/stages/script-synopsis.js +0 -12
  325. package/dist/domain/direct/stages/script-synopsis.js.map +0 -1
  326. package/dist/domain/direct/stages/source-prepare.d.ts +0 -2
  327. package/dist/domain/direct/stages/source-prepare.js +0 -9
  328. package/dist/domain/direct/stages/source-prepare.js.map +0 -1
  329. package/dist/domain/direct/stages/state-binding.d.ts +0 -2
  330. package/dist/domain/direct/stages/state-binding.js +0 -9
  331. package/dist/domain/direct/stages/state-binding.js.map +0 -1
  332. package/dist/domain/direct/stages/state-curation.d.ts +0 -2
  333. package/dist/domain/direct/stages/state-curation.js +0 -10
  334. package/dist/domain/direct/stages/state-curation.js.map +0 -1
  335. package/dist/domain/direct/stages/validate.d.ts +0 -2
  336. package/dist/domain/direct/stages/validate.js +0 -10
  337. package/dist/domain/direct/stages/validate.js.map +0 -1
  338. package/dist/domain/direct-core.d.ts +0 -313
  339. package/dist/domain/direct-core.js +0 -8514
  340. package/dist/domain/direct-core.js.map +0 -1
  341. package/dist/domain/script/validate.d.ts +0 -22
  342. package/dist/domain/script/validate.js +0 -997
  343. package/dist/domain/script/validate.js.map +0 -1
  344. package/dist/infra/providers.d.ts +0 -97
  345. package/dist/infra/providers.js +0 -1396
  346. package/dist/infra/providers.js.map +0 -1
  347. package/dist/usecases/direct.d.ts +0 -27
  348. package/dist/usecases/direct.js +0 -4172
  349. package/dist/usecases/direct.js.map +0 -1
  350. package/dist/usecases/parse.d.ts +0 -15
  351. package/dist/usecases/parse.js +0 -429
  352. package/dist/usecases/parse.js.map +0 -1
  353. package/dist/usecases/script/add-speaker.js +0 -22
  354. package/dist/usecases/script/add-speaker.js.map +0 -1
  355. package/dist/usecases/script/export.d.ts +0 -3
  356. package/dist/usecases/script/export.js +0 -111
  357. package/dist/usecases/script/export.js.map +0 -1
  358. package/dist/usecases/script/overlap.d.ts +0 -3
  359. package/dist/usecases/script/overlap.js +0 -21
  360. package/dist/usecases/script/overlap.js.map +0 -1
  361. package/dist/usecases/script/speakers.d.ts +0 -3
  362. package/dist/usecases/script/speakers.js +0 -28
  363. package/dist/usecases/script/speakers.js.map +0 -1
  364. package/dist/usecases/script/state-change.d.ts +0 -3
  365. package/dist/usecases/script/state-change.js +0 -24
  366. package/dist/usecases/script/state-change.js.map +0 -1
  367. package/skills/scriptctl/references/direct-workflow.md +0 -72
@@ -0,0 +1,431 @@
1
+ // Review HTML renderer (§4 view / §10 pass6). Pure: Script v3 doc → the verified
2
+ // cezhi-verify review UI (dual-tab 剧本审阅 / 资产总览, left video + right script
3
+ // rows, click a row to seek, per-actor state×episode matrix, scene chips). The
4
+ // HTML/CSS/JS is ported VERBATIM from the scaffold's 06_build_review_html; only
5
+ // the data it reads is transformed here from v3 into the shape that UI expects
6
+ // (asset_name / actor_names / actor_states / scene_num / location_name /
7
+ // action.extend.timestamp). Video is referenced relatively as src/ep_NNN.mp4
8
+ // (the view command symlinks the media into the workspace).
9
+ const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
10
+ const isArr = (v) => Array.isArray(v);
11
+ const S = (v) => (typeof v === "string" ? v : "");
12
+ const SPACE_EN_TO_ZH = { interior: "内", exterior: "外" };
13
+ const TIME_EN_TO_ZH = { day: "日", night: "夜" };
14
+ const epNum = (episodeId) => { const m = episodeId.match(/(\d+)/); return m ? Number(m[1]) : 0; };
15
+ const scnNum = (sceneId) => { const m = sceneId.match(/(\d+)/); return m ? Number(m[1]) : 0; };
16
+ function nameMap(list, idField, nameField) {
17
+ const m = new Map();
18
+ if (isArr(list))
19
+ for (const a of list)
20
+ if (isObj(a) && typeof a[idField] === "string")
21
+ m.set(a[idField], S(a[nameField]));
22
+ return m;
23
+ }
24
+ function stateNameMap(list, idField) {
25
+ const m = new Map();
26
+ if (isArr(list))
27
+ for (const a of list) {
28
+ if (!isObj(a) || typeof a[idField] !== "string")
29
+ continue;
30
+ const sm = new Map();
31
+ for (const st of isArr(a.states) ? a.states : [])
32
+ if (isObj(st) && typeof st.state_id === "string")
33
+ sm.set(st.state_id, S(st.state_name));
34
+ m.set(a[idField], sm);
35
+ }
36
+ return m;
37
+ }
38
+ // v3 speaker → display string (actor name, or kind[:label]).
39
+ function speakerLabel(speaker, actorNames) {
40
+ if (!isObj(speaker))
41
+ return "";
42
+ if (typeof speaker.actor_id === "string")
43
+ return actorNames.get(speaker.actor_id) ?? speaker.actor_id;
44
+ if (typeof speaker.kind === "string")
45
+ return `${speaker.kind}${speaker.label ? `:${S(speaker.label)}` : ""}`;
46
+ return "";
47
+ }
48
+ // Transform a v3 script into the scaffold review-model consumed by the UI JS.
49
+ function toReviewModel(doc) {
50
+ const actorNames = nameMap(doc.actors, "actor_id", "actor_name");
51
+ const locNames = nameMap(doc.locations, "location_id", "location_name");
52
+ const propNames = nameMap(doc.props, "prop_id", "prop_name");
53
+ const actorStates = stateNameMap(doc.actors, "actor_id");
54
+ const episodes = (isArr(doc.episodes) ? doc.episodes : []).filter(isObj).map((ep) => ({
55
+ episode: epNum(S(ep.episode_id)),
56
+ synopsis: S(ep.synopsis),
57
+ scenes: (isArr(ep.scenes) ? ep.scenes : []).filter(isObj).map((sc) => {
58
+ const loc = isObj(sc.location) ? (locNames.get(S(sc.location.location_id)) ?? S(sc.location.location_id)) : "";
59
+ const anames = [];
60
+ const astates = {};
61
+ for (const r of isArr(sc.actors) ? sc.actors : []) {
62
+ if (!isObj(r) || typeof r.actor_id !== "string")
63
+ continue;
64
+ const nm = actorNames.get(r.actor_id) ?? r.actor_id;
65
+ if (!anames.includes(nm))
66
+ anames.push(nm);
67
+ if (typeof r.state_id === "string" && r.state_id !== "default")
68
+ astates[nm] = actorStates.get(r.actor_id)?.get(r.state_id) ?? r.state_id;
69
+ }
70
+ const pnames = [];
71
+ for (const r of isArr(sc.props) ? sc.props : [])
72
+ if (isObj(r) && typeof r.prop_id === "string") {
73
+ const nm = propNames.get(r.prop_id) ?? r.prop_id;
74
+ if (!pnames.includes(nm))
75
+ pnames.push(nm);
76
+ }
77
+ const env = isObj(sc.environment) ? sc.environment : {};
78
+ return {
79
+ scene_num: scnNum(S(sc.scene_id)),
80
+ location_name: loc,
81
+ environment: { space: SPACE_EN_TO_ZH[S(env.space)] ?? S(env.space), time: TIME_EN_TO_ZH[S(env.time)] ?? S(env.time) },
82
+ actor_names: anames,
83
+ actor_states: astates,
84
+ prop_names: pnames,
85
+ actions: (isArr(sc.actions) ? sc.actions : []).filter(isObj).map((a) => ({
86
+ type: S(a.type),
87
+ content: S(a.content),
88
+ speaker: speakerLabel(a.speaker, actorNames),
89
+ emotion: typeof a.emotion === "string" ? a.emotion : undefined,
90
+ extend: isObj(a.extend) && typeof a.extend.timestamp === "string" ? { timestamp: a.extend.timestamp } : undefined,
91
+ })),
92
+ };
93
+ }),
94
+ }));
95
+ const actors = (isArr(doc.actors) ? doc.actors : []).filter(isObj).map((a) => ({
96
+ asset_name: S(a.actor_name),
97
+ description: S(a.description),
98
+ extend: {},
99
+ }));
100
+ return { title: S(doc.title), actors, episodes };
101
+ }
102
+ export function renderReviewHtml(doc) {
103
+ if (!isObj(doc))
104
+ throw new Error("review renderer expects a script object");
105
+ const model = toReviewModel(doc);
106
+ const data = JSON.stringify(model);
107
+ const title = S(model.title);
108
+ return `<!DOCTYPE html>
109
+ <html lang="zh">
110
+ <head>
111
+ <meta charset="utf-8">
112
+ <title>剧本审阅器 · ${title}</title>
113
+ <style>
114
+ * { box-sizing: border-box; }
115
+ html, body { margin: 0; height: 100%; font: 14px/1.6 -apple-system, "PingFang SC", system-ui, sans-serif; color: #1a1a1a; }
116
+ body { display: flex; flex-direction: column; height: 100vh; background: #f5f5f7; }
117
+ header { display: flex; align-items: center; gap: 12px; padding: 8px 14px; background: #fff; border-bottom: 1px solid #ddd; flex: none; }
118
+ header h1 { font-size: 15px; margin: 0; font-weight: 600; }
119
+ header select, header .nav { font-size: 13px; padding: 4px 8px; border: 1px solid #ccc; border-radius: 6px; background: #fff; cursor: pointer; }
120
+ header .spacer { flex: 1; }
121
+ header .hint { color: #888; font-size: 12px; }
122
+ main { flex: 1; display: flex; min-height: 0; }
123
+ .left { width: 42%; min-width: 320px; max-width: 620px; background: #000; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; position: relative; }
124
+ .left video { width: 100%; height: auto; max-height: 72vh; background: #000; }
125
+ .vbar { color: #ccc; font-size: 12px; padding: 6px 10px; width: 100%; display: flex; gap: 12px; align-items: center; background: #111; }
126
+ .vbar b { color: #fff; font-variant-numeric: tabular-nums; }
127
+ .right { flex: 1; overflow-y: auto; padding: 10px 4px 40vh 4px; background: #fafafa; }
128
+ .syn { background: #fff7e6; border: 1px solid #ffe0a3; border-radius: 8px; padding: 10px 12px; margin: 8px 10px 14px; color: #5b4a1f; font-size: 13px; }
129
+ .scene { margin: 0 10px 14px; border: 1px solid #e5e5e5; border-radius: 8px; overflow: hidden; background: #fff; }
130
+ .scene-head { background: #eef2f7; padding: 6px 12px; font-size: 12px; color: #445; display: flex; gap: 14px; flex-wrap: wrap; }
131
+ .scene-head .loc { font-weight: 600; color: #223; }
132
+ .rows { }
133
+ .row { display: flex; gap: 10px; padding: 5px 12px; border-top: 1px solid #f0f0f0; cursor: pointer; align-items: baseline; }
134
+ .row:hover { background: #eef6ff; }
135
+ .row.active { background: #d8ecff; }
136
+ .row .ts { flex: none; width: 46px; color: #3a7; font-variant-numeric: tabular-nums; font-size: 12px; }
137
+ .row .kind { flex: none; width: 58px; font-size: 12px; }
138
+ .row .kind.act { color: #999; }
139
+ .row .kind.dlg { color: #c60; font-weight: 600; }
140
+ .row .kind.think { color: #96c; font-style: italic; }
141
+ .row .body { flex: 1; }
142
+ .row .spk { color: #c60; font-weight: 600; margin-right: 4px; }
143
+ .row .emo { color: #a8a; font-size: 12px; margin-left: 4px; }
144
+ .row .body .subtitle { color: #b00; } /* 画面字幕引述,红色标出 */
145
+ /* ===== Tab ===== */
146
+ header .tabs { display: flex; gap: 4px; }
147
+ header .tab { font-size: 13px; padding: 5px 14px; border: 1px solid #ccc; border-radius: 6px; background: #fff; cursor: pointer; color: #555; }
148
+ header .tab.active { background: #1a6dff; color: #fff; border-color: #1a6dff; font-weight: 600; }
149
+ header .ep-ctl { display: flex; gap: 8px; align-items: center; }
150
+ /* ===== 资产总览 ===== */
151
+ .assets { flex: 1; overflow-y: auto; padding: 16px 18px 30vh; background: #f5f5f7; }
152
+ .asset-sec { margin: 0 0 26px; }
153
+ .asset-sec > h2 { font-size: 15px; margin: 4px 0 12px; color: #223; display: flex; align-items: center; gap: 8px; }
154
+ .asset-sec > h2 .cnt { font-size: 12px; font-weight: 400; color: #888; background: #ececf0; padding: 1px 8px; border-radius: 10px; }
155
+ .acard { background: #fff; border: 1px solid #e3e3e6; border-radius: 10px; padding: 14px 16px; margin-bottom: 12px; }
156
+ .acard .ah { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
157
+ .acard .ah .nm { font-size: 17px; font-weight: 700; color: #1a1a1a; }
158
+ .acard .ah .id { font-size: 12px; color: #aaa; font-variant-numeric: tabular-nums; }
159
+ .acard .ah .meta { margin-left: auto; font-size: 12px; color: #888; display: flex; gap: 6px; }
160
+ .acard .ah .meta .tag { background: #f0f0f3; border-radius: 6px; padding: 1px 8px; }
161
+ .acard .desc { font-size: 13px; color: #555; margin: 7px 0 4px; }
162
+ .acard .anchor { font-size: 12px; color: #999; margin-bottom: 8px; }
163
+ /* 状态×集 矩阵 */
164
+ .mtx { overflow-x: auto; margin-top: 8px; border: 1px solid #eee; border-radius: 8px; }
165
+ table.tl { border-collapse: collapse; font-size: 12px; width: 100%; }
166
+ table.tl th, table.tl td { border: 1px solid #eee; padding: 5px 7px; text-align: center; white-space: nowrap; }
167
+ table.tl thead th { background: #f7f7f9; color: #556; font-weight: 600; position: sticky; top: 0; }
168
+ table.tl .stname { text-align: left; font-weight: 600; color: #333; background: #fafafa; position: sticky; left: 0; min-width: 130px; max-width: 220px; white-space: normal; }
169
+ table.tl .stname.unmk { color: #999; font-weight: 400; }
170
+ table.tl .stname .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
171
+ table.tl .stname .sc { color: #aaa; font-weight: 400; font-size: 11px; margin-left: 4px; }
172
+ table.tl td.cell { color: #ccc; }
173
+ table.tl td.hit { background: #fff0f0; }
174
+ table.tl td.hit.unmk { background: #f2f2f2; }
175
+ .scc { display: inline-block; min-width: 20px; margin: 1px; padding: 1px 6px; border-radius: 6px; font-variant-numeric: tabular-nums; cursor: pointer; }
176
+ .scc.on { background: #f4c9c9; color: #7a1f1f; border: 1px solid #e8a9a9; }
177
+ .scc.on.unmk { background: #dcdcdc; color: #555; border-color: #c9c9c9; }
178
+ .scc.on:hover { filter: brightness(0.94); }
179
+ </style>
180
+ </head>
181
+ <body>
182
+ <header>
183
+ <h1>${title || "剧本审阅器"}</h1>
184
+ <div class="tabs">
185
+ <button class="tab active" id="tab-script">剧本审阅</button>
186
+ <button class="tab" id="tab-assets">资产总览</button>
187
+ </div>
188
+ <span class="ep-ctl">
189
+ <select id="epsel"></select>
190
+ <button class="nav" id="prev">◀ 上一集</button>
191
+ <button class="nav" id="next">下一集 ▶</button>
192
+ </span>
193
+ <span class="spacer"></span>
194
+ <span class="hint" id="hint">点任意剧本行 → 视频跳到该时间戳</span>
195
+ </header>
196
+ <main id="view-script">
197
+ <div class="left">
198
+ <video id="vid" controls preload="metadata"></video>
199
+ <div class="vbar"><span>当前 <b id="cur">00:00</b></span><span id="epinfo"></span></div>
200
+ </div>
201
+ <div class="right" id="right"></div>
202
+ </main>
203
+ <div id="view-assets" class="assets" style="display:none"></div>
204
+ <script>
205
+ const SCRIPT = ${data};
206
+ const eps = SCRIPT.episodes.slice().sort((a,b)=>a.episode-b.episode);
207
+ const vid = document.getElementById('vid');
208
+ const right = document.getElementById('right');
209
+ const epsel = document.getElementById('epsel');
210
+ const cur = document.getElementById('cur');
211
+ const epinfo = document.getElementById('epinfo');
212
+ let curEp = eps.length?eps[0].episode:1;
213
+
214
+ function pad(n){return String(n).padStart(2,'0');}
215
+ function fmt(sec){sec=Math.max(0,sec|0);return pad(sec/60|0)+':'+pad(sec%60);}
216
+ function tsToSec(ts){if(!ts)return null;const m=String(ts).match(/(?:(\\d{1,2}):)?(\\d{1,2}):(\\d{2}(?:\\.\\d{1,3})?)/);return m?(+(m[1]||0))*3600+(+m[2])*60+(+m[3]):null;}
217
+ function epId(n){return 'ep_'+String(n).padStart(3,'0');}
218
+ function actKind(t){return t==='dialogue'?'dlg':t==='inner_thought'?'think':'act';}
219
+ function actTs(a){return a && a.extend ? a.extend.timestamp : undefined;}
220
+
221
+ function renderBody(content){
222
+ const esc = content.replace(/&/g,'&amp;').replace(/</g,'&lt;');
223
+ return esc.replace(/(画面(?:字幕|文字)[::][^。!?\\n]*)/g,'<span class="subtitle">$1</span>');
224
+ }
225
+
226
+ for(const e of eps){
227
+ const o=document.createElement('option');
228
+ o.value=e.episode; o.textContent='第 '+e.episode+' 集';
229
+ epsel.appendChild(o);
230
+ }
231
+
232
+ function loadEp(n, targetScene){
233
+ curEp=n;
234
+ epsel.value=n;
235
+ vid.src='src/'+epId(n)+'.mp4';
236
+ const ep=eps.find(e=>e.episode===n);
237
+ if(!ep)return;
238
+ epinfo.textContent='第'+n+'集 · '+ep.scenes.length+'场';
239
+ right.innerHTML='';
240
+ if(ep.synopsis){const s=document.createElement('div');s.className='syn';s.textContent='梗概:'+ep.synopsis;right.appendChild(s);}
241
+ for(const sc of ep.scenes){
242
+ const box=document.createElement('div');box.className='scene';
243
+ box.dataset.scene=sc.scene_num;
244
+ const head=document.createElement('div');head.className='scene-head';
245
+ const env=(sc.environment&&(sc.environment.space||'')+' '+(sc.environment.time||''))||'';
246
+ const chNames=sc.actor_names||[]; const chSt=sc.actor_states||{};
247
+ head.innerHTML='<span class="loc">场景'+sc.scene_num+' · '+(sc.location_name||'')+'</span>'+
248
+ '<span>'+env+'</span>'+
249
+ (chNames.length?'<span>角色: '+chNames.map(nm=>nm+(chSt[nm]?'('+chSt[nm]+')':'')).join('、')+'</span>':'');
250
+ box.appendChild(head);
251
+ const rows=document.createElement('div');rows.className='rows';
252
+ for(const a of sc.actions){
253
+ const ts=actTs(a); const kd=actKind(a.type); const sec=tsToSec(ts);
254
+ const row=document.createElement('div');row.className='row';row.dataset.sec=sec==null?'':sec;
255
+ const spk=a.speaker?'<span class="spk">'+a.speaker+'</span>':'';
256
+ const emo=a.emotion?'<span class="emo">['+a.emotion+']</span>':'';
257
+ row.innerHTML='<span class="ts">'+(ts||'')+'</span>'+
258
+ '<span class="kind '+kd+'">'+kd+'</span>'+
259
+ '<span class="body">'+spk+renderBody(a.content)+emo+'</span>';
260
+ if(sec!=null)row.addEventListener('click',()=>{vid.currentTime=sec;vid.play();});
261
+ rows.appendChild(row);
262
+ }
263
+ box.appendChild(rows);right.appendChild(box);
264
+ }
265
+ if(targetScene){
266
+ const tb=right.querySelector('.scene[data-scene="'+targetScene+'"]');
267
+ if(tb){tb.scrollIntoView({block:'start'}); tb.style.outline='2px solid #1a6dff'; setTimeout(()=>tb.style.outline='',1600);}
268
+ } else { right.scrollTop=0; }
269
+ }
270
+
271
+ vid.addEventListener('timeupdate',()=>{
272
+ cur.textContent=fmt(vid.currentTime);
273
+ const t=vid.currentTime;
274
+ const rows=[...right.querySelectorAll('.row')].filter(r=>r.dataset.sec!=='');
275
+ let active=null;
276
+ for(const r of rows){ if(+r.dataset.sec<=t+0.25) active=r; else break; }
277
+ right.querySelectorAll('.row.active').forEach(r=>r.classList.remove('active'));
278
+ if(active){active.classList.add('active');}
279
+ });
280
+
281
+ epsel.addEventListener('change',()=>loadEp(+epsel.value));
282
+ document.getElementById('prev').addEventListener('click',()=>{const i=eps.findIndex(e=>e.episode===curEp);if(i>0)loadEp(eps[i-1].episode);});
283
+ document.getElementById('next').addEventListener('click',()=>{const i=eps.findIndex(e=>e.episode===curEp);if(i<eps.length-1)loadEp(eps[i+1].episode);});
284
+
285
+ if(eps.length)loadEp(curEp);
286
+
287
+ // ===================== 资产总览 =====================
288
+ const allEps = eps.map(e=>e.episode).sort((a,b)=>a-b);
289
+ const actorMeta = {};
290
+ for(const a of (SCRIPT.actors||[])){
291
+ const ext=a.extend||{};
292
+ actorMeta[a.asset_name]={role:a.description||'',anchor:ext.anchor||''};
293
+ }
294
+
295
+ function aggAssets(){
296
+ const person={}, loc={}, prop={};
297
+ const addP=(nm,st,n,idx)=>{(person[nm]=person[nm]||{});(person[nm][st]=person[nm][st]||new Map()).set(n+':'+idx,{n,idx});};
298
+ const addX=(bag,nm,n,idx)=>{(bag[nm]=bag[nm]||new Map()).set(n+':'+idx,{n,idx});};
299
+ for(const ep of eps){ const n=ep.episode;
300
+ for(const sc of ep.scenes){ const idx=sc.scene_num;
301
+ const names=sc.actor_names||[]; const st=sc.actor_states||{};
302
+ for(const nm of names) addP(nm, st[nm]||'未标注', n, idx);
303
+ if(sc.location_name) addX(loc, sc.location_name, n, idx);
304
+ for(const p of (sc.prop_names||[])) addX(prop, p, n, idx);
305
+ }
306
+ }
307
+ return {person,loc,prop};
308
+ }
309
+ const AG = aggAssets();
310
+
311
+ function epsOf(occMaps){ const s=new Set(); for(const m of occMaps) for(const {n} of m.values()) s.add(n); return [...s].sort((a,b)=>a-b); }
312
+ function sceneChips(occMap, unmk){
313
+ const byEp={}; for(const {n,idx} of occMap.values()){(byEp[n]=byEp[n]||[]).push(idx);}
314
+ return byEp;
315
+ }
316
+ function chipEl(n, idx, unmk){
317
+ const c=document.createElement('span'); c.className='scc on'+(unmk?' unmk':''); c.textContent=idx;
318
+ c.title='第'+n+'集 · 场'+idx+' → 跳转';
319
+ c.addEventListener('click',(e)=>{e.stopPropagation();gotoScene(n,idx);});
320
+ return c;
321
+ }
322
+ function gotoScene(n, idx){ switchTab('script'); loadEp(n, idx); }
323
+
324
+ function personCard(name){
325
+ const states=AG.person[name]; const stKeys=Object.keys(states);
326
+ const involved=epsOf(Object.values(states));
327
+ const card=document.createElement('div'); card.className='acard';
328
+ const meta=actorMeta[name]||{};
329
+ const totalScenes=new Set(); for(const m of Object.values(states)) for(const k of m.keys()) totalScenes.add(k);
330
+ const head=document.createElement('div'); head.className='ah';
331
+ head.innerHTML='<span class="nm">'+esc(name)+'</span>'+
332
+ '<span class="meta"><span class="tag">'+totalScenes.size+' 场</span><span class="tag">'+involved.length+' 集</span><span class="tag">'+stKeys.length+' 状态</span></span>';
333
+ card.appendChild(head);
334
+ if(meta.role){const d=document.createElement('div');d.className='desc';d.textContent=meta.role;card.appendChild(d);}
335
+ if(meta.anchor){const d=document.createElement('div');d.className='anchor';d.textContent='锚点:'+meta.anchor;card.appendChild(d);}
336
+ const wrap=document.createElement('div'); wrap.className='mtx';
337
+ const tb=document.createElement('table'); tb.className='tl';
338
+ let thead='<thead><tr><th class="stname">状态\集</th>';
339
+ for(const n of involved) thead+='<th>'+n+'</th>';
340
+ thead+='</tr></thead>';
341
+ const ordered=stKeys.filter(s=>s!=='未标注').sort((a,b)=>states[b].size-states[a].size);
342
+ if(states['未标注']) ordered.push('未标注');
343
+ const tbody=document.createElement('tbody');
344
+ for(const st of ordered){
345
+ const unmk=st==='未标注';
346
+ const byEp=sceneChips(states[st], unmk);
347
+ const tr=document.createElement('tr');
348
+ const th=document.createElement('td'); th.className='stname'+(unmk?' unmk':'');
349
+ th.innerHTML='<span class="dot" style="background:'+(unmk?'#bbb':'#d9534f')+'"></span>'+esc(st)+'<span class="sc">'+states[st].size+'</span>';
350
+ tr.appendChild(th);
351
+ for(const n of involved){
352
+ const td=document.createElement('td'); td.className='cell'+(byEp[n]?' hit'+(unmk?' unmk':''):'');
353
+ if(byEp[n]){ for(const idx of byEp[n].sort((a,b)=>a-b)) td.appendChild(chipEl(n,idx,unmk)); }
354
+ tr.appendChild(td);
355
+ }
356
+ tbody.appendChild(tr);
357
+ }
358
+ tb.innerHTML=thead; tb.appendChild(tbody);
359
+ wrap.appendChild(tb); card.appendChild(wrap);
360
+ return card;
361
+ }
362
+
363
+ function occCard(name, occMap){
364
+ const involved=epsOf([occMap]);
365
+ const card=document.createElement('div'); card.className='acard';
366
+ const head=document.createElement('div'); head.className='ah';
367
+ head.innerHTML='<span class="nm">'+esc(name)+'</span>'+
368
+ '<span class="meta"><span class="tag">'+occMap.size+' 场</span><span class="tag">'+involved.length+' 集</span></span>';
369
+ card.appendChild(head);
370
+ const wrap=document.createElement('div'); wrap.className='mtx';
371
+ const tb=document.createElement('table'); tb.className='tl';
372
+ let thead='<thead><tr><th class="stname">出现\集</th>';
373
+ for(const n of involved) thead+='<th>'+n+'</th>'; thead+='</tr></thead>';
374
+ const byEp=sceneChips(occMap,false);
375
+ const tbody=document.createElement('tbody'); const tr=document.createElement('tr');
376
+ const th=document.createElement('td'); th.className='stname';
377
+ th.innerHTML='<span class="dot" style="background:#5b8def"></span>出现';
378
+ tr.appendChild(th);
379
+ for(const n of involved){ const td=document.createElement('td'); td.className='cell'+(byEp[n]?' hit':'');
380
+ if(byEp[n]){ for(const idx of byEp[n].sort((a,b)=>a-b)) td.appendChild(chipEl(n,idx,false)); } tr.appendChild(td);}
381
+ tbody.appendChild(tr); tb.innerHTML=thead; tb.appendChild(tbody);
382
+ wrap.appendChild(tb); card.appendChild(wrap);
383
+ return card;
384
+ }
385
+
386
+ function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;');}
387
+
388
+ let assetsRendered=false;
389
+ function renderAssets(){
390
+ if(assetsRendered) return; assetsRendered=true;
391
+ const root=document.getElementById('view-assets');
392
+ const persons=Object.keys(AG.person).sort((a,b)=>{
393
+ const ea=new Set(),eb=new Set();
394
+ for(const m of Object.values(AG.person[a])) for(const {n} of m.values()) ea.add(n);
395
+ for(const m of Object.values(AG.person[b])) for(const {n} of m.values()) eb.add(n);
396
+ return eb.size-ea.size;
397
+ });
398
+ const secP=document.createElement('div'); secP.className='asset-sec';
399
+ secP.innerHTML='<h2>人物 <span class="cnt">'+persons.length+'</span></h2>';
400
+ for(const nm of persons) secP.appendChild(personCard(nm));
401
+ root.appendChild(secP);
402
+ const locs=Object.keys(AG.loc).sort((a,b)=>AG.loc[b].size-AG.loc[a].size);
403
+ const secL=document.createElement('div'); secL.className='asset-sec';
404
+ secL.innerHTML='<h2>场景 <span class="cnt">'+locs.length+'</span></h2>';
405
+ for(const nm of locs) secL.appendChild(occCard(nm,AG.loc[nm]));
406
+ root.appendChild(secL);
407
+ const props=Object.keys(AG.prop).sort((a,b)=>AG.prop[b].size-AG.prop[a].size);
408
+ const secD=document.createElement('div'); secD.className='asset-sec';
409
+ secD.innerHTML='<h2>道具 <span class="cnt">'+props.length+'</span></h2>';
410
+ for(const nm of props) secD.appendChild(occCard(nm,AG.prop[nm]));
411
+ root.appendChild(secD);
412
+ }
413
+
414
+ const tabScript=document.getElementById('tab-script'), tabAssets=document.getElementById('tab-assets');
415
+ const viewScript=document.getElementById('view-script'), viewAssets=document.getElementById('view-assets');
416
+ const epCtl=document.querySelector('.ep-ctl'), hint=document.getElementById('hint');
417
+ function switchTab(which){
418
+ const asset=which==='assets';
419
+ tabScript.classList.toggle('active',!asset); tabAssets.classList.toggle('active',asset);
420
+ viewScript.style.display=asset?'none':'flex'; viewAssets.style.display=asset?'block':'none';
421
+ epCtl.style.display=asset?'none':'flex';
422
+ hint.textContent=asset?'点场号 → 跳回剧本审阅对应集/场':'点任意剧本行 → 视频跳到该时间戳';
423
+ if(asset) renderAssets();
424
+ }
425
+ tabScript.addEventListener('click',()=>switchTab('script'));
426
+ tabAssets.addEventListener('click',()=>switchTab('assets'));
427
+ </script>
428
+ </body>
429
+ </html>`;
430
+ }
431
+ //# sourceMappingURL=review-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-renderer.js","sourceRoot":"","sources":["../../../src/infra/ingest/review-renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export declare const TEXT_EXTENSIONS: readonly [".txt", ".md", ".docx"];
2
+ export declare const VIDEO_MIME: Readonly<Record<string, string>>;
3
+ export type SourceKind = "text" | "video-file" | "video-directory";
4
+ export declare class UnsupportedInputError extends Error {
5
+ readonly received: string;
6
+ constructor(received: string);
7
+ }
8
+ export type ResolvedSource = {
9
+ kind: "text";
10
+ path: string;
11
+ ext: string;
12
+ } | {
13
+ kind: "video-file";
14
+ path: string;
15
+ ext: string;
16
+ } | {
17
+ kind: "video-directory";
18
+ path: string;
19
+ files: string[];
20
+ };
21
+ export declare function resolveSource(sourcePath: string): ResolvedSource;
@@ -0,0 +1,51 @@
1
+ // Ingest source resolver (§3.2/§10.1). Classifies a source path into text or
2
+ // video and enforces the strict input allowlist. PDF/JSON/XLSX are explicitly
3
+ // unsupported here (they belong to other commands), returning a clear error.
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ export const TEXT_EXTENSIONS = [".txt", ".md", ".docx"];
7
+ // Common Gemini-ingestible video containers (§10.1); extension → MIME.
8
+ export const VIDEO_MIME = {
9
+ ".mp4": "video/mp4",
10
+ ".mov": "video/mov",
11
+ ".avi": "video/avi",
12
+ ".mpeg": "video/mpeg",
13
+ ".mpg": "video/mpg",
14
+ ".webm": "video/webm",
15
+ ".flv": "video/x-flv",
16
+ ".wmv": "video/wmv",
17
+ };
18
+ export class UnsupportedInputError extends Error {
19
+ received;
20
+ constructor(received) {
21
+ super(`Unsupported input: ${received}`);
22
+ this.received = received;
23
+ }
24
+ }
25
+ function ext(p) {
26
+ return path.extname(p).toLowerCase();
27
+ }
28
+ function isVideoExt(e) {
29
+ return Object.hasOwn(VIDEO_MIME, e);
30
+ }
31
+ export function resolveSource(sourcePath) {
32
+ const stat = fs.statSync(sourcePath);
33
+ if (stat.isDirectory()) {
34
+ // Directory input is only for a set of video episodes (§10.1); scan one level.
35
+ const entries = fs.readdirSync(sourcePath)
36
+ .filter((name) => !name.startsWith("."))
37
+ .filter((name) => isVideoExt(ext(name)))
38
+ .sort()
39
+ .map((name) => path.join(sourcePath, name));
40
+ if (entries.length === 0)
41
+ throw new UnsupportedInputError(`directory has no supported video files: ${sourcePath}`);
42
+ return { kind: "video-directory", path: sourcePath, files: entries };
43
+ }
44
+ const e = ext(sourcePath);
45
+ if (TEXT_EXTENSIONS.includes(e))
46
+ return { kind: "text", path: sourcePath, ext: e };
47
+ if (isVideoExt(e))
48
+ return { kind: "video-file", path: sourcePath, ext: e };
49
+ throw new UnsupportedInputError(`file extension ${e || "(none)"} is not supported by ingest (txt/md/docx/video only)`);
50
+ }
51
+ //# sourceMappingURL=source-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-resolver.js","sourceRoot":"","sources":["../../../src/infra/ingest/source-resolver.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,8EAA8E;AAC9E,6EAA6E;AAE7E,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAU,CAAC;AAEjE,uEAAuE;AACvE,MAAM,CAAC,MAAM,UAAU,GAAqC;IAC1D,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,WAAW;CACpB,CAAC;AAIF,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IACzB;IAArB,YAAqB,QAAgB;QACnC,KAAK,CAAC,sBAAsB,QAAQ,EAAE,CAAC,CAAC;QADrB,aAAQ,GAAR,QAAQ,CAAQ;IAErC,CAAC;CACF;AAED,SAAS,GAAG,CAAC,CAAS;IACpB,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AACvC,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;AACtC,CAAC;AAOD,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QACvB,+EAA+E;QAC/E,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC;aACvC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;aACvC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;aACvC,IAAI,EAAE;aACN,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC;QAC9C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,qBAAqB,CAAC,2CAA2C,UAAU,EAAE,CAAC,CAAC;QACnH,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IACvE,CAAC;IACD,MAAM,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;IAC1B,IAAK,eAAqC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IAC1G,IAAI,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IAC3E,MAAM,IAAI,qBAAqB,CAAC,kBAAkB,CAAC,IAAI,QAAQ,sDAAsD,CAAC,CAAC;AACzH,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare class SourceDecodeError extends Error {
2
+ }
3
+ export declare function readTextSource(sourcePath: string, ext: string): Promise<string>;
@@ -0,0 +1,27 @@
1
+ // Text source reader (§pass1-prepare). Strict UTF-8 decode (rejects invalid
2
+ // bytes), BOM strip, CRLF/CR → LF. Markdown is preserved verbatim (order,
3
+ // headings, lists) — no screenplay interpretation here. DOCX reuses the existing
4
+ // OpenXML converter, then goes through the same normalization.
5
+ import * as fs from "node:fs";
6
+ import { convertDocx } from "../converters.js";
7
+ export class SourceDecodeError extends Error {
8
+ }
9
+ function normalize(text) {
10
+ return text.replace(/^/, "").replace(/\r\n?/g, "\n");
11
+ }
12
+ function decodeUtf8Strict(buf) {
13
+ try {
14
+ return new TextDecoder("utf-8", { fatal: true }).decode(buf);
15
+ }
16
+ catch {
17
+ throw new SourceDecodeError("source is not valid UTF-8");
18
+ }
19
+ }
20
+ export async function readTextSource(sourcePath, ext) {
21
+ if (ext === ".docx") {
22
+ const [text] = await convertDocx(sourcePath);
23
+ return normalize(text);
24
+ }
25
+ return normalize(decodeUtf8Strict(fs.readFileSync(sourcePath)));
26
+ }
27
+ //# sourceMappingURL=text-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-reader.js","sourceRoot":"","sources":["../../../src/infra/ingest/text-reader.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,0EAA0E;AAC1E,iFAAiF;AACjF,+DAA+D;AAE/D,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,MAAM,OAAO,iBAAkB,SAAQ,KAAK;CAAG;AAE/C,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAW;IACnC,IAAI,CAAC;QACH,OAAO,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,iBAAiB,CAAC,2BAA2B,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,UAAkB,EAAE,GAAW;IAClE,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;QAC7C,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,SAAS,CAAC,gBAAgB,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type ResolvedSource } from "./source-resolver.js";
2
+ export interface VideoEpisode {
3
+ episode: number;
4
+ path: string;
5
+ ext: string;
6
+ mime: string;
7
+ }
8
+ export declare function discoverEpisodes(source: ResolvedSource): VideoEpisode[];
@@ -0,0 +1,60 @@
1
+ // Video episode discovery (§10.1). Turns a resolved video source into a numbered
2
+ // episode list. A single file is episode 1. In a directory: if every file
3
+ // carries an episode number, reuse them (and reject duplicates); if none do,
4
+ // natural-sort and assign ep 1..N; mixing numbered and unnumbered files fails
5
+ // up front rather than guessing an order.
6
+ import * as path from "node:path";
7
+ import { CliError, EXIT_INPUT } from "../../common.js";
8
+ import { VIDEO_MIME } from "./source-resolver.js";
9
+ function ext(p) {
10
+ return path.extname(p).toLowerCase();
11
+ }
12
+ function mimeFor(p) {
13
+ return VIDEO_MIME[ext(p)] ?? "application/octet-stream";
14
+ }
15
+ // First run of digits in the basename, or null when the name carries no number.
16
+ // ponytail: first-integer heuristic; if real datasets name files with non-episode
17
+ // digits (dates, resolutions) switch to an explicit `ep(\d+)` pattern.
18
+ function episodeNumber(file) {
19
+ // Strip the extension first — `.mp4`/`.mpeg` carry digits that are not episode
20
+ // numbers.
21
+ const m = path.basename(file, path.extname(file)).match(/\d+/);
22
+ return m ? Number(m[0]) : null;
23
+ }
24
+ function discoveryError(message, received) {
25
+ return new CliError("INGEST VIDEO DISCOVERY", message, {
26
+ exitCode: EXIT_INPUT,
27
+ errorCode: "INGEST_VIDEO_DISCOVERY",
28
+ received,
29
+ nextSteps: ["Name every episode file with a number, or none of them, and rerun `scriptctl ingest`."],
30
+ });
31
+ }
32
+ function episode(file, n) {
33
+ return { episode: n, path: file, ext: ext(file), mime: mimeFor(file) };
34
+ }
35
+ export function discoverEpisodes(source) {
36
+ if (source.kind === "text") {
37
+ throw new CliError("INGEST WRONG PIPELINE", "discoverEpisodes received a text source.", { exitCode: EXIT_INPUT, errorCode: "INGEST_WRONG_PIPELINE" });
38
+ }
39
+ if (source.kind === "video-file")
40
+ return [episode(source.path, 1)];
41
+ const files = source.files; // already one-level, allowlisted, sorted (source-resolver)
42
+ const numbers = files.map(episodeNumber);
43
+ const numbered = numbers.filter((n) => n !== null);
44
+ if (numbered.length === 0) {
45
+ // None numbered: keep the resolver's natural sort, assign 1..N.
46
+ return files.map((file, i) => episode(file, i + 1));
47
+ }
48
+ if (numbered.length !== files.length) {
49
+ const bad = files.filter((_, i) => numbers[i] === null).map((f) => path.basename(f));
50
+ throw discoveryError("Some episode files carry a number and some do not.", [`unnumbered: ${bad.join(", ")}`]);
51
+ }
52
+ const seen = new Set();
53
+ for (const n of numbered) {
54
+ if (seen.has(n))
55
+ throw discoveryError("Two episode files resolve to the same number.", [`duplicate episode: ${n}`]);
56
+ seen.add(n);
57
+ }
58
+ return files.map((file, i) => episode(file, numbers[i])).sort((a, b) => a.episode - b.episode);
59
+ }
60
+ //# sourceMappingURL=video-discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-discovery.js","sourceRoot":"","sources":["../../../src/infra/ingest/video-discovery.ts"],"names":[],"mappings":"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"}