@lingjingai/scriptctl 0.49.16 → 0.50.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 (388) hide show
  1. package/README.md +8 -2
  2. package/changes/0.50.0.md +63 -0
  3. package/changes/unreleased.md +2 -1
  4. package/dist/cli.js +26 -5
  5. package/dist/cli.js.map +1 -1
  6. package/dist/domain/authoring/apply.d.ts +1 -33
  7. package/dist/domain/authoring/apply.js +1 -783
  8. package/dist/domain/authoring/apply.js.map +1 -1
  9. package/dist/domain/authoring/export.d.ts +1 -7
  10. package/dist/domain/authoring/export.js +1 -84
  11. package/dist/domain/authoring/export.js.map +1 -1
  12. package/dist/domain/authoring/parser.d.ts +2 -3
  13. package/dist/domain/authoring/parser.js +2 -914
  14. package/dist/domain/authoring/parser.js.map +1 -1
  15. package/dist/domain/authoring/serializer.d.ts +1 -2
  16. package/dist/domain/authoring/serializer.js +1 -243
  17. package/dist/domain/authoring/serializer.js.map +1 -1
  18. package/dist/domain/authoring/shared/bracket.d.ts +4 -0
  19. package/dist/domain/authoring/shared/bracket.js +95 -0
  20. package/dist/domain/authoring/shared/bracket.js.map +1 -0
  21. package/dist/domain/authoring/shared/types.d.ts +12 -0
  22. package/dist/domain/authoring/shared/types.js +2 -0
  23. package/dist/domain/authoring/shared/types.js.map +1 -0
  24. package/dist/domain/authoring/types.d.ts +2 -87
  25. package/dist/domain/authoring/types.js +2 -1
  26. package/dist/domain/authoring/types.js.map +1 -1
  27. package/dist/domain/authoring/v3/apply.d.ts +33 -0
  28. package/dist/domain/authoring/v3/apply.js +784 -0
  29. package/dist/domain/authoring/v3/apply.js.map +1 -0
  30. package/dist/domain/authoring/v3/export.d.ts +7 -0
  31. package/dist/domain/authoring/v3/export.js +85 -0
  32. package/dist/domain/authoring/v3/export.js.map +1 -0
  33. package/dist/domain/authoring/v3/parser.d.ts +3 -0
  34. package/dist/domain/authoring/v3/parser.js +823 -0
  35. package/dist/domain/authoring/v3/parser.js.map +1 -0
  36. package/dist/domain/authoring/v3/serializer.d.ts +2 -0
  37. package/dist/domain/authoring/v3/serializer.js +244 -0
  38. package/dist/domain/authoring/v3/serializer.js.map +1 -0
  39. package/dist/domain/authoring/v3/types.d.ts +76 -0
  40. package/dist/domain/authoring/v3/types.js +2 -0
  41. package/dist/domain/authoring/v3/types.js.map +1 -0
  42. package/dist/domain/authoring/v4/apply.d.ts +40 -0
  43. package/dist/domain/authoring/v4/apply.js +451 -0
  44. package/dist/domain/authoring/v4/apply.js.map +1 -0
  45. package/dist/domain/authoring/v4/parser.d.ts +2 -0
  46. package/dist/domain/authoring/v4/parser.js +729 -0
  47. package/dist/domain/authoring/v4/parser.js.map +1 -0
  48. package/dist/domain/authoring/v4/serializer.d.ts +6 -0
  49. package/dist/domain/authoring/v4/serializer.js +280 -0
  50. package/dist/domain/authoring/v4/serializer.js.map +1 -0
  51. package/dist/domain/authoring/v4/types.d.ts +50 -0
  52. package/dist/domain/authoring/v4/types.js +2 -0
  53. package/dist/domain/authoring/v4/types.js.map +1 -0
  54. package/dist/domain/ingest/apply-plan.d.ts +1 -1
  55. package/dist/domain/ingest/assembler.d.ts +9 -0
  56. package/dist/domain/ingest/assembler.js +98 -36
  57. package/dist/domain/ingest/assembler.js.map +1 -1
  58. package/dist/domain/ingest/asset-audit.d.ts +50 -0
  59. package/dist/domain/ingest/asset-audit.js +196 -0
  60. package/dist/domain/ingest/asset-audit.js.map +1 -0
  61. package/dist/domain/ingest/production-validation.d.ts +5 -4
  62. package/dist/domain/ingest/production-validation.js +10 -25
  63. package/dist/domain/ingest/production-validation.js.map +1 -1
  64. package/dist/domain/ingest/records.d.ts +1 -1
  65. package/dist/domain/ingest/records.js +1 -1
  66. package/dist/domain/ingest/records.js.map +1 -1
  67. package/dist/domain/ingest/serial-context.d.ts +1 -1
  68. package/dist/domain/ingest/validation.d.ts +1 -11
  69. package/dist/domain/ingest/validation.js +1 -199
  70. package/dist/domain/ingest/validation.js.map +1 -1
  71. package/dist/domain/ingest/video-apply.js +68 -45
  72. package/dist/domain/ingest/video-apply.js.map +1 -1
  73. package/dist/domain/script/address.d.ts +1 -32
  74. package/dist/domain/script/address.js +1 -144
  75. package/dist/domain/script/address.js.map +1 -1
  76. package/dist/domain/script/dispatch.d.ts +14 -0
  77. package/dist/domain/script/dispatch.js +47 -0
  78. package/dist/domain/script/dispatch.js.map +1 -0
  79. package/dist/domain/script/lookups.d.ts +1 -33
  80. package/dist/domain/script/lookups.js +1 -316
  81. package/dist/domain/script/lookups.js.map +1 -1
  82. package/dist/domain/script/parse.d.ts +1 -3
  83. package/dist/domain/script/parse.js +1 -164
  84. package/dist/domain/script/parse.js.map +1 -1
  85. package/dist/domain/script/patch/apply.d.ts +1 -2
  86. package/dist/domain/script/patch/apply.js +1 -30
  87. package/dist/domain/script/patch/apply.js.map +1 -1
  88. package/dist/domain/script/patch/helpers.d.ts +1 -17
  89. package/dist/domain/script/patch/helpers.js +1 -210
  90. package/dist/domain/script/patch/helpers.js.map +1 -1
  91. package/dist/domain/script/patch/inline-speaker.d.ts +1 -2
  92. package/dist/domain/script/patch/inline-speaker.js +1 -49
  93. package/dist/domain/script/patch/inline-speaker.js.map +1 -1
  94. package/dist/domain/script/patch/ops-action.d.ts +1 -12
  95. package/dist/domain/script/patch/ops-action.js +1 -366
  96. package/dist/domain/script/patch/ops-action.js.map +1 -1
  97. package/dist/domain/script/patch/ops-asset.d.ts +1 -14
  98. package/dist/domain/script/patch/ops-asset.js +1 -364
  99. package/dist/domain/script/patch/ops-asset.js.map +1 -1
  100. package/dist/domain/script/patch/ops-dialogue.d.ts +1 -2
  101. package/dist/domain/script/patch/ops-dialogue.js +1 -25
  102. package/dist/domain/script/patch/ops-dialogue.js.map +1 -1
  103. package/dist/domain/script/patch/ops-extend.d.ts +1 -8
  104. package/dist/domain/script/patch/ops-extend.js +1 -110
  105. package/dist/domain/script/patch/ops-extend.js.map +1 -1
  106. package/dist/domain/script/patch/ops-meta.d.ts +1 -5
  107. package/dist/domain/script/patch/ops-meta.js +1 -81
  108. package/dist/domain/script/patch/ops-meta.js.map +1 -1
  109. package/dist/domain/script/patch/ops-scene.d.ts +1 -6
  110. package/dist/domain/script/patch/ops-scene.js +1 -350
  111. package/dist/domain/script/patch/ops-scene.js.map +1 -1
  112. package/dist/domain/script/patch/ops-state.d.ts +1 -8
  113. package/dist/domain/script/patch/ops-state.js +1 -164
  114. package/dist/domain/script/patch/ops-state.js.map +1 -1
  115. package/dist/domain/script/patch/registry.d.ts +1 -2
  116. package/dist/domain/script/patch/registry.js +1 -52
  117. package/dist/domain/script/patch/registry.js.map +1 -1
  118. package/dist/domain/script/patch/types.d.ts +1 -10
  119. package/dist/domain/script/patch/types.js +1 -1
  120. package/dist/domain/script/patch/types.js.map +1 -1
  121. package/dist/domain/script/refs.d.ts +1 -4
  122. package/dist/domain/script/refs.js +1 -63
  123. package/dist/domain/script/refs.js.map +1 -1
  124. package/dist/domain/script/scene-refs.d.ts +1 -3
  125. package/dist/domain/script/scene-refs.js +1 -34
  126. package/dist/domain/script/scene-refs.js.map +1 -1
  127. package/dist/domain/script/schema.d.ts +1 -6
  128. package/dist/domain/script/schema.js +1 -55
  129. package/dist/domain/script/schema.js.map +1 -1
  130. package/dist/domain/script/selector.d.ts +1 -45
  131. package/dist/domain/script/selector.js +1 -184
  132. package/dist/domain/script/selector.js.map +1 -1
  133. package/dist/domain/script/shared/regex.d.ts +2 -0
  134. package/dist/domain/script/shared/regex.js +7 -0
  135. package/dist/domain/script/shared/regex.js.map +1 -0
  136. package/dist/domain/script/shared/types.d.ts +18 -0
  137. package/dist/domain/script/shared/types.js +9 -0
  138. package/dist/domain/script/shared/types.js.map +1 -0
  139. package/dist/domain/script/shared/validation.d.ts +10 -0
  140. package/dist/domain/script/shared/validation.js +2 -0
  141. package/dist/domain/script/shared/validation.js.map +1 -0
  142. package/dist/domain/script/types.d.ts +1 -123
  143. package/dist/domain/script/types.js +1 -8
  144. package/dist/domain/script/types.js.map +1 -1
  145. package/dist/domain/script/v3/address.d.ts +32 -0
  146. package/dist/domain/script/v3/address.js +145 -0
  147. package/dist/domain/script/v3/address.js.map +1 -0
  148. package/dist/domain/script/v3/lookups.d.ts +33 -0
  149. package/dist/domain/script/v3/lookups.js +317 -0
  150. package/dist/domain/script/v3/lookups.js.map +1 -0
  151. package/dist/domain/script/v3/ordinal.d.ts +8 -0
  152. package/dist/domain/script/v3/ordinal.js +92 -0
  153. package/dist/domain/script/v3/ordinal.js.map +1 -0
  154. package/dist/domain/script/v3/parse.d.ts +3 -0
  155. package/dist/domain/script/v3/parse.js +165 -0
  156. package/dist/domain/script/v3/parse.js.map +1 -0
  157. package/dist/domain/script/v3/patch/apply.d.ts +2 -0
  158. package/dist/domain/script/v3/patch/apply.js +31 -0
  159. package/dist/domain/script/v3/patch/apply.js.map +1 -0
  160. package/dist/domain/script/v3/patch/helpers.d.ts +17 -0
  161. package/dist/domain/script/v3/patch/helpers.js +211 -0
  162. package/dist/domain/script/v3/patch/helpers.js.map +1 -0
  163. package/dist/domain/script/v3/patch/inline-speaker.d.ts +2 -0
  164. package/dist/domain/script/v3/patch/inline-speaker.js +50 -0
  165. package/dist/domain/script/v3/patch/inline-speaker.js.map +1 -0
  166. package/dist/domain/script/v3/patch/ops-action.d.ts +12 -0
  167. package/dist/domain/script/v3/patch/ops-action.js +367 -0
  168. package/dist/domain/script/v3/patch/ops-action.js.map +1 -0
  169. package/dist/domain/script/v3/patch/ops-asset.d.ts +14 -0
  170. package/dist/domain/script/v3/patch/ops-asset.js +365 -0
  171. package/dist/domain/script/v3/patch/ops-asset.js.map +1 -0
  172. package/dist/domain/script/v3/patch/ops-dialogue.d.ts +2 -0
  173. package/dist/domain/script/v3/patch/ops-dialogue.js +26 -0
  174. package/dist/domain/script/v3/patch/ops-dialogue.js.map +1 -0
  175. package/dist/domain/script/v3/patch/ops-extend.d.ts +8 -0
  176. package/dist/domain/script/v3/patch/ops-extend.js +111 -0
  177. package/dist/domain/script/v3/patch/ops-extend.js.map +1 -0
  178. package/dist/domain/script/v3/patch/ops-meta.d.ts +5 -0
  179. package/dist/domain/script/v3/patch/ops-meta.js +82 -0
  180. package/dist/domain/script/v3/patch/ops-meta.js.map +1 -0
  181. package/dist/domain/script/v3/patch/ops-scene.d.ts +6 -0
  182. package/dist/domain/script/v3/patch/ops-scene.js +351 -0
  183. package/dist/domain/script/v3/patch/ops-scene.js.map +1 -0
  184. package/dist/domain/script/v3/patch/ops-state.d.ts +8 -0
  185. package/dist/domain/script/v3/patch/ops-state.js +165 -0
  186. package/dist/domain/script/v3/patch/ops-state.js.map +1 -0
  187. package/dist/domain/script/v3/patch/registry.d.ts +2 -0
  188. package/dist/domain/script/v3/patch/registry.js +53 -0
  189. package/dist/domain/script/v3/patch/registry.js.map +1 -0
  190. package/dist/domain/script/v3/patch/types.d.ts +10 -0
  191. package/dist/domain/script/v3/patch/types.js +2 -0
  192. package/dist/domain/script/v3/patch/types.js.map +1 -0
  193. package/dist/domain/script/v3/refs.d.ts +4 -0
  194. package/dist/domain/script/v3/refs.js +64 -0
  195. package/dist/domain/script/v3/refs.js.map +1 -0
  196. package/dist/domain/script/v3/scene-refs.d.ts +3 -0
  197. package/dist/domain/script/v3/scene-refs.js +35 -0
  198. package/dist/domain/script/v3/scene-refs.js.map +1 -0
  199. package/dist/domain/script/v3/schema.d.ts +6 -0
  200. package/dist/domain/script/v3/schema.js +56 -0
  201. package/dist/domain/script/v3/schema.js.map +1 -0
  202. package/dist/domain/script/v3/selector.d.ts +44 -0
  203. package/dist/domain/script/v3/selector.js +180 -0
  204. package/dist/domain/script/v3/selector.js.map +1 -0
  205. package/dist/domain/script/v3/types.d.ts +106 -0
  206. package/dist/domain/script/v3/types.js +2 -0
  207. package/dist/domain/script/v3/types.js.map +1 -0
  208. package/dist/domain/script/v3/validation.d.ts +3 -0
  209. package/dist/domain/script/v3/validation.js +200 -0
  210. package/dist/domain/script/v3/validation.js.map +1 -0
  211. package/dist/domain/script/v4/address.d.ts +61 -0
  212. package/dist/domain/script/v4/address.js +100 -0
  213. package/dist/domain/script/v4/address.js.map +1 -0
  214. package/dist/domain/script/v4/factory.d.ts +2 -0
  215. package/dist/domain/script/v4/factory.js +13 -0
  216. package/dist/domain/script/v4/factory.js.map +1 -0
  217. package/dist/domain/script/v4/from-v3.d.ts +29 -0
  218. package/dist/domain/script/v4/from-v3.js +307 -0
  219. package/dist/domain/script/v4/from-v3.js.map +1 -0
  220. package/dist/domain/script/v4/ids.d.ts +27 -0
  221. package/dist/domain/script/v4/ids.js +63 -0
  222. package/dist/domain/script/v4/ids.js.map +1 -0
  223. package/dist/domain/script/v4/ordinal.d.ts +3 -0
  224. package/dist/domain/script/v4/ordinal.js +67 -0
  225. package/dist/domain/script/v4/ordinal.js.map +1 -0
  226. package/dist/domain/script/v4/parse.d.ts +3 -0
  227. package/dist/domain/script/v4/parse.js +18 -0
  228. package/dist/domain/script/v4/parse.js.map +1 -0
  229. package/dist/domain/script/v4/patch/apply.d.ts +3 -0
  230. package/dist/domain/script/v4/patch/apply.js +24 -0
  231. package/dist/domain/script/v4/patch/apply.js.map +1 -0
  232. package/dist/domain/script/v4/patch/helpers.d.ts +32 -0
  233. package/dist/domain/script/v4/patch/helpers.js +139 -0
  234. package/dist/domain/script/v4/patch/helpers.js.map +1 -0
  235. package/dist/domain/script/v4/patch/ops-action.d.ts +13 -0
  236. package/dist/domain/script/v4/patch/ops-action.js +379 -0
  237. package/dist/domain/script/v4/patch/ops-action.js.map +1 -0
  238. package/dist/domain/script/v4/patch/ops-asset.d.ts +17 -0
  239. package/dist/domain/script/v4/patch/ops-asset.js +617 -0
  240. package/dist/domain/script/v4/patch/ops-asset.js.map +1 -0
  241. package/dist/domain/script/v4/patch/ops-extend.d.ts +11 -0
  242. package/dist/domain/script/v4/patch/ops-extend.js +86 -0
  243. package/dist/domain/script/v4/patch/ops-extend.js.map +1 -0
  244. package/dist/domain/script/v4/patch/ops-meta.d.ts +5 -0
  245. package/dist/domain/script/v4/patch/ops-meta.js +49 -0
  246. package/dist/domain/script/v4/patch/ops-meta.js.map +1 -0
  247. package/dist/domain/script/v4/patch/ops-scene-ref.d.ts +3 -0
  248. package/dist/domain/script/v4/patch/ops-scene-ref.js +98 -0
  249. package/dist/domain/script/v4/patch/ops-scene-ref.js.map +1 -0
  250. package/dist/domain/script/v4/patch/ops-structure.d.ts +6 -0
  251. package/dist/domain/script/v4/patch/ops-structure.js +236 -0
  252. package/dist/domain/script/v4/patch/ops-structure.js.map +1 -0
  253. package/dist/domain/script/v4/patch/registry.d.ts +2 -0
  254. package/dist/domain/script/v4/patch/registry.js +50 -0
  255. package/dist/domain/script/v4/patch/registry.js.map +1 -0
  256. package/dist/domain/script/v4/patch/schema.d.ts +7 -0
  257. package/dist/domain/script/v4/patch/schema.js +75 -0
  258. package/dist/domain/script/v4/patch/schema.js.map +1 -0
  259. package/dist/domain/script/v4/patch/types.d.ts +10 -0
  260. package/dist/domain/script/v4/patch/types.js +2 -0
  261. package/dist/domain/script/v4/patch/types.js.map +1 -0
  262. package/dist/domain/script/v4/selector.d.ts +11 -0
  263. package/dist/domain/script/v4/selector.js +125 -0
  264. package/dist/domain/script/v4/selector.js.map +1 -0
  265. package/dist/domain/script/v4/types.d.ts +120 -0
  266. package/dist/domain/script/v4/types.js +2 -0
  267. package/dist/domain/script/v4/types.js.map +1 -0
  268. package/dist/domain/script/v4/validation.d.ts +2 -0
  269. package/dist/domain/script/v4/validation.js +378 -0
  270. package/dist/domain/script/v4/validation.js.map +1 -0
  271. package/dist/help-text.js +146 -114
  272. package/dist/help-text.js.map +1 -1
  273. package/dist/llm/config.js +2 -0
  274. package/dist/llm/config.js.map +1 -1
  275. package/dist/llm/registry.d.ts +1 -0
  276. package/dist/llm/registry.js +3 -0
  277. package/dist/llm/registry.js.map +1 -1
  278. package/dist/llm/tasks/ingest/asset-audit.d.ts +6 -0
  279. package/dist/llm/tasks/ingest/asset-audit.js +164 -0
  280. package/dist/llm/tasks/ingest/asset-audit.js.map +1 -0
  281. package/dist/llm/tasks/ingest/asset-curate.d.ts +1 -1
  282. package/dist/llm/types.d.ts +1 -1
  283. package/dist/llm/types.js.map +1 -1
  284. package/dist/usecases/ingest/command.js +1 -1
  285. package/dist/usecases/ingest/final-asset-audit.d.ts +39 -0
  286. package/dist/usecases/ingest/final-asset-audit.js +88 -0
  287. package/dist/usecases/ingest/final-asset-audit.js.map +1 -0
  288. package/dist/usecases/ingest/pipeline.js +62 -11
  289. package/dist/usecases/ingest/pipeline.js.map +1 -1
  290. package/dist/usecases/ingest/serial-govern.d.ts +2 -1
  291. package/dist/usecases/ingest/serial-govern.js +11 -4
  292. package/dist/usecases/ingest/serial-govern.js.map +1 -1
  293. package/dist/usecases/ingest/video-pipeline.js +100 -14
  294. package/dist/usecases/ingest/video-pipeline.js.map +1 -1
  295. package/dist/usecases/ingest/view-server.js +2 -2
  296. package/dist/usecases/ingest/view-server.js.map +1 -1
  297. package/dist/usecases/script/actions.js +101 -5
  298. package/dist/usecases/script/actions.js.map +1 -1
  299. package/dist/usecases/script/actors.js +16 -7
  300. package/dist/usecases/script/actors.js.map +1 -1
  301. package/dist/usecases/script/alias.js +7 -6
  302. package/dist/usecases/script/alias.js.map +1 -1
  303. package/dist/usecases/script/assets.js +18 -2
  304. package/dist/usecases/script/assets.js.map +1 -1
  305. package/dist/usecases/script/audit-assets.d.ts +4 -0
  306. package/dist/usecases/script/audit-assets.js +40 -0
  307. package/dist/usecases/script/audit-assets.js.map +1 -0
  308. package/dist/usecases/script/command-constraints.d.ts +36 -0
  309. package/dist/usecases/script/command-constraints.js +115 -0
  310. package/dist/usecases/script/command-constraints.js.map +1 -0
  311. package/dist/usecases/script/context.js +31 -4
  312. package/dist/usecases/script/context.js.map +1 -1
  313. package/dist/usecases/script/create.js +2 -2
  314. package/dist/usecases/script/create.js.map +1 -1
  315. package/dist/usecases/script/delete.js +6 -3
  316. package/dist/usecases/script/delete.js.map +1 -1
  317. package/dist/usecases/script/describe.js +7 -4
  318. package/dist/usecases/script/describe.js.map +1 -1
  319. package/dist/usecases/script/dialogue.js +15 -5
  320. package/dist/usecases/script/dialogue.js.map +1 -1
  321. package/dist/usecases/script/dispatch.d.ts +3 -0
  322. package/dist/usecases/script/dispatch.js +10 -0
  323. package/dist/usecases/script/dispatch.js.map +1 -0
  324. package/dist/usecases/script/do.js +74 -11
  325. package/dist/usecases/script/do.js.map +1 -1
  326. package/dist/usecases/script/emotion.js +14 -3
  327. package/dist/usecases/script/emotion.js.map +1 -1
  328. package/dist/usecases/script/episodes.js +15 -4
  329. package/dist/usecases/script/episodes.js.map +1 -1
  330. package/dist/usecases/script/export-authoring.js +35 -11
  331. package/dist/usecases/script/export-authoring.js.map +1 -1
  332. package/dist/usecases/script/extend.js +23 -15
  333. package/dist/usecases/script/extend.js.map +1 -1
  334. package/dist/usecases/script/ids.js +53 -10
  335. package/dist/usecases/script/ids.js.map +1 -1
  336. package/dist/usecases/script/insert.js +74 -38
  337. package/dist/usecases/script/insert.js.map +1 -1
  338. package/dist/usecases/script/lib.d.ts +50 -1
  339. package/dist/usecases/script/lib.js +599 -61
  340. package/dist/usecases/script/lib.js.map +1 -1
  341. package/dist/usecases/script/locations.js +16 -7
  342. package/dist/usecases/script/locations.js.map +1 -1
  343. package/dist/usecases/script/merge.js +8 -5
  344. package/dist/usecases/script/merge.js.map +1 -1
  345. package/dist/usecases/script/move.js +24 -5
  346. package/dist/usecases/script/move.js.map +1 -1
  347. package/dist/usecases/script/parenthetical.d.ts +3 -0
  348. package/dist/usecases/script/parenthetical.js +55 -0
  349. package/dist/usecases/script/parenthetical.js.map +1 -0
  350. package/dist/usecases/script/props.js +16 -7
  351. package/dist/usecases/script/props.js.map +1 -1
  352. package/dist/usecases/script/refs.js +44 -0
  353. package/dist/usecases/script/refs.js.map +1 -1
  354. package/dist/usecases/script/rename.js +7 -4
  355. package/dist/usecases/script/rename.js.map +1 -1
  356. package/dist/usecases/script/scenes.js +32 -6
  357. package/dist/usecases/script/scenes.js.map +1 -1
  358. package/dist/usecases/script/script-patch.js +180 -16
  359. package/dist/usecases/script/script-patch.js.map +1 -1
  360. package/dist/usecases/script/session.d.ts +3 -0
  361. package/dist/usecases/script/session.js +25 -5
  362. package/dist/usecases/script/session.js.map +1 -1
  363. package/dist/usecases/script/split.js +12 -6
  364. package/dist/usecases/script/split.js.map +1 -1
  365. package/dist/usecases/script/states.js +45 -1
  366. package/dist/usecases/script/states.js.map +1 -1
  367. package/dist/usecases/script/synopsis.js +11 -4
  368. package/dist/usecases/script/synopsis.js.map +1 -1
  369. package/dist/usecases/script/v3/mutations.d.ts +2 -0
  370. package/dist/usecases/script/v3/mutations.js +5 -0
  371. package/dist/usecases/script/v3/mutations.js.map +1 -0
  372. package/dist/usecases/script/v4/mutations.d.ts +2 -0
  373. package/dist/usecases/script/v4/mutations.js +6 -0
  374. package/dist/usecases/script/v4/mutations.js.map +1 -0
  375. package/dist/usecases/script/v4/queries.d.ts +14 -0
  376. package/dist/usecases/script/v4/queries.js +154 -0
  377. package/dist/usecases/script/v4/queries.js.map +1 -0
  378. package/dist/usecases/script/working-copy.js +3 -3
  379. package/dist/usecases/script/working-copy.js.map +1 -1
  380. package/dist/usecases/script/write-authoring.js +99 -5
  381. package/dist/usecases/script/write-authoring.js.map +1 -1
  382. package/package.json +3 -2
  383. package/skills/scriptctl/SKILL.md +122 -282
  384. package/skills/scriptctl/references/atomic-edit-workflow.md +23 -38
  385. package/skills/scriptctl/references/bulk-authoring.md +35 -154
  386. package/skills/scriptctl/references/ingest-workflow.md +12 -11
  387. package/skills/scriptctl/references/production-writing-standard.md +38 -67
  388. package/skills/scriptctl/references/state-reference-repair.md +19 -42
@@ -1,12 +1,19 @@
1
- import { CliError, EXIT_NEEDS_AGENT, EXIT_OK, EXIT_USAGE, } from "../../common.js";
2
- import { applyPatchOperations } from "../../domain/script/patch/apply.js";
1
+ import { CliError, EXIT_INPUT, EXIT_NEEDS_AGENT, EXIT_OK, EXIT_USAGE, sha256Text, } from "../../common.js";
2
+ import { applyPatchOperationsV4 } from "../../domain/script/v4/patch/apply.js";
3
+ import { isV4Id } from "../../domain/script/v4/ids.js";
4
+ import { requireScriptV4 } from "../../domain/script/v4/parse.js";
5
+ import { validateScriptV4 } from "../../domain/script/v4/validation.js";
3
6
  import { sceneRefs } from "../../domain/script/scene-refs.js";
4
7
  import { isDangerousRegex, MAX_GREP_PATTERN_LEN, parseInSelector, selectorSingleActionPoint, WHOLE_SCRIPT, } from "../../domain/script/selector.js";
5
8
  import { parseAnyAddress, } from "../../domain/script/address.js";
9
+ import { parseV4Address } from "../../domain/script/v4/address.js";
6
10
  import { summarizeIssues } from "../../domain/script/text-util.js";
7
11
  import { sortDeep } from "../../domain/script/text-util.js";
8
12
  import { asList, idNum, isDict, strOf, } from "../../domain/script/shared.js";
9
13
  import { loadScriptForEdit, validateSession, saveScriptSession, syncValidationResult, scriptSourceText, } from "./session.js";
14
+ import { resolveActionTargetsV4 } from "./v4/queries.js";
15
+ import { applyVersionedMutations } from "./dispatch.js";
16
+ import { assertInsertCommandFlags, assertMoveCommandFlags, assertSceneRefCommandFlags, } from "./command-constraints.js";
10
17
  export function tokenizeLine(line) {
11
18
  const out = [];
12
19
  let cur = "";
@@ -75,6 +82,7 @@ export const DO_BOOL_FLAGS = {
75
82
  replace: new Set(["all", "regex"]),
76
83
  transition: new Set(["clear"]),
77
84
  emotion: new Set(["clear"]),
85
+ parenthetical: new Set(["remove"]),
78
86
  "scene-ref": new Set(["clear", "remove", "append"]),
79
87
  delete: new Set(["force"]),
80
88
  extend: new Set(["clear", "json-value"]),
@@ -82,6 +90,8 @@ export const DO_BOOL_FLAGS = {
82
90
  export const DO_REPEAT_FLAGS = {
83
91
  alias: new Set(["add", "remove"]),
84
92
  "add-actor": new Set(["alias"]),
93
+ "scene-ref": new Set(["state"]),
94
+ insert: new Set(["state"]),
85
95
  };
86
96
  function pushFlag(flags, repeats, name, val) {
87
97
  if (repeats.has(name)) {
@@ -305,9 +315,37 @@ export function dialogueTemplate(f) {
305
315
  // consumes). `do` = the human/agent-friendly notation for those ops: no JSON,
306
316
  // no field-name guessing — the verbs you already type. Multipolar verbs
307
317
  // (delete/move/insert/merge/describe) pick their op by the address kind.
308
- export function lineToOp(verb, args, flags) {
318
+ function repeatedDoFlag(raw) {
319
+ if (raw === undefined)
320
+ return [];
321
+ return Array.isArray(raw) ? raw.map(strOf) : [strOf(raw)];
322
+ }
323
+ function doInsertFlags(flags) {
324
+ return {
325
+ type: flags["type"],
326
+ content: flags["content"],
327
+ emotion: flags["emotion"],
328
+ parenthetical: flags["parenthetical"],
329
+ actionId: flags["action-id"],
330
+ at: flags["at"],
331
+ before: flags["before"],
332
+ after: flags["after"],
333
+ actor: flags["actor"],
334
+ speaker: flags["speaker"],
335
+ speakerState: flags["speaker-state"],
336
+ kind: flags["kind"],
337
+ label: flags["label"],
338
+ location: flags["location"],
339
+ time: flags["time"],
340
+ space: flags["space"],
341
+ sceneId: flags["scene-id"],
342
+ states: repeatedDoFlag(flags["state"]),
343
+ };
344
+ }
345
+ export function lineToOp(verb, args, flags, scriptVersion = 3) {
309
346
  const a0 = strOf(args[0]);
310
347
  const a1 = strOf(args[1]);
348
+ const parseAddress = (raw) => scriptVersion === 4 ? parseV4Address(raw) : parseAnyAddress(raw);
311
349
  switch (verb) {
312
350
  case "replace":
313
351
  return flags["regex"]
@@ -320,20 +358,52 @@ export function lineToOp(verb, args, flags) {
320
358
  case "transition":
321
359
  return { ...transitionTemplate(a1, { process: flags["process"], contrast: flags["contrast"], clear: Boolean(flags["clear"]) }), at: a0 };
322
360
  case "emotion":
361
+ if (scriptVersion === 4)
362
+ throw doErr("emotion belongs to Script v3; use parenthetical for Script v4");
323
363
  return { ...emotionTemplate({ emotion: args[1], clear: Boolean(flags["clear"]) }), at: a0 };
324
- case "scene-ref":
364
+ case "parenthetical":
365
+ if (scriptVersion === 3)
366
+ throw doErr("parenthetical belongs to Script v4; use emotion for Script v3");
367
+ if (Boolean(flags["remove"]) === (flags["set"] !== undefined)) {
368
+ throw doErr("parenthetical needs exactly one of --set <text> or --remove");
369
+ }
370
+ return Boolean(flags["remove"])
371
+ ? { op: "action.parenthetical.remove", at: a0 }
372
+ : { op: "action.parenthetical.set", at: a0, parenthetical: strOf(flags["set"]) };
373
+ case "scene-ref": {
374
+ const sceneRefFlags = {
375
+ states: repeatedDoFlag(flags["state"]),
376
+ clear: Boolean(flags["clear"]),
377
+ remove: Boolean(flags["remove"]),
378
+ append: Boolean(flags["append"]),
379
+ occurrence: flags["occurrence"],
380
+ };
381
+ assertSceneRefCommandFlags(scriptVersion, sceneRefFlags);
382
+ if (scriptVersion === 4) {
383
+ return sceneRefFlags.remove
384
+ ? { op: "scene.ref.remove", at: a0, target: a1 }
385
+ : { op: "scene.ref.set", at: a0, target: a1, state_ids: sceneRefFlags.states };
386
+ }
325
387
  return {
326
388
  ...contextTemplate(a1, {
327
- state: flags["state"],
328
- clear: Boolean(flags["clear"]),
329
- remove: Boolean(flags["remove"]),
330
- append: Boolean(flags["append"]),
331
- occurrence: flags["occurrence"],
389
+ state: sceneRefFlags.states[0],
390
+ clear: sceneRefFlags.clear,
391
+ remove: sceneRefFlags.remove,
392
+ append: sceneRefFlags.append,
393
+ occurrence: sceneRefFlags.occurrence,
332
394
  }),
333
395
  at: a0,
334
396
  };
397
+ }
335
398
  case "dialogue":
336
- return { ...dialogueTemplate({ actor: flags["actor"], kind: flags["kind"], label: flags["label"] }), at: a0 };
399
+ if (scriptVersion === 3 && flags["state"] !== undefined) {
400
+ throw doErr("dialogue --state belongs to Script v4");
401
+ }
402
+ return {
403
+ ...dialogueTemplate({ actor: flags["actor"], kind: flags["kind"], label: flags["label"] }),
404
+ ...(scriptVersion === 4 && flags["state"] !== undefined ? { state_id: flags["state"] } : {}),
405
+ at: a0,
406
+ };
337
407
  case "rename":
338
408
  return { op: "asset.rename", target: a0, name: a1 };
339
409
  case "role":
@@ -346,14 +416,15 @@ export function lineToOp(verb, args, flags) {
346
416
  if (args.length > 2)
347
417
  throw doErr("synopsis text with spaces must be quoted");
348
418
  if (args.length === 1) {
349
- if (/^ep_/.test(a0))
419
+ if (/^ep_/.test(a0) || /^ep[1-9A-HJ-NP-Za-km-z]{6}$/.test(a0)) {
350
420
  throw doErr(`synopsis for ${a0} needs text`);
421
+ }
351
422
  return { op: "meta.synopsis.set", synopsis: a0 };
352
423
  }
353
- const addr = parseAnyAddress(a0);
424
+ const addr = parseAddress(a0);
354
425
  if (addr.kind !== "episode")
355
426
  throw doErr(`synopsis target must be an episode id or omitted for global (${a0})`);
356
- return { op: "episode.synopsis.set", episode_id: addr.episodeId, synopsis: a1 };
427
+ return { op: "episode.synopsis.set", episode_id: "id" in addr ? addr.id : addr.episodeId, synopsis: a1 };
357
428
  }
358
429
  case "alias": {
359
430
  const add = flags["add"] ?? [];
@@ -365,7 +436,7 @@ export function lineToOp(verb, args, flags) {
365
436
  throw doErr("alias needs --add <alias> or --remove <alias>");
366
437
  }
367
438
  case "describe": {
368
- const addr = parseAnyAddress(a0);
439
+ const addr = parseAddress(a0);
369
440
  if (addr.kind === "state")
370
441
  return { op: "state.describe", target: a0, description: a1 };
371
442
  if (addr.kind === "asset")
@@ -373,7 +444,8 @@ export function lineToOp(verb, args, flags) {
373
444
  throw doErr(`describe cannot operate on a ${addr.kind} address (${a0}); use an asset or asset/state target`);
374
445
  }
375
446
  case "extend": {
376
- parseAnyAddress(a0); // early-validate (any kind is valid)
447
+ if (a0 !== "global")
448
+ parseAddress(a0);
377
449
  if (flags["clear"]) {
378
450
  if (args.length >= 3)
379
451
  throw doErr("extend --clear takes no value (drop --clear to set, or drop the value to delete)");
@@ -399,37 +471,52 @@ export function lineToOp(verb, args, flags) {
399
471
  case "state-delete":
400
472
  return { op: "state.delete", target: a0, strategy: flags["strategy"], replacement: flags["replacement"] };
401
473
  case "add-actor":
402
- return { op: "actor.add", name: a0, role_type: flags["role"], description: flags["description"], aliases: flags["alias"] ?? undefined };
474
+ return { op: "actor.add", actor_id: flags["id"], name: a0, role_type: flags["role"], description: flags["description"], aliases: flags["alias"] ?? undefined };
403
475
  case "add-location":
404
- return { op: "location.add", name: a0, description: flags["description"] };
476
+ return { op: "location.add", location_id: flags["id"], name: a0, description: flags["description"] };
405
477
  case "add-prop":
406
- return { op: "prop.add", name: a0, description: flags["description"] };
478
+ return { op: "prop.add", prop_id: flags["id"], name: a0, description: flags["description"] };
407
479
  case "add-episode":
408
480
  return { op: "episode.add", title: flags["title"], episode_id: flags["id"] };
409
481
  case "importance":
410
482
  return { op: "importance.set", target: a0, importance: a1 };
411
483
  case "split":
412
- return { op: "scene.split", at: a0, at_index: flags["at"], new_scene_id: flags["new-scene-id"] };
484
+ return scriptVersion === 4
485
+ ? { op: "scene.split", at: a0, at_action: flags["at"], new_scene_id: flags["new-scene-id"] }
486
+ : { op: "scene.split", at: a0, at_index: Number(flags["at"]), new_scene_id: flags["new-scene-id"] };
413
487
  case "delete": {
414
- const addr = parseAnyAddress(a0);
488
+ const addr = parseAddress(a0);
415
489
  if (addr.kind === "action")
416
490
  return { op: "action.delete", at: a0 };
417
491
  if (addr.kind === "scene")
418
492
  return { op: "scene.delete", at: a0, force: Boolean(flags["force"]) };
493
+ if (addr.kind === "state")
494
+ return { op: "state.delete", target: a0, strategy: flags["strategy"], replacement: flags["replacement"] };
419
495
  if (addr.kind === "asset")
420
496
  return { op: "asset.delete", target: a0, strategy: flags["strategy"], replacement: flags["replacement"] };
421
- throw doErr(`delete cannot operate on a ${addr.kind} address (${a0}); episodes/states have no delete op`);
497
+ throw doErr(`delete cannot operate on a ${addr.kind} address (${a0})`);
422
498
  }
423
499
  case "move": {
424
- const addr = parseAnyAddress(a0);
425
- if (addr.kind === "scene")
426
- return { op: "scene.move", at: a0, to: a1, at_index: flags["at"] };
427
- if (addr.kind === "action")
428
- return { op: "action.move", at: a0, to: a1 };
500
+ assertMoveCommandFlags(scriptVersion, {
501
+ at: flags["at"],
502
+ before: flags["before"],
503
+ after: flags["after"],
504
+ });
505
+ const addr = parseAddress(a0);
506
+ if (addr.kind === "scene") {
507
+ return scriptVersion === 4
508
+ ? { op: "scene.move", at: a0, to: a1, before: flags["before"], after: flags["after"] }
509
+ : { op: "scene.move", at: a0, to: a1, at_index: flags["at"] };
510
+ }
511
+ if (addr.kind === "action") {
512
+ return scriptVersion === 4
513
+ ? { op: "action.move", at: a0, to: a1, before: flags["before"], after: flags["after"] }
514
+ : { op: "action.move", at: a0, to: a1 };
515
+ }
429
516
  throw doErr(`move cannot operate on a ${addr.kind} address (${a0}); move an action or a scene`);
430
517
  }
431
518
  case "merge": {
432
- const addr = parseAnyAddress(a0);
519
+ const addr = parseAddress(a0);
433
520
  if (addr.kind === "scene")
434
521
  return { op: "scene.merge", from: a0, into: strOf(flags["into"]) };
435
522
  if (addr.kind === "asset")
@@ -437,12 +524,46 @@ export function lineToOp(verb, args, flags) {
437
524
  throw doErr(`merge cannot operate on a ${addr.kind} address (${a0}); merge two assets or two scenes`);
438
525
  }
439
526
  case "insert": {
440
- const addr = parseAnyAddress(a0);
527
+ const addr = parseAddress(a0);
528
+ const insertFlags = doInsertFlags(flags);
441
529
  if (addr.kind === "episode") {
442
- return { op: "scene.insert", at: a0, location: flags["location"], time: flags["time"], space: flags["space"], at_index: flags["at"], before: flags["before"], after: flags["after"], scene_id: flags["scene-id"] };
530
+ assertInsertCommandFlags(scriptVersion, "scene", insertFlags);
531
+ return {
532
+ op: "scene.insert",
533
+ at: a0,
534
+ location: flags["location"],
535
+ time: flags["time"],
536
+ space: flags["space"],
537
+ ...(scriptVersion === 4
538
+ ? { location_state_ids: insertFlags.states, before: flags["before"], after: flags["after"] }
539
+ : { at_index: flags["at"], before: flags["before"], after: flags["after"] }),
540
+ scene_id: flags["scene-id"],
541
+ };
443
542
  }
444
543
  if (addr.kind === "scene") {
445
- return { op: "action.insert", at: a0, type: flags["type"], content: flags["content"], at_index: flags["at"], before: flags["before"], after: flags["after"], actor_id: flags["actor"], speaker_id: flags["speaker"], kind: flags["kind"], label: flags["label"], emotion: flags["emotion"] };
544
+ assertInsertCommandFlags(scriptVersion, "action", insertFlags);
545
+ return {
546
+ op: "action.insert",
547
+ at: a0,
548
+ type: flags["type"],
549
+ content: flags["content"],
550
+ before: flags["before"],
551
+ after: flags["after"],
552
+ actor_id: flags["actor"],
553
+ kind: flags["kind"],
554
+ label: flags["label"],
555
+ ...(scriptVersion === 4
556
+ ? {
557
+ action_id: flags["action-id"],
558
+ state_id: flags["speaker-state"],
559
+ parenthetical: flags["parenthetical"],
560
+ }
561
+ : {
562
+ at_index: flags["at"],
563
+ speaker_id: flags["speaker"],
564
+ emotion: flags["emotion"],
565
+ }),
566
+ };
446
567
  }
447
568
  throw doErr(`insert needs ep_NNN (scene insert) or ep_NNN/scn_NNN (action insert), got a ${addr.kind} address (${a0})`);
448
569
  }
@@ -519,22 +640,355 @@ export const SKIPPABLE_EDIT_CODES = new Set([
519
640
  "FROM_TEXT_NOT_FOUND",
520
641
  "OVERLAP_DIALOGUE_UNSUPPORTED",
521
642
  ]);
643
+ export function applyPatchOperationsForSession(session, source, operations) {
644
+ return applyVersionedMutations(session.version, session.script, source, operations);
645
+ }
646
+ function v4Counts(script) {
647
+ return {
648
+ episodes: script.episodes.length,
649
+ scenes: script.episodes.reduce((sum, episode) => sum + episode.scenes.length, 0),
650
+ actions: script.episodes.reduce((sum, episode) => sum + episode.scenes.reduce((sceneSum, scene) => sceneSum + scene.actions.length, 0), 0),
651
+ actors: script.actors.length,
652
+ locations: script.locations.length,
653
+ props: script.props.length,
654
+ states: [...script.actors, ...script.locations, ...script.props]
655
+ .reduce((sum, asset) => sum + asset.states.length, 0),
656
+ };
657
+ }
658
+ function v4CanonicalIds(script) {
659
+ return [
660
+ ...script.actors.flatMap((asset) => [
661
+ asset.actor_id,
662
+ ...asset.states.map((state) => `${asset.actor_id}/${state.state_id}`),
663
+ ]),
664
+ ...script.locations.flatMap((asset) => [
665
+ asset.location_id,
666
+ ...asset.states.map((state) => `${asset.location_id}/${state.state_id}`),
667
+ ]),
668
+ ...script.props.flatMap((asset) => [
669
+ asset.prop_id,
670
+ ...asset.states.map((state) => `${asset.prop_id}/${state.state_id}`),
671
+ ]),
672
+ ...script.episodes.flatMap((episode) => [
673
+ episode.episode_id,
674
+ ...episode.scenes.flatMap((scene) => [
675
+ scene.scene_id,
676
+ ...scene.actions.map((action) => action.action_id),
677
+ ]),
678
+ ]),
679
+ ];
680
+ }
681
+ function orderedIntersection(left, right) {
682
+ return left.filter((value) => right.has(value));
683
+ }
684
+ function v4OperationTargets(operation) {
685
+ const values = [];
686
+ const isIdentity = (value) => {
687
+ if (value === "global")
688
+ return true;
689
+ if (isV4Id("episode", value)
690
+ || isV4Id("scene", value)
691
+ || isV4Id("action", value)
692
+ || isV4Id("actor", value)
693
+ || isV4Id("location", value)
694
+ || isV4Id("prop", value))
695
+ return true;
696
+ const slash = value.indexOf("/");
697
+ if (slash < 0 || slash !== value.lastIndexOf("/"))
698
+ return false;
699
+ const owner = value.slice(0, slash);
700
+ const state = value.slice(slash + 1);
701
+ return (isV4Id("actor", owner)
702
+ || isV4Id("location", owner)
703
+ || isV4Id("prop", owner)) && (state === "default" || isV4Id("state", state));
704
+ };
705
+ const add = (value) => {
706
+ if (typeof value !== "string" || value.trim() === "")
707
+ return;
708
+ const normalized = value.trim();
709
+ if (isIdentity(normalized))
710
+ values.push(normalized);
711
+ };
712
+ const first = (...fields) => {
713
+ for (const field of fields) {
714
+ const value = operation[field];
715
+ if (typeof value === "string" && value.trim() !== "")
716
+ return value;
717
+ }
718
+ return undefined;
719
+ };
720
+ const addState = (ownerValue, stateValue) => {
721
+ const owner = strOf(ownerValue).trim();
722
+ const state = strOf(stateValue).trim();
723
+ if (owner && state)
724
+ add(`${owner}/${state}`);
725
+ };
726
+ const addStates = (ownerValue, stateValues) => {
727
+ if (!Array.isArray(stateValues))
728
+ return;
729
+ stateValues.forEach((state) => addState(ownerValue, state));
730
+ };
731
+ const addPlacement = () => {
732
+ add(operation["before"]);
733
+ add(operation["after"]);
734
+ };
735
+ const kind = strOf(operation["op"]);
736
+ switch (kind) {
737
+ case "action.parenthetical.set":
738
+ case "action.parenthetical.remove":
739
+ case "action.content.replace":
740
+ case "action.content.regex-replace":
741
+ case "action.type.set":
742
+ case "action.delete":
743
+ case "action.transition.clear":
744
+ add(operation["at"]);
745
+ break;
746
+ case "action.actor.set":
747
+ case "dialogue.speaker.set":
748
+ add(operation["at"]);
749
+ add(operation["actor_id"]);
750
+ addState(operation["actor_id"], operation["state_id"]);
751
+ break;
752
+ case "action.insert":
753
+ add(operation["at"]);
754
+ add(first("action_id", "id"));
755
+ add(operation["actor_id"]);
756
+ addState(operation["actor_id"], operation["state_id"]);
757
+ addPlacement();
758
+ break;
759
+ case "action.move":
760
+ add(operation["at"]);
761
+ add(operation["to"]);
762
+ addPlacement();
763
+ break;
764
+ case "action.transition.set":
765
+ add(operation["at"]);
766
+ add(operation["target"]);
767
+ break;
768
+ case "scene.ref.set":
769
+ case "scene.ref.remove":
770
+ add(operation["at"]);
771
+ add(operation["target"]);
772
+ addStates(operation["target"], operation["state_ids"]);
773
+ break;
774
+ case "actor.add":
775
+ add(first("actor_id", "id"));
776
+ break;
777
+ case "location.add":
778
+ add(first("location_id", "id"));
779
+ break;
780
+ case "prop.add":
781
+ add(first("prop_id", "id"));
782
+ break;
783
+ case "state.add": {
784
+ const owner = operation["target"];
785
+ add(owner);
786
+ addState(owner, first("state_id", "id"));
787
+ break;
788
+ }
789
+ case "state.rename":
790
+ case "state.describe":
791
+ add(operation["target"]);
792
+ break;
793
+ case "state.delete": {
794
+ const target = strOf(operation["target"]).trim();
795
+ add(target);
796
+ const slash = target.indexOf("/");
797
+ if (slash > 0)
798
+ addState(target.slice(0, slash), operation["replacement"]);
799
+ break;
800
+ }
801
+ case "asset.rename":
802
+ case "asset.describe":
803
+ case "asset.role.set":
804
+ case "asset.alias.set":
805
+ case "asset.alias.add":
806
+ case "asset.alias.remove":
807
+ case "importance.set":
808
+ add(operation["target"]);
809
+ break;
810
+ case "asset.merge":
811
+ add(first("from", "source"));
812
+ add(first("into", "target"));
813
+ break;
814
+ case "asset.delete":
815
+ add(operation["target"]);
816
+ if (operation["strategy"] === "replace")
817
+ add(operation["replacement"]);
818
+ break;
819
+ case "episode.add":
820
+ add(first("episode_id", "id"));
821
+ break;
822
+ case "episode.synopsis.set":
823
+ add(first("episode_id", "target", "at"));
824
+ break;
825
+ case "meta.worldview.set":
826
+ case "meta.synopsis.set":
827
+ add("global");
828
+ break;
829
+ case "scene.insert": {
830
+ const location = first("location", "location_id");
831
+ add(first("at", "episode_id"));
832
+ add(first("scene_id", "id"));
833
+ add(location);
834
+ addStates(location, operation["location_state_ids"]);
835
+ addPlacement();
836
+ break;
837
+ }
838
+ case "scene.move":
839
+ add(operation["at"]);
840
+ add(operation["to"]);
841
+ addPlacement();
842
+ break;
843
+ case "scene.delete":
844
+ add(operation["at"]);
845
+ break;
846
+ case "scene.split":
847
+ add(operation["at"]);
848
+ add(first("at_action", "action_id"));
849
+ add(first("new_scene_id", "scene_id"));
850
+ break;
851
+ case "scene.merge":
852
+ add(operation["from"]);
853
+ add(operation["into"]);
854
+ break;
855
+ case "extend.set":
856
+ case "extend.clear":
857
+ add(operation["at"]);
858
+ break;
859
+ default:
860
+ throw new Error(`V4 plan target mapping is missing for operation ${kind}`);
861
+ }
862
+ return [...new Set(values)];
863
+ }
864
+ export function buildV4MutationPlan(session, operations, options = {}) {
865
+ if (session.version !== 4) {
866
+ throw new CliError("SCRIPT PLAN BLOCKED: wrong version", "A v4 mutation plan requires Script v4.", {
867
+ exitCode: EXIT_USAGE,
868
+ errorCode: "V4_PLAN_VERSION_INVALID",
869
+ received: [`Script v${session.version}`],
870
+ });
871
+ }
872
+ const original = requireScriptV4(session.script);
873
+ const proposed = structuredClone(original);
874
+ const originalIds = v4CanonicalIds(original);
875
+ const applied = [];
876
+ const steps = [];
877
+ for (const [index, operation] of operations.entries()) {
878
+ const beforeIds = v4CanonicalIds(proposed);
879
+ const beforeSet = new Set(beforeIds);
880
+ const before = v4Counts(proposed);
881
+ let status = "applied";
882
+ try {
883
+ applied.push(...applyPatchOperationsV4(proposed, [operation]));
884
+ }
885
+ catch (error) {
886
+ if (error instanceof CliError
887
+ && error.errorCode
888
+ && options.skipCodes?.has(error.errorCode)) {
889
+ status = "skipped";
890
+ }
891
+ else {
892
+ throw error;
893
+ }
894
+ }
895
+ const afterIds = v4CanonicalIds(proposed);
896
+ const afterSet = new Set(afterIds);
897
+ steps.push({
898
+ index,
899
+ operation,
900
+ targets: v4OperationTargets(operation),
901
+ status,
902
+ before,
903
+ after: v4Counts(proposed),
904
+ created_ids: afterIds.filter((id) => !beforeSet.has(id)),
905
+ removed_ids: beforeIds.filter((id) => !afterSet.has(id)),
906
+ retained_ids: orderedIntersection(beforeIds, afterSet),
907
+ });
908
+ }
909
+ const proposedIds = v4CanonicalIds(proposed);
910
+ const originalSet = new Set(originalIds);
911
+ const proposedSet = new Set(proposedIds);
912
+ const targets = [...new Set(steps.flatMap((step) => step.targets))];
913
+ const touched = new Set([
914
+ ...targets,
915
+ ...steps.flatMap((step) => step.created_ids),
916
+ ...steps.flatMap((step) => step.removed_ids),
917
+ ]);
918
+ const plan = {
919
+ version: 1,
920
+ script_version: 4,
921
+ baseline: session.baselineHash,
922
+ operations,
923
+ targets,
924
+ steps,
925
+ before: v4Counts(original),
926
+ after: v4Counts(proposed),
927
+ created_ids: proposedIds.filter((id) => !originalSet.has(id)),
928
+ removed_ids: originalIds.filter((id) => !proposedSet.has(id)),
929
+ retained_ids: orderedIntersection(originalIds, proposedSet),
930
+ untouched_ids: orderedIntersection(originalIds, proposedSet)
931
+ .filter((id) => !touched.has(id)),
932
+ validation: validateScriptV4(proposed),
933
+ };
934
+ return {
935
+ plan,
936
+ digest: `sha256:${sha256Text(JSON.stringify(sortDeep(plan)))}`,
937
+ applied,
938
+ };
939
+ }
940
+ const V4_PLANNED_ID_FIELDS = {
941
+ "actor.add": "actor_id",
942
+ "location.add": "location_id",
943
+ "prop.add": "prop_id",
944
+ "state.add": "state_id",
945
+ "episode.add": "episode_id",
946
+ "scene.insert": "scene_id",
947
+ "scene.split": "new_scene_id",
948
+ "action.insert": "action_id",
949
+ };
950
+ export function assertV4PlanHasExplicitNewIds(operations) {
951
+ for (const operation of operations) {
952
+ const kind = strOf(operation["op"]);
953
+ const field = V4_PLANNED_ID_FIELDS[kind];
954
+ if (!field || strOf(operation[field]).trim())
955
+ continue;
956
+ const idKind = field === "new_scene_id" ? "scene" : field.replace(/_id$/, "");
957
+ throw new CliError("SCRIPT V4 PLAN BLOCKED: new ID missing", "Planned creation operations require an explicit candidate ID.", {
958
+ exitCode: EXIT_USAGE,
959
+ errorCode: "V4_PLAN_ID_REQUIRED",
960
+ required: [`${kind}.${field}`],
961
+ received: [`${kind}.${field}=<missing>`],
962
+ nextSteps: [`Generate a candidate with \`scriptctl ids --kind ${idKind} --count 1\`, then include it in the operation.`],
963
+ });
964
+ }
965
+ }
522
966
  // ---------------------------------------------------------------------------
523
967
  // Single-op helpers
524
968
  // ---------------------------------------------------------------------------
525
- export async function applySingleScriptOp(opts, op) {
969
+ export async function applySingleScriptOpToSession(session, opts, op) {
970
+ const plan = strOf(opts["plan"]).trim();
971
+ if (plan) {
972
+ throw new CliError("SCRIPT OP BLOCKED: --plan is not applicable", "Single-point writes do not use plan digests.", {
973
+ exitCode: EXIT_USAGE,
974
+ errorCode: "MULTI_PLAN_USAGE_INVALID",
975
+ received: [plan],
976
+ nextSteps: ["Remove --plan from this single-point edit."],
977
+ });
978
+ }
979
+ return applyScriptOpsToSession(session, opts, [op]);
980
+ }
981
+ export async function applyScriptOpsToSession(session, opts, operations) {
526
982
  const workspace = strOf(opts["workspace_path"] || "workspace");
527
- const session = await loadScriptForEdit(opts);
528
- const script = session.script;
529
- const applied = applyPatchOperations(script, scriptSourceText(workspace), [op]);
530
- const opName = applied[0] ?? strOf(op["op"]);
983
+ const applied = applyPatchOperationsForSession(session, scriptSourceText(workspace), operations);
984
+ const opName = applied.length === 1 ? applied[0] : "script.ops";
531
985
  const [newRevision, idempotent] = await saveScriptSession(session, opts, opName);
532
986
  const validation = validateSession(session);
533
987
  await syncValidationResult(session, validation, newRevision);
534
988
  const passed = Boolean(validation["passed"]);
535
989
  // One outcome line: what changed + revision (remote) + validation verdict.
536
990
  const rev = session.remote ? `; rev ${newRevision}` : "";
537
- const resultLine = `${opName} applied${rev}; validation ${passed ? "passed" : "needs repair"}`;
991
+ const resultLine = `${applied.join(", ")} applied${rev}; validation ${passed ? "passed" : "needs repair"}`;
538
992
  const report = {
539
993
  title: passed ? "SCRIPT OP APPLIED" : "SCRIPT OP APPLIED: Validation needs repair",
540
994
  op: opName,
@@ -549,6 +1003,10 @@ export async function applySingleScriptOp(opts, op) {
549
1003
  };
550
1004
  return [report, passed ? EXIT_OK : EXIT_NEEDS_AGENT];
551
1005
  }
1006
+ export async function applySingleScriptOp(opts, op) {
1007
+ const session = await loadScriptForEdit(opts);
1008
+ return applySingleScriptOpToSession(session, opts, op);
1009
+ }
552
1010
  export function summarizeScriptOp(_script, op) {
553
1011
  const kind = strOf(op["op"]);
554
1012
  if (kind === "actor.add")
@@ -593,50 +1051,116 @@ export function regexPattern(raw) {
593
1051
  // reports the blast radius and writes nothing; --apply commits. Per-action
594
1052
  // "no match here / overlap dialogue" errors are skipped (a sweep); anything else
595
1053
  // aborts the whole edit before any write. Shared by every selector-capable verb.
596
- export async function applySelectorEdits(opts, template, verbLabel) {
1054
+ export async function applySelectorEditsToSession(session, opts, template, verbLabel) {
597
1055
  const workspace = strOf(opts["workspace_path"] || "workspace");
598
1056
  const apply = Boolean(opts["apply"]);
599
1057
  const opName = strOf(template["op"]);
600
- const session = await loadScriptForEdit(opts);
601
1058
  const script = session.script;
602
- const targets = resolveActionTargets(script, opts);
603
- const refs = targets.map((t) => `${t.epId}/${t.sceneId}#${t.idx}`);
1059
+ const refs = session.version === 3
1060
+ ? resolveActionTargets(script, opts).map((target) => `${target.epId}/${target.sceneId}#${target.idx}`)
1061
+ : resolveActionTargetsV4(requireScriptV4(script), opts, { allowOrdinals: false })
1062
+ .map((target) => target.action.action_id);
1063
+ const operations = refs.map((ref) => ({ ...template, at: ref }));
1064
+ const multiV4 = session.version === 4 && refs.length > 1;
1065
+ const planned = multiV4
1066
+ ? buildV4MutationPlan(session, operations, { skipCodes: SKIPPABLE_EDIT_CODES })
1067
+ : null;
1068
+ const planDigest = planned?.digest ?? null;
1069
+ const suppliedPlan = strOf(opts["plan"]).trim();
1070
+ if (suppliedPlan && (!multiV4 || !apply)) {
1071
+ throw new CliError("SELECTOR EDIT BLOCKED: --plan is not applicable", "A plan digest is only accepted for a v4 multi-target apply.", {
1072
+ exitCode: EXIT_USAGE,
1073
+ errorCode: "MULTI_PLAN_USAGE_INVALID",
1074
+ received: [suppliedPlan],
1075
+ nextSteps: ["Remove --plan, or apply the same v4 multi-target selection that produced the digest."],
1076
+ });
1077
+ }
1078
+ if (multiV4 && apply && !suppliedPlan) {
1079
+ throw new CliError("SELECTOR EDIT BLOCKED: plan required", "A v4 multi-target write requires its dry-run plan digest.", {
1080
+ exitCode: EXIT_USAGE,
1081
+ errorCode: "MULTI_PLAN_REQUIRED",
1082
+ required: [planDigest ?? ""],
1083
+ received: ["<missing>"],
1084
+ nextSteps: ["Run the same command without --apply, review the full plan, then retry with `--apply --plan <digest>`."],
1085
+ });
1086
+ }
1087
+ if (multiV4 && apply && suppliedPlan !== planDigest) {
1088
+ throw new CliError("SELECTOR EDIT BLOCKED: plan mismatch", "The supplied digest does not match the current targets and operations.", {
1089
+ exitCode: EXIT_INPUT,
1090
+ errorCode: "MULTI_PLAN_MISMATCH",
1091
+ required: [planDigest ?? ""],
1092
+ received: [suppliedPlan],
1093
+ nextSteps: ["Review the newly computed dry-run plan, then apply its exact digest."],
1094
+ });
1095
+ }
604
1096
  const source = scriptSourceText(workspace);
605
- let applied = 0;
606
- let skipped = 0;
607
- for (const ref of refs) {
608
- try {
609
- applyPatchOperations(script, source, [{ ...template, at: ref }]);
610
- applied += 1;
1097
+ let applied = planned?.plan.steps.filter((step) => step.status === "applied").length ?? 0;
1098
+ let skipped = planned?.plan.steps.filter((step) => step.status === "skipped").length ?? 0;
1099
+ if (!planned) {
1100
+ for (const ref of refs) {
1101
+ try {
1102
+ applyPatchOperationsForSession(session, source, [{ ...template, at: ref }]);
1103
+ applied += 1;
1104
+ }
1105
+ catch (err) {
1106
+ if (err instanceof CliError && err.errorCode && SKIPPABLE_EDIT_CODES.has(err.errorCode)) {
1107
+ skipped += 1;
1108
+ continue;
1109
+ }
1110
+ throw err;
1111
+ }
611
1112
  }
612
- catch (err) {
613
- if (err instanceof CliError && err.errorCode && SKIPPABLE_EDIT_CODES.has(err.errorCode)) {
614
- skipped += 1;
615
- continue;
1113
+ }
1114
+ else if (apply) {
1115
+ for (const step of planned.plan.steps) {
1116
+ if (step.status === "applied") {
1117
+ applyPatchOperationsForSession(session, source, [step.operation]);
616
1118
  }
617
- throw err;
618
1119
  }
619
1120
  }
620
- const sample = refs.slice(0, 12);
1121
+ const displayedRefs = multiV4 ? refs : refs.slice(0, 12);
621
1122
  const blast = [
622
1123
  `verb: ${verbLabel} (${opName})`,
623
1124
  `matched: ${refs.length} action(s)`,
624
1125
  `${apply ? "applied" : "would-apply"}: ${applied}`,
625
1126
  `${apply ? "skipped" : "would-skip"}: ${skipped}`,
626
- ...(sample.length ? [`targets: ${sample.join(", ")}${refs.length > sample.length ? ` … (+${refs.length - sample.length} more)` : ""}`] : []),
1127
+ ...(displayedRefs.length ? [`targets: ${displayedRefs.join(", ")}${refs.length > displayedRefs.length ? ` … (+${refs.length - displayedRefs.length} more)` : ""}`] : []),
1128
+ ...(planDigest ? [`plan_digest: ${planDigest}`] : []),
627
1129
  ];
628
1130
  if (!apply) {
629
- const validation = validateSession(session);
630
- const passed = Boolean(validation["passed"]);
1131
+ const legacyValidation = planned ? null : validateSession(session);
1132
+ const passed = planned
1133
+ ? planned.plan.validation.ok
1134
+ : Boolean(legacyValidation?.["passed"]);
1135
+ const plannedIssueLines = planned
1136
+ ? [...planned.plan.validation.errors, ...planned.plan.validation.warnings]
1137
+ .map((issue) => `${issue.path}: ${issue.code} - ${issue.message}`)
1138
+ : null;
1139
+ const legacyIssueLines = legacyValidation === null
1140
+ ? []
1141
+ : summarizeIssues(asList(legacyValidation["issues"]));
631
1142
  const report = {
632
1143
  title: refs.length === 0 ? "SELECTOR EDIT DRY-RUN: no match" : "SELECTOR EDIT DRY-RUN (no write)",
633
1144
  op: `${opName}.dry-run`,
634
1145
  changed: false,
635
1146
  result: [...blast, `validation: ${passed ? "would pass" : "would need repair"}; dry-run, no write`],
636
- warnings: passed ? [] : summarizeIssues(asList(validation["issues"])),
637
- issues: summarizeIssues(asList(validation["issues"])),
1147
+ warnings: passed
1148
+ ? []
1149
+ : plannedIssueLines ?? legacyIssueLines,
1150
+ issues: plannedIssueLines ?? legacyIssueLines,
638
1151
  artifacts: [session.artifactLabel],
639
- next: [refs.length === 0 ? "Widen the selection (--in / --grep / predicates)." : "Re-run with --apply to commit."],
1152
+ ...(planDigest ? {
1153
+ baseline: session.baselineHash,
1154
+ plan_digest: planDigest,
1155
+ operations,
1156
+ targets: refs,
1157
+ plan: planned?.plan,
1158
+ } : {}),
1159
+ next: [refs.length === 0
1160
+ ? "Widen the selection (--in / --grep / predicates)."
1161
+ : planDigest
1162
+ ? `Re-run with --apply --plan ${planDigest} to commit this exact plan.`
1163
+ : "Re-run with --apply to commit."],
640
1164
  };
641
1165
  return [report, passed ? EXIT_OK : EXIT_NEEDS_AGENT];
642
1166
  }
@@ -664,10 +1188,21 @@ export async function applySelectorEdits(opts, template, verbLabel) {
664
1188
  warnings: passed ? [] : summarizeIssues(asList(validation["issues"])),
665
1189
  issues: summarizeIssues(asList(validation["issues"])),
666
1190
  artifacts: [session.artifactLabel],
1191
+ ...(planDigest ? {
1192
+ baseline: session.baselineHash,
1193
+ plan_digest: planDigest,
1194
+ operations,
1195
+ targets: refs,
1196
+ plan: planned?.plan,
1197
+ } : {}),
667
1198
  next: passed ? undefined : ["Inspect issues and apply another edit."],
668
1199
  };
669
1200
  return [report, passed ? EXIT_OK : EXIT_NEEDS_AGENT];
670
1201
  }
1202
+ export async function applySelectorEdits(opts, template, verbLabel) {
1203
+ const session = await loadScriptForEdit(opts);
1204
+ return applySelectorEditsToSession(session, opts, template, verbLabel);
1205
+ }
671
1206
  export { sortDeep };
672
1207
  // ===========================================================================
673
1208
  // 0.6.0 plural-noun query commands (replace `commandScriptInspect --target X`)
@@ -735,7 +1270,10 @@ export function buildGrepMatcher(raw) {
735
1270
  }
736
1271
  try {
737
1272
  const re = new RegExp(pattern, m[2]);
738
- return (hay) => re.test(hay);
1273
+ return (hay) => {
1274
+ re.lastIndex = 0;
1275
+ return re.test(hay);
1276
+ };
739
1277
  }
740
1278
  catch {
741
1279
  // Malformed regex — treat as literal.
@@ -854,7 +1392,7 @@ export function resolveActionTargets(script, opts) {
854
1392
  }
855
1393
  // Whether a selection in `opts` has any filter at all (scope or predicate).
856
1394
  export function selectionHasAnyFilter(opts) {
857
- return Boolean(!parseInSelector(strOf(opts["in"])).isEmpty ||
1395
+ return Boolean(strOf(opts["in"]).trim() ||
858
1396
  strOf(opts["grep"]) || strOf(opts["type"]).trim() || strOf(opts["actor"]).trim() ||
859
1397
  strOf(opts["has"]).trim());
860
1398
  }
@@ -999,7 +1537,7 @@ export function splitSpeakerList(raw) {
999
1537
  }
1000
1538
  export function blankCreateReport(session, validation, idempotent) {
1001
1539
  const passed = Boolean(validation["passed"]);
1002
- const resultLines = ["created: blank script (schema v2)", `validation: ${passed ? "passed" : "needs repair"}`];
1540
+ const resultLines = [`created: blank script (Script v${session.version})`, `validation: ${passed ? "passed" : "needs repair"}`];
1003
1541
  if (session.remote) {
1004
1542
  resultLines.push(`revision: ${session.revision}`);
1005
1543
  if (idempotent !== null)