@lingjingai/scriptctl 0.77.0 → 0.78.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.
@@ -0,0 +1,13 @@
1
+ # 0.78.0
2
+
3
+ ## Added
4
+ - None.
5
+
6
+ ## Changed
7
+ - 全剧阶段不再逐个核对道具描述,道具描述保持提取阶段的结果;只有用 `--assets` 点名某个道具时才核对。这项改动本身不会让已跑完的剧重新审核。
8
+
9
+ ## Fixed
10
+ - None.
11
+
12
+ ## Breaking
13
+ - None.
@@ -130,7 +130,7 @@ ${Ys}
130
130
  ${JSON.stringify(n)}
131
131
  \u9010\u9879 finding\uFF0C\u6700\u540E validate\uFF1B\u6700\u7EC8\u53EA\u8F93\u51FA\u4E00\u53E5\u5B8C\u6210\u6458\u8981\uFF0C\u4E0D\u91CD\u590D\u6574\u4EFD\u62A5\u544A\u3002`}function Pn(e){return{apiKey:e.apiKey,model:e.model??"gemini-3.8-flash",baseUrl:e.baseUrl,provider:e.provider,protocol:e.protocol,responseSchemaField:e.responseSchemaField,fallbacks:e.fallbacks,singleModel:e.singleModel,thinking:e.thinking,maxAttempts:e.maxAttempts??3,maxSteps:e.maxSteps??300,timeoutMs:e.episodeTimeoutMs??15*6e4,traceDirectory:kt.join(kt.dirname(e.scriptPath),".review",kt.basename(e.scriptPath,".json"),"trace")}}var Im=["wardrobe","actor-identity","asset"],Zs=e=>Om("sha256").update(JSON.stringify(e)).digest("hex"),EO=Zs([Wn.toString(),Ys,Ds]);function Lm(e,t){return e.subject?.id?.split("/")[0]===t.id||[t.id,t.name,...t.aliases].includes(e.subject?.name??"")}function al(e,t,n){let o=new Set(e.map(s=>s.id));return[...e,...t.map(s=>{let i=s.id;for(let r=o.size;o.has(i);r++)i=`episode-${n}-${String(r).padStart(3,"0")}-${s.category}`;return o.add(i),{...s,id:i}})]}function yO(){let e=Number(process.env.SCRIPTCTL_REVIEW_RERUN_DELAY_MS);return process.env.SCRIPTCTL_REVIEW_RERUN_DELAY_MS!==void 0&&Number.isFinite(e)&&e>=0?e:3e4}async function Vi(e,t){let n=Gs(await e());if(n.state!=="failed"||!(n.retry==="retry"||n.retry==="retry_once"))return n;await new Promise(s=>setTimeout(s,yO())),t();let o=await e();return{...o,findings:al(n.findings.filter(s=>s.outcome!=="unresolved"),o.findings,o.episode_number),usage:[...n.usage,...o.usage],wall_ms:n.wall_ms+o.wall_ms,steps:n.steps+o.steps,media_calls:n.media_calls+o.media_calls}}function ds(e,t,n,o="",s){let i=n?e.assets.find(l=>l.id===n):void 0,r=e.episodes.map(l=>({episode:l.no,synopsis:l.synopsis,scenes:l.scenes.filter(d=>!i||[...d.location?[d.location]:[],...d.actors,...d.props].some(m=>m.id===i.id)||d.actions.some(m=>m.speaker?.id===i.id))})).filter(l=>!i||l.scenes.length),c=s?{sources:r.map(l=>({episode:l.episode,...s[l.episode-1]??{path:null,exists:!1}}))}:{},a=i?{asset:i.raw,episodes:r.map(l=>({episode:l.episode,synopsis:l.synopsis,scenes:l.scenes.map(d=>({number:d.no,scene:d.raw}))})),findings:t.filter(l=>Lm(l,i)),...c}:{script:e.raw,findings:t,...c};return{standard:Zs([EO,o]),evidence:Zs(a)}}function us(e,t){let n=e?.review_basis;return n?.standard===t.standard&&n.evidence===t.evidence}function Hr(e,t,n="",o,s){let i=e.episodes.find(l=>l.no===t),r=new Map,c=(l,d)=>{let m=r.get(l)??new Set;d.forEach(g=>m.add(g)),r.set(l,m)};for(let l of i.scenes){for(let d of[...l.location?[l.location]:[],...l.actors,...l.props])c(d.id,d.states);for(let d of l.actions)d.speaker?.id&&c(d.speaker.id,d.speaker.state?[d.speaker.state]:[])}let a=[...r].sort(([l],[d])=>l.localeCompare(d)).map(([l,d])=>{let m=e.byId.get(l);return m?{id:l,kind:m.kind,name:m.name,aliases:m.aliases,desc:m.desc,states:m.states.filter(g=>d.has(g.id))}:{id:l}});return{standard:Zs(["episode-review-basis-v1",n]),evidence:Zs({episode:i.raw,assets:a,neighbours:Vo(e,t),source:o?.[t-1]??null,...s?.length?{sourceFacts:s}:{}})}}function Nm(e,t,n){if(!e.episodes.some(s=>s.no===n))return[];let o=new Map;for(let s of t)if(s.episode_number===0){for(let i of s.findings)if(i.outcome==="unresolved"&&i.anchor.episode_number===n){let r=`global-handoff-${Zs({unit:s.asset??"identity",anchor:i.anchor,subject:i.subject,issue:i.issue,before:i.before,evidence:i.evidence})}`;o.set(r,{...i,id:r})}}return[...o.values()]}function Cr(e,t,n){let o=us(e,t)?e?.review_basis?.handoffs??[]:[];return n.filter(s=>!o.includes(s.id))}function Yr(e,t,n=[]){return e?.state==="completed"&&us(e,t)&&Cr(e,t,n).length===0}function $i(e){let t=new Map;for(let n of[...UO(e.scriptPath),...e.resumeUnits??[]])t.set(n.episode_number?`episode-${n.episode_number}`:n.asset??"identity",n);return t}function bm(e){return kt.join(kt.dirname(e),".review",kt.basename(e,".json"),"global.json")}function Rm(e){return Om("sha256").update(bt.readFileSync(e)).digest("hex")}function UO(e){let t=bm(e);if(!bt.existsSync(t))return[];let n=JSON.parse(bt.readFileSync(t,"utf8"));return n.units.some(o=>o.review_basis)||n.scriptHash===Rm(e)?n.units:[]}function cl(e){let t=$i(e);return(n,o=[])=>{let s=Ya(e.scriptPath),i=t.get("asset-escalation");if(!n.escalation_progress&&i?.escalation_progress&&delete i.escalation_progress,n.episode_number){let a=`episode-${n.episode_number}`,l=t.get(a),d=Hr(s,n.episode_number,e.episodeStandard,e.convergenceSources,e.episodeSourceFacts?.(n.episode_number)),m=us(l,d)?l?.review_basis?.handoffs??[]:[],g=n.state==="completed"?o.filter(f=>n.findings.some(h=>po([h]).has(f.id))).map(f=>f.id):[],u=l?.findings.filter(f=>f.outcome==="unresolved"&&f.evidence.some(h=>m.includes(h.detail))&&!f.evidence.some(h=>g.includes(h.detail)))??[],p=new Set(n.findings.map(f=>JSON.stringify(f)));n.findings.unshift(...u.filter(f=>!p.has(JSON.stringify(f)))),n.review_basis={...d,handoffs:[...new Set([...m,...g])].sort()},t.set(a,n)}else{let a=t.get(n.asset??"identity"),l=Ti(s,e.scriptPath),d=new Set(n.findings.map(f=>JSON.stringify(f))),m=ds(s,l,n.asset,e.convergenceStandard,e.convergenceSources),g=f=>f.subject?.id===n.asset&&f.evidence.some(h=>h.kind==="structure"&&h.label==="appearance-episode-receipt-v1"),u=n.asset&&s.byId.get(n.asset)?.kind==="actor";if(a&&n.asset!=="asset-escalation"&&!(u&&n.findings.some(g))){let f=new Set(u?a.findings.filter(g).map(E=>E.anchor.episode_number):[]),h=us(a,m);n.findings.unshift(...a.findings.filter(E=>(u&&E.subject?.id===n.asset&&f.has(E.anchor.episode_number)||h&&E.outcome!=="unresolved")&&!d.has(JSON.stringify(E))))}n.review_basis=m,t.set(n.asset??"identity",n);let p=t.get("identity");n.asset&&p?.review_basis?.standard===m.standard&&(p.review_basis=ds(s,l,void 0,e.convergenceStandard,e.convergenceSources))}qs(e.scriptPath,n);let r=bm(e.scriptPath);bt.mkdirSync(kt.dirname(r),{recursive:!0});let c={scriptHash:Rm(e.scriptPath),units:[...t.values()]};bt.writeFileSync(`${r}.tmp`,JSON.stringify(c)+`
132
132
  `),bt.renameSync(`${r}.tmp`,r)}}function Pm(e){return kt.join(kt.dirname(e),".review",kt.basename(e,".json"),"findings")}function qs(e,t){if(!t.episode_number)return;let n=Pm(e);bt.mkdirSync(n,{recursive:!0});let o=kt.join(n,`episode-${t.episode_number}.json`);bt.writeFileSync(`${o}.tmp`,JSON.stringify(t.findings,null,2)+`
133
- `),bt.renameSync(`${o}.tmp`,o)}function Ti(e,t){let n=Pm(t);return e.episodes.flatMap(o=>{let s=kt.join(n,`episode-${o.no}.json`);return bt.existsSync(s)?JSON.parse(bt.readFileSync(s,"utf8")):[]}).filter(o=>Im.includes(o.category))}async function Ws(e,t,n,o){let s=Date.now(),i=cl(t),r=$i(t),c=t.convergeOnly?[]:e.episodes.filter(g=>(!t.episodes?.length||t.episodes.includes(g.no))&&!Yr(r.get(`episode-${g.no}`),Hr(e,g.no,t.episodeStandard,t.convergenceSources))).map(g=>g.no),a=[],l=[],d=g=>{a.push(g),t.onEvent?.(g)};async function m(g,u,p){d({type:"unit-started",episode_number:g,...p?{asset:p}:{}});let f=Gs(await Vi(()=>n(g,u,p),()=>d({type:"unit-started",episode_number:g,...p?{asset:p}:{},message:"\u4E0A\u6B21\u5931\u8D25\uFF0C\u81EA\u52A8\u91CD\u8DD1"})));return ze(e,t.scriptPath),qs(t.scriptPath,f),i(f),o?.(f),l.push(f),d({type:g?"episode":"global",...f}),f}return await Se(c,t.concurrency??5,g=>m(g)),(t.convergeOnly||t.converge!==!1)&&await ll(e,{...t,onEvent:d},(g,u)=>m(0,g,u)),d({type:"done",statistics:Ki(l),usage_total:l.flatMap(g=>g.usage),wall_ms:Date.now()-s}),a}async function ll(e,t,n){let o=Ti(e,t.scriptPath),s=$i(t),i=new Map(e.assets.map(u=>[u.id,Ae(e,u.id)])),r=[...o],c=[...s.values()].filter(u=>u.episode_number===0),a=c.filter(u=>us(u,ds(e,o,u.asset,t.convergenceStandard,t.convergenceSources))),l=po(a.flatMap(u=>u.findings.filter(p=>p.outcome!=="unresolved"))),d=o.filter(u=>u.outcome==="unresolved"&&!l.has(u.id));d.push(...c.filter(u=>!u.asset).flatMap(u=>u.findings.filter(p=>p.outcome==="unresolved"&&Im.includes(p.category)))),r.push(...c.flatMap(u=>u.findings));let m=s.get("identity")?.state==="completed"&&d.length===0&&us(s.get("identity"),ds(e,o,void 0,t.convergenceStandard,t.convergenceSources));if(m){let u=s.get("identity");t.onEvent?.({type:"global",...u,usage:[]})}for(let u=0;u<3&&(u>0||!m);u++){let p=c.filter(y=>!y.asset).flatMap(y=>y.findings.filter(M=>M.outcome!=="unresolved")),f=await n([...d,...p]);if(r.push(...f.findings),f.state==="failed")return;let h=po(f.findings),E=d.filter(y=>!h.has(y.id));if(!E.length||E.length===d.length)break;d=E}let g=e.assets.filter(u=>{let p=s.get(u.id);return!p||p.state!=="completed"||p.findings.some(f=>f.outcome==="unresolved")||!us(p,ds(e,o,u.id,t.convergenceStandard,t.convergenceSources))||i.get(u.id)!==Ae(e,u.id)});for(let u of e.assets){if(g.includes(u))continue;let p=s.get(u.id);t.onEvent?.({type:"global",...p,usage:[]})}await Se(g.map(u=>u.id),t.concurrency??5,u=>n(r.filter(p=>Lm(p,e.byId.get(u))),u))}function Dr(e){return e.asset?`-${e.asset}`:""}function Ki(e){let t=e.flatMap(n=>n.findings);return{fixed:t.filter(n=>n.outcome==="fixed").length,unchanged:t.filter(n=>n.outcome==="unchanged").length,unresolved:t.filter(n=>n.outcome==="unresolved").length,failed_episodes:e.filter(n=>n.state==="failed"&&n.episode_number>0).length}}async function Se(e,t,n){let o=[...e],s=Array.from({length:Math.max(1,Math.min(t,o.length))},async()=>{for(;;){let c=o.shift();if(c===void 0)return;await n(c)}}),r=(await Promise.allSettled(s)).filter(c=>c.status==="rejected");if(r.length)throw new Error(r.map(c=>{let a=c.reason;return a instanceof Error?a.message:String(a)}).join(`
133
+ `),bt.renameSync(`${o}.tmp`,o)}function Ti(e,t){let n=Pm(t);return e.episodes.flatMap(o=>{let s=kt.join(n,`episode-${o.no}.json`);return bt.existsSync(s)?JSON.parse(bt.readFileSync(s,"utf8")):[]}).filter(o=>Im.includes(o.category))}async function Ws(e,t,n,o){let s=Date.now(),i=cl(t),r=$i(t),c=t.convergeOnly?[]:e.episodes.filter(g=>(!t.episodes?.length||t.episodes.includes(g.no))&&!Yr(r.get(`episode-${g.no}`),Hr(e,g.no,t.episodeStandard,t.convergenceSources))).map(g=>g.no),a=[],l=[],d=g=>{a.push(g),t.onEvent?.(g)};async function m(g,u,p){d({type:"unit-started",episode_number:g,...p?{asset:p}:{}});let f=Gs(await Vi(()=>n(g,u,p),()=>d({type:"unit-started",episode_number:g,...p?{asset:p}:{},message:"\u4E0A\u6B21\u5931\u8D25\uFF0C\u81EA\u52A8\u91CD\u8DD1"})));return ze(e,t.scriptPath),qs(t.scriptPath,f),i(f),o?.(f),l.push(f),d({type:g?"episode":"global",...f}),f}return await Se(c,t.concurrency??5,g=>m(g)),(t.convergeOnly||t.converge!==!1)&&await ll(e,{...t,onEvent:d},(g,u)=>m(0,g,u)),d({type:"done",statistics:Ki(l),usage_total:l.flatMap(g=>g.usage),wall_ms:Date.now()-s}),a}async function ll(e,t,n){let o=Ti(e,t.scriptPath),s=$i(t),i=new Map(e.assets.map(p=>[p.id,Ae(e,p.id)])),r=[...o],c=[...s.values()].filter(p=>p.episode_number===0),a=c.filter(p=>us(p,ds(e,o,p.asset,t.convergenceStandard,t.convergenceSources))),l=po(a.flatMap(p=>p.findings.filter(f=>f.outcome!=="unresolved"))),d=o.filter(p=>p.outcome==="unresolved"&&!l.has(p.id));d.push(...c.filter(p=>!p.asset).flatMap(p=>p.findings.filter(f=>f.outcome==="unresolved"&&Im.includes(f.category)))),r.push(...c.flatMap(p=>p.findings));let m=s.get("identity")?.state==="completed"&&d.length===0&&us(s.get("identity"),ds(e,o,void 0,t.convergenceStandard,t.convergenceSources));if(m){let p=s.get("identity");t.onEvent?.({type:"global",...p,usage:[]})}for(let p=0;p<3&&(p>0||!m);p++){let f=c.filter(M=>!M.asset).flatMap(M=>M.findings.filter(A=>A.outcome!=="unresolved")),h=await n([...d,...f]);if(r.push(...h.findings),h.state==="failed")return;let E=po(h.findings),y=d.filter(M=>!E.has(M.id));if(!y.length||y.length===d.length)break;d=y}let g=e.assets.filter(p=>p.kind!=="prop"),u=g.filter(p=>{let f=s.get(p.id);return!f||f.state!=="completed"||f.findings.some(h=>h.outcome==="unresolved")||!us(f,ds(e,o,p.id,t.convergenceStandard,t.convergenceSources))||i.get(p.id)!==Ae(e,p.id)});for(let p of g){if(u.includes(p))continue;let f=s.get(p.id);t.onEvent?.({type:"global",...f,usage:[]})}await Se(u.map(p=>p.id),t.concurrency??5,p=>n(r.filter(f=>Lm(f,e.byId.get(p))),p))}function Dr(e){return e.asset?`-${e.asset}`:""}function Ki(e){let t=e.flatMap(n=>n.findings);return{fixed:t.filter(n=>n.outcome==="fixed").length,unchanged:t.filter(n=>n.outcome==="unchanged").length,unresolved:t.filter(n=>n.outcome==="unresolved").length,failed_episodes:e.filter(n=>n.state==="failed"&&n.episode_number>0).length}}async function Se(e,t,n){let o=[...e],s=Array.from({length:Math.max(1,Math.min(t,o.length))},async()=>{for(;;){let c=o.shift();if(c===void 0)return;await n(c)}}),r=(await Promise.allSettled(s)).filter(c=>c.status==="rejected");if(r.length)throw new Error(r.map(c=>{let a=c.reason;return a instanceof Error?a.message:String(a)}).join(`
134
134
  `))}import{spawn as AO}from"node:child_process";import*as Xs from"node:fs";import*as vm from"node:os";import*as dl from"node:path";async function MO(e){let t=await new Promise((o,s)=>{let i=AO("ffprobe",["-v","error","-show_entries","format=duration","-of","default=noprint_wrappers=1:nokey=1",e],{stdio:["ignore","pipe","pipe"]}),r="",c="";i.stdout.on("data",a=>{r+=String(a)}),i.stderr.on("data",a=>{c+=String(a)}),i.on("error",a=>s(new Error(`ffprobe failed to start: ${a.message}`))),i.on("close",a=>a===0?o(r):s(new Error(`ffprobe exited ${a}: ${c.trim().slice(0,300)}`)))}),n=Number(t.trim());if(!Number.isFinite(n)||n<=0)throw new Error(`ffprobe returned no video duration: ${e}`);return n}async function _m(e){let t=await MO(e),n=Xs.mkdtempSync(dl.join(vm.tmpdir(),"review-episode-")),o=dl.join(n,"episode.mp4"),s=()=>Xs.rmSync(n,{recursive:!0,force:!0});try{return await as("ffmpeg",["-y","-nostdin","-loglevel","error","-i",e,"-map","0:v:0","-map","0:a:0?","-vf",`fps=${$o},scale=w='if(lte(iw,ih),trunc(min(480,iw)/2)*2,-2)':h='if(gt(iw,ih),trunc(min(480,ih)/2)*2,-2)',setsar=1`,"-c:v","libx264","-preset","ultrafast","-crf","30","-pix_fmt","yuv420p","-c:a","aac","-b:a","48k","-ar","24000","-ac","1","-movflags","+faststart",o]),{path:o,bytes:Xs.statSync(o).size,cleanup:s,duration:t,fps:$o}}catch(i){throw s(),i}}import{z as je}from"zod";var Gm=`\u6839\u636E actor \u7684\u540D\u5B57\u4E0E\u522B\u540D\uFF0C\u4ECE\u8F93\u5165 reviewFields \u548C synopsis \u4EC5\u6458\u5F55\u76EE\u6807\u4EBA\u7269\u7684\u5916\u8C8C\u3001\u8EAB\u4F53\u6570\u503C\u3001\u65F6\u671F\u8EAB\u4EFD\u53CA\u53D8\u5316\u5C5E\u6027\uFF0C\u4EE5\u53CA\u5176\u8863\u7269\u914D\u9970\u7684\u53EF\u89C1\u5916\u89C2\uFF0C\u5F62\u6210\u5F85\u6838\u6E05\u5355\u3002\u7ED3\u5408\u539F\u6587\u4E3B\u8BED\u53CA\u6307\u4EE3\u786E\u8BA4\u5F52\u5C5E\uFF0C\u5176\u4ED6\u4EBA\u7269\u7684\u63CF\u8FF0\u4FDD\u6301\u539F\u6587\uFF0C\u4E0D\u5217\u5165\u76EE\u6807\u6E05\u5355\u3002\u6BCF\u4E2A reviewFields \u9879\u8FD4\u56DE\u4E00\u6761 fields\uFF0C\u6CBF\u7528 action \u4E0E field\uFF1B\u6CA1\u6709\u6B64\u7C7B\u5C5E\u6027\u65F6 claims \u7559\u7A7A\u3002\u6897\u6982\u5C5E\u6027\u653E\u5165 synopsisClaims\u3002
135
135
  \u6BCF\u6761 claim \u7528 subject \u4E2D\u6027\u5B9A\u4F4D\u4EBA\u7269\u3001\u8EAB\u4F53\u90E8\u4F4D\u6216\u5177\u4F53\u7269\u4EF6\uFF0C\u7528 original \u9010\u5B57\u6458\u5F55\u53EF\u72EC\u7ACB\u66FF\u6362\u7684\u5B8C\u6574\u5916\u8C8C\u5C5E\u6027\u6210\u5206\u3002\u6309\u539F\u53E5\u53E5\u6CD5\u786E\u5B9A\u7F16\u8F91\u8FB9\u754C\uFF0C\u5305\u542B\u4ECE\u5C5E\u4E8E\u8BE5\u5C5E\u6027\u7684\u8FDE\u63A5\u8BCD\u53CA\u6807\u70B9\uFF1B\u7EAF\u4FEE\u9970\u6210\u5206\u79FB\u9664\u540E\uFF0C\u4EBA\u7269\u548C\u52A8\u4F5C\u4E3B\u5E72\u4ECD\u5B8C\u6574\u3002\u90E8\u4F4D\u6216\u7269\u4EF6\u77ED\u8BED\u5305\u542B\u5F85\u6838\u7684\u65B9\u4F4D\u3001\u7A0B\u5EA6\u53CA\u5FC5\u8981\u4E2D\u5FC3\u8BED\uFF0C\u4FEE\u8BA2\u65F6\u4FDD\u7559\u5BF9\u8C61\u6307\u79F0\u3002\u8BED\u6CD5\u76F8\u8FDE\u7684\u5C5E\u6027\u4FDD\u7559\u5728\u540C\u4E00\u6210\u5206\uFF0C\u5404\u5C5E\u6027\u5728\u6838\u5BF9\u65F6\u5206\u522B\u8BF4\u660E\u4F9D\u636E\uFF1B\u80FD\u72EC\u7ACB\u66FF\u6362\u7684\u6210\u5206\u5206\u522B\u6210\u6761\u3002\u5916\u8C8C\u53D8\u5316\u6216\u7ED3\u679C\u65AD\u8A00\u5305\u542B\u5176\u53D8\u5316\u5173\u7CFB\u53CA\u5B8C\u6574\u7ED3\u679C\u5C5E\u6027\uFF0C\u4F7F\u672C\u6B21\u53D8\u5316\u7ED3\u8BBA\u5904\u4E8E\u540C\u4E00\u7F16\u8F91\u8303\u56F4\uFF1B\u7ED3\u679C\u4E2D\u7684\u5916\u8C8C\u5C5E\u6027\u5355\u72EC\u622A\u53D6\u4E0D\u8DB3\u4EE5\u4EE3\u8868\u5B8C\u6574\u53D8\u5316\u65AD\u8A00\u3002
136
136
  \u4EBA\u7269\u53CA\u8863\u7269\u914D\u9970\u7684\u53EF\u89C1\u5916\u89C2\u3001\u7A7F\u6234\u72B6\u6001\u5C5E\u4E8E\u672C\u6B21\u8303\u56F4\u3002\u52A8\u4F5C\u53D1\u751F\u4E0E\u5426\u3001\u53D6\u51FA\u6765\u6E90\u548C\u7A7F\u8131\u8FC7\u7A0B\u4FDD\u7559\u5728\u539F\u6587\u4E2D\uFF0C\u53EA\u4F5C\u5BF9\u8C61\u5B9A\u4F4D\u4E0A\u4E0B\u6587\uFF1B\u52A8\u4F5C\u4E2D\u7684\u5916\u8C8C\u4FEE\u9970\u5355\u72EC\u6458\u51FA\uFF0C\u4EBA\u7269\u4E3B\u8BED\u548C\u52A8\u4F5C\u4E3B\u5E72\u7559\u5728\u7F16\u8F91\u6210\u5206\u5916\u3002
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingjingai/scriptctl",
3
- "version": "0.77.0",
3
+ "version": "0.78.0",
4
4
  "description": "剧本阶段统一 CLI:素材转 Script v4,并兼容 Script v3 的本地写作、精修、校验和发布。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -279,7 +279,7 @@ agent 并直接改文件。
279
279
 
280
280
  文本 Agent 先用 `text_range` 顺序读本集原文检查遗漏,再以 `source` / `search_source` 反查 action。来源是完整规范化原文的 1-based 闭区间,保存于 `extend.scriptctl.source.ranges`(`start_line` / `end_line`)。ingest 提供 chunk 级候选窗口,需读原文确认;台词保持原语言,动作允许忠实的简体中文转述。插入遗漏内容必须同时绑定原文。`.review/text/episode-N.json` 和 `global.json` 保存结论与已读/未读行窗口,加载不代表正确,未读完整保留 unresolved。
281
281
 
282
- 审核不检查道具的登记和场景绑定。审核逐场检查制作字段,日夜和内外景缺失时主动查证原稿及连续场景;有依据才补,无法确认则逐项记录待确认原因。全剧收敛补齐人物、场景、道具及状态描述,完整审核后由客户端调用 `synopsis generate --apply` 补全梗概,再保存内容绑定的 AI 审核记录。未读完原文或漫画页的分集记为未完成并保留已有修复。
282
+ 审核不检查道具的登记、场景绑定和描述。审核逐场检查制作字段,日夜和内外景缺失时主动查证原稿及连续场景;有依据才补,无法确认则逐项记录待确认原因。全剧收敛补齐人物、场景及状态描述,完整审核后由客户端调用 `synopsis generate --apply` 补全梗概,再保存内容绑定的 AI 审核记录。未读完原文或漫画页的分集记为未完成并保留已有修复。
283
283
 
284
284
  ## 版本边界
285
285
 
@@ -308,6 +308,6 @@ agent 并直接改文件。
308
308
  state 引用修复见
309
309
  [references/state-reference-repair.md](references/state-reference-repair.md)。
310
310
 
311
- 逐集 `review` 根据审核后正文补齐分集梗概;全剧收敛补齐全剧梗概、人物/场景/道具及每个状态的描述。所有说明依据已确认的正文与原片证据,已有完整准确的资料保留。
311
+ 逐集 `review` 根据审核后正文补齐分集梗概;全剧收敛补齐全剧梗概、人物/场景及每个状态的描述。所有说明依据已确认的正文与原片证据,已有完整准确的资料保留。
312
312
 
313
313
  制作字段检查:`validate` 与 `scan` 列出日夜晨昏、内外景和场景地点的缺项及路径。审核任务自动带入缺项清单;依据当前模式的原稿证据,用 `edit --time / --space` 和 `ref` 补齐,完成后复扫。证据不足时保留未知并记录场地址与原因。客户端全剧审核保留未补齐项和已完成进度,补齐后自动保存已审核状态。