@lingjingai/scriptctl 0.49.6 → 0.49.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changes/0.49.7.md +14 -0
- package/changes/0.49.8.md +13 -0
- package/dist/cli.js +39 -28
- package/dist/cli.js.map +1 -1
- package/dist/common.js +2 -4
- package/dist/common.js.map +1 -1
- package/dist/domain/script/patch/helpers.d.ts +8 -1
- package/dist/domain/script/patch/helpers.js +79 -7
- package/dist/domain/script/patch/helpers.js.map +1 -1
- package/dist/domain/script/patch/ops-asset.js +34 -10
- package/dist/domain/script/patch/ops-asset.js.map +1 -1
- package/dist/domain/script/patch/ops-scene.js +6 -1
- package/dist/domain/script/patch/ops-scene.js.map +1 -1
- package/dist/domain/script/patch/ops-state.js +28 -6
- package/dist/domain/script/patch/ops-state.js.map +1 -1
- package/dist/domain/script/schema.js +3 -3
- package/dist/domain/script/schema.js.map +1 -1
- package/dist/help-text.js +58 -31
- package/dist/help-text.js.map +1 -1
- package/dist/infra/script-output-api.d.ts +9 -6
- package/dist/infra/script-output-api.js +16 -3
- package/dist/infra/script-output-api.js.map +1 -1
- package/dist/infra/script-output-store.d.ts +16 -0
- package/dist/infra/script-output-store.js.map +1 -1
- package/dist/infra/script-working-copy.d.ts +22 -0
- package/dist/infra/script-working-copy.js +155 -0
- package/dist/infra/script-working-copy.js.map +1 -0
- package/dist/usecases/ingest/publish-command.d.ts +1 -4
- package/dist/usecases/ingest/publish-command.js +3 -76
- package/dist/usecases/ingest/publish-command.js.map +1 -1
- package/dist/usecases/script/context.js +7 -1
- package/dist/usecases/script/context.js.map +1 -1
- package/dist/usecases/script/create.js +12 -48
- package/dist/usecases/script/create.js.map +1 -1
- package/dist/usecases/script/lib.d.ts +2 -0
- package/dist/usecases/script/lib.js +35 -5
- package/dist/usecases/script/lib.js.map +1 -1
- package/dist/usecases/script/session.d.ts +2 -1
- package/dist/usecases/script/session.js +18 -49
- package/dist/usecases/script/session.js.map +1 -1
- package/dist/usecases/script/working-copy.d.ts +5 -0
- package/dist/usecases/script/working-copy.js +256 -0
- package/dist/usecases/script/working-copy.js.map +1 -0
- package/package.json +3 -1
- package/scripts/install-skill.mjs +3 -3
- package/skills/scriptctl/SKILL.md +28 -30
- package/skills/scriptctl/references/atomic-write-workflow.md +20 -10
- package/skills/scriptctl/references/ingest-workflow.md +8 -9
- package/skills/scriptctl/references/production-writing-standard.md +8 -8
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { CliError, EXIT_INPUT, EXIT_OK, EXIT_RUNTIME, scriptJsonPath, } from "../../common.js";
|
|
3
|
+
import { isDict, strOf } from "../../domain/script/shared.js";
|
|
4
|
+
import { remoteScriptOutputStoreFromEnv, ScriptOutputApiError } from "../../infra/script-output-api.js";
|
|
5
|
+
import { assertLocalFileFingerprint, assertScriptUnchanged, atomicWriteJson, localFileFingerprint, readWorkingCopyMetadata, semanticScriptHash, workingCopyPaths, writeWorkingCopyMetadata, } from "../../infra/script-working-copy.js";
|
|
6
|
+
import { apiErrorToCli, validateScriptV3Report } from "./session.js";
|
|
7
|
+
function configuredProjectGroupNo(opts) {
|
|
8
|
+
return strOf(opts["project_group_no"] || process.env.SANDBOX_PROJECT_GROUP_NO).trim();
|
|
9
|
+
}
|
|
10
|
+
function remoteClient(projectGroupNo) {
|
|
11
|
+
try {
|
|
12
|
+
return remoteScriptOutputStoreFromEnv(projectGroupNo);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (error instanceof ScriptOutputApiError) {
|
|
16
|
+
throw apiErrorToCli("SCRIPT API BLOCKED: gateway not configured", error);
|
|
17
|
+
}
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function clientForMetadata(opts, metadata) {
|
|
22
|
+
const configuredProject = configuredProjectGroupNo(opts);
|
|
23
|
+
if (configuredProject && configuredProject !== metadata.projectGroupNo) {
|
|
24
|
+
throw remoteMismatch(metadata, `configured projectGroupNo=${configuredProject}`);
|
|
25
|
+
}
|
|
26
|
+
const client = remoteClient(metadata.projectGroupNo);
|
|
27
|
+
if (client.gatewayBase !== metadata.apiBaseUrl) {
|
|
28
|
+
throw remoteMismatch(metadata, `configured apiBaseUrl=${client.gatewayBase}`);
|
|
29
|
+
}
|
|
30
|
+
return client;
|
|
31
|
+
}
|
|
32
|
+
function remoteMismatch(metadata, received) {
|
|
33
|
+
return new CliError("SCRIPT BLOCKED: working copy remote mismatch", "The configured remote does not match this working copy.", {
|
|
34
|
+
exitCode: EXIT_INPUT,
|
|
35
|
+
errorCode: "WORKING_COPY_REMOTE_MISMATCH",
|
|
36
|
+
required: [`projectGroupNo=${metadata.projectGroupNo}`, `apiBaseUrl=${metadata.apiBaseUrl}`],
|
|
37
|
+
received: [received],
|
|
38
|
+
nextSteps: ["Restore the checkout environment for this working copy, or checkout another file."],
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function readLocalScript(scriptPath) {
|
|
42
|
+
let value;
|
|
43
|
+
try {
|
|
44
|
+
value = JSON.parse(fs.readFileSync(scriptPath, "utf-8"));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new CliError("SCRIPT BLOCKED: script JSON invalid", "The local script is missing or invalid JSON.", {
|
|
48
|
+
exitCode: EXIT_INPUT,
|
|
49
|
+
errorCode: "SCRIPT_JSON_INVALID",
|
|
50
|
+
received: [scriptPath, error instanceof Error ? error.message : String(error)],
|
|
51
|
+
nextSteps: ["Fix the local script JSON before continuing."],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (!isDict(value)) {
|
|
55
|
+
throw new CliError("SCRIPT BLOCKED: script root invalid", "The script root must be a JSON object.", {
|
|
56
|
+
exitCode: EXIT_INPUT,
|
|
57
|
+
errorCode: "SCRIPT_ROOT_INVALID",
|
|
58
|
+
received: [scriptPath, Array.isArray(value) ? "array" : typeof value],
|
|
59
|
+
nextSteps: ["Replace the file with a valid Script v3 JSON object."],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
export async function commandCheckout(opts) {
|
|
65
|
+
const scriptPath = scriptJsonPath(opts);
|
|
66
|
+
const discardLocal = Boolean(opts["discard_local"]);
|
|
67
|
+
const paths = workingCopyPaths(scriptPath);
|
|
68
|
+
const metadata = readWorkingCopyMetadata(scriptPath);
|
|
69
|
+
const existed = fs.existsSync(scriptPath);
|
|
70
|
+
const beforeFingerprint = localFileFingerprint(scriptPath);
|
|
71
|
+
if (existed && metadata === null && !discardLocal) {
|
|
72
|
+
throw new CliError("CHECKOUT BLOCKED: local target exists", "The target is an unmanaged local script.", {
|
|
73
|
+
exitCode: EXIT_INPUT,
|
|
74
|
+
errorCode: "LOCAL_TARGET_EXISTS",
|
|
75
|
+
received: [scriptPath],
|
|
76
|
+
nextSteps: [`Run \`scriptctl checkout --script-path ${scriptPath} --discard-local\` only if the local file may be discarded.`],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (existed && metadata !== null && !discardLocal) {
|
|
80
|
+
const currentHash = semanticScriptHash(readLocalScript(scriptPath));
|
|
81
|
+
if (currentHash !== metadata.baseContentHash) {
|
|
82
|
+
throw new CliError("CHECKOUT BLOCKED: local changes exist", "The working copy has unpublished local changes.", {
|
|
83
|
+
exitCode: EXIT_INPUT,
|
|
84
|
+
errorCode: "WORKING_COPY_DIRTY",
|
|
85
|
+
received: [scriptPath, `baseRevision=${metadata.baseRevision}`],
|
|
86
|
+
nextSteps: ["Publish the local changes, or pass --discard-local to replace them with the remote snapshot."],
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const client = metadata !== null && !discardLocal
|
|
91
|
+
? clientForMetadata(opts, metadata)
|
|
92
|
+
: remoteClient(configuredProjectGroupNo(opts) || null);
|
|
93
|
+
let snapshot;
|
|
94
|
+
try {
|
|
95
|
+
snapshot = await client.getSnapshot();
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (error instanceof ScriptOutputApiError) {
|
|
99
|
+
throw apiErrorToCli("CHECKOUT BLOCKED: snapshot query failed", error);
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
if (snapshot === null || !isDict(snapshot.script)) {
|
|
104
|
+
throw new CliError("CHECKOUT BLOCKED: remote script not found", "No remote script_v2 snapshot exists for this project.", {
|
|
105
|
+
exitCode: EXIT_INPUT,
|
|
106
|
+
errorCode: "REMOTE_SCRIPT_NOT_FOUND",
|
|
107
|
+
received: [`projectGroupNo=${client.projectGroupNo}`],
|
|
108
|
+
nextSteps: ["Use the ingest/create workflow and publish the first script instead of checkout."],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
assertLocalFileFingerprint(scriptPath, beforeFingerprint);
|
|
112
|
+
const baseContentHash = semanticScriptHash(snapshot.script);
|
|
113
|
+
atomicWriteJson(scriptPath, snapshot.script);
|
|
114
|
+
writeWorkingCopyMetadata(scriptPath, {
|
|
115
|
+
schemaVersion: 1,
|
|
116
|
+
projectGroupNo: client.projectGroupNo,
|
|
117
|
+
apiBaseUrl: client.gatewayBase,
|
|
118
|
+
baseRevision: Number(snapshot.revision),
|
|
119
|
+
baseContentHash,
|
|
120
|
+
});
|
|
121
|
+
return [{
|
|
122
|
+
title: "CHECKOUT COMPLETE",
|
|
123
|
+
changed: true,
|
|
124
|
+
scriptPath,
|
|
125
|
+
managed: true,
|
|
126
|
+
dirty: false,
|
|
127
|
+
discardedLocal: discardLocal && existed,
|
|
128
|
+
projectGroupNo: client.projectGroupNo,
|
|
129
|
+
baseRevision: Number(snapshot.revision),
|
|
130
|
+
artifacts: [scriptPath, paths.metadataPath],
|
|
131
|
+
result: [
|
|
132
|
+
discardLocal && existed
|
|
133
|
+
? `discarded the local file and checked out revision ${snapshot.revision} to ${scriptPath}`
|
|
134
|
+
: `checked out revision ${snapshot.revision} to ${scriptPath}`,
|
|
135
|
+
],
|
|
136
|
+
}, EXIT_OK];
|
|
137
|
+
}
|
|
138
|
+
export function commandWorkingCopyStatus(opts) {
|
|
139
|
+
const scriptPath = scriptJsonPath(opts);
|
|
140
|
+
const exists = fs.existsSync(scriptPath);
|
|
141
|
+
const metadata = readWorkingCopyMetadata(scriptPath);
|
|
142
|
+
const dirty = exists && metadata !== null
|
|
143
|
+
? semanticScriptHash(readLocalScript(scriptPath)) !== metadata.baseContentHash
|
|
144
|
+
: null;
|
|
145
|
+
return [{
|
|
146
|
+
title: "WORKING COPY STATUS",
|
|
147
|
+
scriptPath,
|
|
148
|
+
exists,
|
|
149
|
+
managed: metadata !== null,
|
|
150
|
+
dirty,
|
|
151
|
+
...(metadata === null ? {} : {
|
|
152
|
+
projectGroupNo: metadata.projectGroupNo,
|
|
153
|
+
apiBaseUrl: metadata.apiBaseUrl,
|
|
154
|
+
baseRevision: metadata.baseRevision,
|
|
155
|
+
}),
|
|
156
|
+
result: [metadata === null
|
|
157
|
+
? `${scriptPath}: unmanaged local script`
|
|
158
|
+
: `${scriptPath}: ${dirty ? "dirty" : "clean"} at revision ${metadata.baseRevision}`],
|
|
159
|
+
}, EXIT_OK];
|
|
160
|
+
}
|
|
161
|
+
export async function commandWorkingCopyPublish(opts) {
|
|
162
|
+
const scriptPath = scriptJsonPath(opts);
|
|
163
|
+
const script = readLocalScript(scriptPath);
|
|
164
|
+
const scriptHash = semanticScriptHash(script);
|
|
165
|
+
const metadata = readWorkingCopyMetadata(scriptPath);
|
|
166
|
+
if (metadata !== null && metadata.baseContentHash === scriptHash) {
|
|
167
|
+
return [{
|
|
168
|
+
title: "PUBLISH NO CHANGES",
|
|
169
|
+
changed: false,
|
|
170
|
+
published: false,
|
|
171
|
+
reason: "NO_CHANGES",
|
|
172
|
+
revision: metadata.baseRevision,
|
|
173
|
+
scriptPath,
|
|
174
|
+
result: [`no local changes; revision remains ${metadata.baseRevision}`],
|
|
175
|
+
}, EXIT_OK];
|
|
176
|
+
}
|
|
177
|
+
const validation = validateScriptV3Report(script);
|
|
178
|
+
if (!Boolean(validation["passed"])) {
|
|
179
|
+
throw new CliError("PUBLISH BLOCKED: v3 validation failed", "The local script does not pass Script v3 validation.", {
|
|
180
|
+
exitCode: EXIT_INPUT,
|
|
181
|
+
errorCode: "PUBLISH_VALIDATION_FAILED",
|
|
182
|
+
received: validation["issues"].slice(0, 5).map((issue) => `${issue["path"] ?? "<root>"}: ${issue["summary"]}`),
|
|
183
|
+
nextSteps: [`Run \`scriptctl validate --script-path ${scriptPath}\`, repair the issues, then publish again.`],
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
const client = metadata === null
|
|
187
|
+
? remoteClient(configuredProjectGroupNo(opts) || null)
|
|
188
|
+
: clientForMetadata(opts, metadata);
|
|
189
|
+
const baseRevision = metadata?.baseRevision ?? 0;
|
|
190
|
+
assertScriptUnchanged(scriptPath, scriptHash);
|
|
191
|
+
let response;
|
|
192
|
+
try {
|
|
193
|
+
response = await client.publishScript({
|
|
194
|
+
baseRevision,
|
|
195
|
+
script,
|
|
196
|
+
validation: {
|
|
197
|
+
status: "valid",
|
|
198
|
+
summary: {
|
|
199
|
+
passed: true,
|
|
200
|
+
hasBlocking: false,
|
|
201
|
+
stats: validation["stats"] ?? {},
|
|
202
|
+
issueCount: 0,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
if (error instanceof ScriptOutputApiError) {
|
|
209
|
+
if (String(error.code) === "9002" || error.message.includes("版本冲突")) {
|
|
210
|
+
throw apiErrorToCli("PUBLISH BLOCKED: revision conflict", error);
|
|
211
|
+
}
|
|
212
|
+
const ambiguousSuccessResponse = error.status !== null
|
|
213
|
+
&& error.status >= 200
|
|
214
|
+
&& error.status < 300
|
|
215
|
+
&& error.code === null;
|
|
216
|
+
if (error.status === null || error.status >= 500 || ambiguousSuccessResponse) {
|
|
217
|
+
throw new CliError("PUBLISH UNKNOWN: response not confirmed", "The publish outcome is unknown because no definitive response was received.", {
|
|
218
|
+
exitCode: EXIT_RUNTIME,
|
|
219
|
+
errorCode: "PUBLISH_OUTCOME_UNKNOWN",
|
|
220
|
+
received: [error.message, `baseRevision=${baseRevision}`],
|
|
221
|
+
nextSteps: [`Inspect the remote script, then run \`scriptctl checkout --script-path ${scriptPath} --discard-local\` to recover when appropriate.`],
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
throw apiErrorToCli("PUBLISH BLOCKED: write failed", error);
|
|
225
|
+
}
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
const revision = isDict(response) ? Number(response["revision"]) : Number.NaN;
|
|
229
|
+
if (!Number.isInteger(revision) || revision <= baseRevision) {
|
|
230
|
+
throw new CliError("PUBLISH UNKNOWN: response invalid", "The gateway returned an invalid publish revision.", {
|
|
231
|
+
exitCode: EXIT_RUNTIME,
|
|
232
|
+
errorCode: "PUBLISH_OUTCOME_UNKNOWN",
|
|
233
|
+
received: [JSON.stringify(response)],
|
|
234
|
+
nextSteps: [`Inspect the remote script before retrying or discarding ${scriptPath}.`],
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
writeWorkingCopyMetadata(scriptPath, {
|
|
238
|
+
schemaVersion: 1,
|
|
239
|
+
projectGroupNo: client.projectGroupNo,
|
|
240
|
+
apiBaseUrl: client.gatewayBase,
|
|
241
|
+
baseRevision: revision,
|
|
242
|
+
baseContentHash: scriptHash,
|
|
243
|
+
});
|
|
244
|
+
return [{
|
|
245
|
+
title: "PUBLISH COMPLETE",
|
|
246
|
+
changed: true,
|
|
247
|
+
published: true,
|
|
248
|
+
created: metadata === null,
|
|
249
|
+
scriptPath,
|
|
250
|
+
projectGroupNo: client.projectGroupNo,
|
|
251
|
+
baseRevision,
|
|
252
|
+
revision,
|
|
253
|
+
result: [`published revision ${baseRevision} → ${revision}`],
|
|
254
|
+
}, EXIT_OK];
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=working-copy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"working-copy.js","sourceRoot":"","sources":["../../../src/usecases/script/working-copy.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingjingai/scriptctl",
|
|
3
|
-
"version": "0.49.
|
|
3
|
+
"version": "0.49.8",
|
|
4
4
|
"description": "剧本阶段统一 CLI:直转外部素材到 script.json,以及当前最终剧本的读取、校验、原子编辑与批量 patch 精修。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,10 +41,12 @@
|
|
|
41
41
|
"https-proxy-agent": "^9.1.0",
|
|
42
42
|
"jszip": "^3.10.1",
|
|
43
43
|
"pdfjs-dist": "^4.10.38",
|
|
44
|
+
"proper-lockfile": "^4.1.2",
|
|
44
45
|
"yaml": "^2.8.1"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"@types/node": "^20.14.0",
|
|
49
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
48
50
|
"c8": "^11.0.0",
|
|
49
51
|
"typescript": "^5.5.0",
|
|
50
52
|
"vitest": "^2.0.0"
|
|
@@ -49,13 +49,13 @@ function readVersion(root) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
// 把源 SKILL.md 里的 `scriptctl_version:`
|
|
52
|
+
// 把源 SKILL.md metadata 里的 `scriptctl_version:` 值改成实际安装的版本,让装好的
|
|
53
53
|
// skill 一眼看清对应的是哪个 scriptctl。没有该行时不硬插(保持 frontmatter 干净)。
|
|
54
54
|
function stampVersion(skillMdPath, version) {
|
|
55
55
|
try {
|
|
56
56
|
const text = fs.readFileSync(skillMdPath, "utf-8");
|
|
57
|
-
if (
|
|
58
|
-
const stamped = text.replace(/^scriptctl_version:.*$/m,
|
|
57
|
+
if (!/^\s+scriptctl_version:/m.test(text)) return;
|
|
58
|
+
const stamped = text.replace(/^(\s+)scriptctl_version:.*$/m, `$1scriptctl_version: "${version}"`);
|
|
59
59
|
fs.writeFileSync(skillMdPath, stamped, "utf-8");
|
|
60
60
|
} catch {
|
|
61
61
|
/* 戳版本失败不影响 skill 可用 */
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: scriptctl
|
|
3
|
-
description: "剧本 script.json
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
description: "剧本 script.json 的本地读写 + 入库 CLI。远端剧本先 checkout 成工作副本,所有原子读写只改本地 script.json,完成后 publish 严格按 revision 入库;新增 op 无需新增业务 API。转剧本:ingest(txt/md/docx 或视频,产出 workspace/script.json)→ 标准化复核 → view 自查 → publish。覆盖集/场/action/资产/state/scene-ref/结构调整/批量 do/patch/校验。剧本阶段做任何剧本相关读写、以及操作两个 script.json 之前都应加载。"
|
|
4
|
+
metadata:
|
|
5
|
+
version: "0.24.0"
|
|
6
|
+
scriptctl_version: "0.49.8"
|
|
7
|
+
author: "official"
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
# scriptctl
|
|
10
11
|
|
|
11
|
-
`scriptctl` 读写结构化剧本 `script.json
|
|
12
|
+
`scriptctl` 读写结构化剧本 `script.json`,并把外部素材转成剧本、入库。查询和原子编辑始终作用于本地文件;远端只在 `checkout` / `publish` 边界访问。
|
|
12
13
|
|
|
13
14
|
两条主线:
|
|
14
15
|
- **转剧本入库**:`ingest`(抽取,产出 `workspace/script.json`)→ 标准化复核 → `view`(自查)→ `publish`(入库)。见 [references/ingest-workflow.md](references/ingest-workflow.md)。
|
|
@@ -21,7 +22,7 @@ author: "official"
|
|
|
21
22
|
1. **忠实理解输入**:创作时落实题材、集纲和人物关系;提取与解析时保留源素材可验证的对白、动作、顺序、场界和状态变化。把不确定信息标为待复核,不补写源素材未表达的事实。
|
|
22
23
|
2. **注册资产**:先读现有人物、地点、道具;正文将出现的资产先用 `add-actor / add-location / add-prop` 注册。人物保留一个稳定的 `actor_name` 作为真名,称呼、绰号、职位放 `aliases[]`,同一人物共用一个 actor。
|
|
23
24
|
3. **建好资产状态**:每个新资产自带 `default` state;先用 `state-rename` + `describe <kind:id/default>` 定义常态,持久、可复用、需要独立生成视觉资产的变体再用 `state-add` 建立。
|
|
24
|
-
4. **建立真实场并绑定资产**:`insert <ep> --location` 建 scene 后,用 `scene-ref <ep/scn> <kind:id> --state <state_id>`
|
|
25
|
+
4. **建立真实场并绑定资产**:`insert <ep> --location` 建 scene 后,用 `scene-ref <ep/scn> <kind:id> --state <state_id>` 绑定本场地点、全部人物、全部道具及其实际状态。同一人物需要多份场景引用时,用 `--append` 逐份追加;正文中出现的资产属于本场引用集合。
|
|
25
26
|
5. **写入或标准化正文**:资产批次落盘并读回确认后再写正文。发现新资产或新 state 时,先完成资产和 scene-ref,再继续正文。
|
|
26
27
|
6. **逐场复核**:每批正文落盘后读回 `scenes / actions`;一集完成后运行 `validate`。`validate` 通过只代表结构与引用合法,仍需完成内容标准和源素材一致性复核。
|
|
27
28
|
|
|
@@ -36,29 +37,23 @@ author: "official"
|
|
|
36
37
|
|
|
37
38
|
### 场界判定
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
场是时间、空间和叙事连续的单元。同一地点、同一连续时段、同一叙事线且动作未中断时,继续向当前 scene 追加;笑点升级、攻防回合、角色进出、剧情节拍和同场人物造型变化不是分场理由。地点切换、明显跳时、切到另一条同时叙事线或明确转场时建立新 scene。一集可以只有一场。
|
|
40
41
|
|
|
41
|
-
location 表示可独立布置、可独立生成参考图、可重复引用的稳定物理空间;客厅、卧室、卫生间、大厅、广场分别注册。相关空间使用“豪宅·客厅”“豪宅·卧室”这类统一命名表达归属,当前 Schema 不提供地点组或父地点。每个 scene 只支持一个 location
|
|
42
|
+
location 表示可独立布置、可独立生成参考图、可重复引用的稳定物理空间;客厅、卧室、卫生间、大厅、广场分别注册。相关空间使用“豪宅·客厅”“豪宅·卧室”这类统一命名表达归属,当前 Schema 不提供地点组或父地点。每个 scene 只支持一个 location;人物引用可以重复,同一 actor 可按实际画面追加同 state 或不同 state 的多份引用。
|
|
42
43
|
|
|
43
44
|
跨地点蒙太奇按剪辑顺序建立多个短 scene,每个 scene 绑定自己的地点、实际人物、道具和 state,并用 transition 明确蒙太奇开始、切换和结束。纯空镜 scene 绑定实际 location 与关键道具即可,不创建虚假人物。
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
人物在同一连续场内需要多份视觉参考时,先绑定第一份引用,再用 `scene-ref ... actor:<id> --state <state_id> --append` 追加其余引用;用 `--occurrence <n>` 精确修改或删除第 n 份同人物引用。地点切换、跳时或叙事线切换仍建立新 scene。收尾时用 `scenes --in <ep>` 复核相邻场界。完整命令顺序见 [references/atomic-write-workflow.md](references/atomic-write-workflow.md)。
|
|
46
47
|
|
|
47
|
-
##
|
|
48
|
+
## 先定本地文件
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
- ☁️ **沙箱里**:后端注入 `SANDBOX_PROJECT_GROUP_NO`,**裸命令自动指向该项目 DB**(自动 remote),无需任何 flag。
|
|
58
|
-
- 🖥️ **本地电脑上**:几乎每条命令都带 `--script-path <file>`。下文示例为简洁常省略它,实际本地使用请补上。
|
|
59
|
-
- **不给任何目标、又不在沙箱**:CLI 拒绝猜测并报 `SCRIPT_NO_TARGET`。选一个目标。
|
|
60
|
-
- `--script-path`(本地文件)与 `--remote`/`--local`/`--project-group-no`(store)**互斥**,混用报 `TARGET_FLAG_CONFLICT`。
|
|
61
|
-
- `--workspace-path <dir>` 只给 `ingest`/`view`/`publish`/`ingest status` 用,指 ingest 工作区,不是剧本目标。**不传就是 `<cwd>/workspace`**(默认值 `workspace` 相对当前目录解析);要放别处就显式传 `--workspace-path <dir>`,`ingest` 会在结果里回显解析后的绝对路径。`view`/`publish`/`ingest status` 要传**同一个** workspace 路径。
|
|
50
|
+
- 查询、`create`、全部原子编辑、`do`、`patch` 只接受 `--script-path <file>`;省略时统一使用 `<cwd>/workspace/script.json`。
|
|
51
|
+
- 编辑远端已有剧本:先 `scriptctl checkout [--script-path FILE]`,再对该文件运行任意原子命令,最后 `scriptctl publish [--script-path FILE]`。
|
|
52
|
+
- `checkout` 会建立 `.scriptctl/<filename>.working-copy.json`,记录远端项目、API 地址、base revision 和语义内容 hash。已有未管理文件或有本地修改时拒绝覆盖;只有明确 `--discard-local` 才丢弃。
|
|
53
|
+
- `status` 完全本地判断 managed / clean / dirty,不访问网络。
|
|
54
|
+
- `publish` 对已管理文件使用 metadata 的 base revision 做严格 CAS;冲突保留本地文件。干净工作副本不访问网络。无 metadata 的文件只允许以 base revision 0 首次发布。
|
|
55
|
+
- 所有写命令按文件加锁;外部编辑器在命令期间改文件也会在落盘前被检测并拒绝覆盖。
|
|
56
|
+
- `--workspace-path` 仍只属于 `ingest` / `view` / `ingest status`;普通读写和 publish 用 `--script-path`。
|
|
62
57
|
|
|
63
58
|
### 两个 script.json:洗稿 / 对标 / 转绘
|
|
64
59
|
|
|
@@ -121,7 +116,7 @@ scriptctl scenes --in ep_003 --script-path "$NEW"
|
|
|
121
116
|
| 改 action 归属角色(内联发声源) | `scriptctl actor <ep/scn#idx> <act_id\|none>` |
|
|
122
117
|
| 改/清 action 情绪 | `scriptctl emotion <ep/scn#idx> "紧张"` / `--clear` |
|
|
123
118
|
| **把某行设成对白 + 定内联发声源** | `scriptctl dialogue <ep/scn#idx> --actor act_001`(角色)/ `--kind <system\|broadcast\|offscreen\|group> [--label 广播]`(非角色声源)。同时把 type 翻成 dialogue |
|
|
124
|
-
| **设/改场景里某资产的造型(state)** | `scriptctl scene-ref <ep/scn> <actor\|location\|prop>:<id> --state <state_id
|
|
119
|
+
| **设/改场景里某资产的造型(state)** | `scriptctl scene-ref <ep/scn> <actor\|location\|prop>:<id> --state <state_id>`;同一 actor 再加一份用 `--append`,重复后用 `--occurrence <n>` 精确修改/清除/删除第 n 份。新写正文时必须给实际 state;`--clear` / `--state none` 仅用于修复中间态。location 单值(set 即替换) |
|
|
125
120
|
| 改资产名/描述/别名/role | `scriptctl rename actor:<id> "X"` / `describe` / `alias --add X` / `role <主角\|配角>` |
|
|
126
121
|
| **设资产重要度**(决定下游是否生成视觉资产+状态跟踪) | `scriptctl importance <actor\|location\|prop>:<id> <featured\|background>`(featured=主角/配角;background=龙套/背景,下游跳过) |
|
|
127
122
|
| 给资产加 state | `scriptctl state-add actor:<id> "震惊"` |
|
|
@@ -219,8 +214,12 @@ scriptctl dialogue ep_001/scn_003#7 --kind broadcast --label 广播 # 非角
|
|
|
219
214
|
```bash
|
|
220
215
|
scriptctl states act_001 # 先看有哪些 state
|
|
221
216
|
scriptctl scene-ref ep_001/scn_003 actor:act_001 --state st_injured
|
|
217
|
+
scriptctl scene-ref ep_001/scn_003 actor:act_001 --state st_calm --append
|
|
218
|
+
scriptctl scene-ref ep_001/scn_003 actor:act_001 --state st_injured --occurrence 2
|
|
222
219
|
```
|
|
223
220
|
|
|
221
|
+
同一人物可在同一 scene 中出现多份引用。`--occurrence` 从 1 开始;命中多份却不指定 occurrence 时,修改、清除和删除都会拒绝执行。
|
|
222
|
+
|
|
224
223
|
**整理重复角色**:
|
|
225
224
|
```bash
|
|
226
225
|
scriptctl refs actor:act_005 # 先看 act_005 出现在哪
|
|
@@ -272,14 +271,13 @@ scriptctl view # 生成 review.html
|
|
|
272
271
|
scriptctl summary --script-path workspace/script.json # 抽完后当普通剧本读/改(注意目标是本地文件)
|
|
273
272
|
|
|
274
273
|
# 3) 入库:校验 v3 通过后写进目标 store
|
|
275
|
-
scriptctl publish
|
|
276
|
-
scriptctl publish --local # 本地:写 SCRIPTCTL_OUTPUT_DIR
|
|
274
|
+
scriptctl publish --script-path workspace/script.json # 首次发布或更新受管工作副本
|
|
277
275
|
```
|
|
278
276
|
|
|
279
277
|
- 🔴 **ingest 不入库**——它只产出 `<workspace>/script.json`。**入库是 `publish` 这独立一步**(沙箱里转完必须 publish,否则后端/前端看不到剧本)。
|
|
280
|
-
- 🔴 **workspace 路径**:不传 `--workspace-path` 默认 `<cwd>/workspace`;`ingest`
|
|
281
|
-
-
|
|
282
|
-
- publish 冲突(`
|
|
278
|
+
- 🔴 **workspace 路径**:不传 `--workspace-path` 默认 `<cwd>/workspace`;`ingest` 结果回显绝对路径。后续读写/publish 指向该目录下的 `script.json`。
|
|
279
|
+
- 抽取后用所有本地动词精修,再 publish。若后续继续编辑远端版本,checkout 到本地再改,不直接对 DB 跑原子 op。
|
|
280
|
+
- publish 冲突(`REVISION_CONFLICT`)表示远端 revision 已变化;本地稿仍保留,先确认远端变化再决定如何合并或显式丢弃重拉。
|
|
283
281
|
|
|
284
282
|
### `ingest status`:进度按真实文件判定
|
|
285
283
|
|
|
@@ -323,7 +321,7 @@ scriptctl ingest status --json # 机器:.status = {kind,state,phase,passes
|
|
|
323
321
|
- 多态 verb(`delete`/`merge`/`move`/`describe`/`rename`/`insert`/`scene-ref`)按 address 格式分发;flag 用错 kind 会直接报错(不静默忽略)。
|
|
324
322
|
- 新增正文前,`insert <ep/scn>` 要求本场已有地点,并且本场所有地点/人物/道具引用都绑定已物化 state;角色对白/心声的 actor 必须已在本场 cast 中。空镜、环境动作或非角色发声场可以没有人物引用。
|
|
325
323
|
- `actor_name` 在全剧内唯一(Unicode 规范化和大小写折叠后仍不能重复);别名只放 `aliases[]`。`validate` 会报告历史数据里的重名人物和“说话人不在本场”。
|
|
326
|
-
- 互斥 flag(`scene-ref` 的 --state/--clear/--remove;`dialogue` 的 --actor/--kind;`transition` 的 --process+--contrast/--clear)只能传一个,多传报 `*_FLAG_CONFLICT
|
|
324
|
+
- 互斥 flag(`scene-ref` 的 --state/--clear/--remove;`dialogue` 的 --actor/--kind;`transition` 的 --process+--contrast/--clear)只能传一个,多传报 `*_FLAG_CONFLICT`。`scene-ref --append` 只与 `--state` 同用;重复 actor 引用用 1-based `--occurrence` 定位。
|
|
327
325
|
- `mock` provider 仅测试,禁止作为交付;provider 失败不降级 mock。
|
|
328
326
|
- 工具失败排查根因,不手工拼装 JSON 绕过校验。
|
|
329
327
|
|
|
@@ -26,7 +26,7 @@ create(空白剧本,既有剧本略过)
|
|
|
26
26
|
→ publish 入库(沙箱必做)
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
> 发声源写在**对白行内联**(`dialogue <at> --actor/--kind`,或 `insert --actor/--kind`);造型挂在**场景 cast 引用**上(`scene-ref <ep/scn> <kind:id> --state
|
|
29
|
+
> 发声源写在**对白行内联**(`dialogue <at> --actor/--kind`,或 `insert --actor/--kind`);造型挂在**场景 cast 引用**上(`scene-ref <ep/scn> <kind:id> --state`)。同一人物需要多份引用时用 `--append` 追加,用 `--occurrence <n>` 定位第 n 份。正文插入前,本场必须已有地点,并且本场全部地点/人物/道具引用都绑定已建立的 state。空镜、环境动作或非角色发声场可以没有人物引用。
|
|
30
30
|
|
|
31
31
|
资产/分集/场景的 id **自动按序分配**:第一个 actor 是 `act_001`、location 是 `loc_001`、prop 是 `prp_001`、episode 是 `ep_001`、它下面第一场是 `scn_001`……所以你能在后续命令里直接引用这些可预测的 id(也可以 `--id` 显式指定)。
|
|
32
32
|
|
|
@@ -61,6 +61,14 @@ scriptctl insert ep_001/scn_001 --type inner_thought --content "陈默又出现
|
|
|
61
61
|
scriptctl validate
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
同一人物在本场需要第二份参考时,继续追加而不是新建 actor:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
scriptctl scene-ref ep_001/scn_001 actor:act_001 --state st_rain --append
|
|
68
|
+
scriptctl scene-ref ep_001/scn_001 actor:act_001 --state default --occurrence 2
|
|
69
|
+
scriptctl scene-ref ep_001/scn_001 actor:act_001 --remove --occurrence 2
|
|
70
|
+
```
|
|
71
|
+
|
|
64
72
|
### B. 整集 / 整本(推荐):资产与正文分两段 `do`
|
|
65
73
|
|
|
66
74
|
一集几十个 action 用 `do` 批量写入。先独立落盘资产批次,读回自动分配的 id;再用正文批次建集、按真实场界建 scene、绑定 state 并写 action。这两次落盘让宿主先展示人物、地点、道具及状态,再展示正文。
|
|
@@ -115,7 +123,7 @@ insert ep_001/scn_001 --type action --content "雨夜,林夏推门进店。"
|
|
|
115
123
|
insert ep_001/scn_001 --type dialogue --content "欢迎光临。" --actor act_002
|
|
116
124
|
```
|
|
117
125
|
|
|
118
|
-
|
|
126
|
+
地点、时段和叙事线连续时,后续节拍继续向 `ep_001/scn_001` 追加 action。同场人物需要多个造型或多份画面引用时,对同一 actor 追加 scene-ref,并在 Action 中写清变化顺序。互动式写作可把长场拆成多个 action 批次依次 `do --apply`,每批都指向同一 scene。场界规则以 SKILL.md「标准剧本通用流程」为准。
|
|
119
127
|
|
|
120
128
|
动词与单条命令完全一致(参数见 `scriptctl <verb> --help`)。`do` 也读 stdin:`… | scriptctl do -`。
|
|
121
129
|
|
|
@@ -125,16 +133,18 @@ insert ep_001/scn_001 --type dialogue --content "欢迎光临。" --actor act_00
|
|
|
125
133
|
|
|
126
134
|
## 写到哪里去(落库)
|
|
127
135
|
|
|
128
|
-
`create` 和所有编辑 verb
|
|
136
|
+
`create` 和所有编辑 verb 只写本地 `--script-path <file>`;省略时是
|
|
137
|
+
`<cwd>/workspace/script.json`。远端剧本必须遵循:
|
|
129
138
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
| **DB(项目组)** | `--remote`(沙箱裸命令自动 remote),或 `--project-group-no <no>` | ☁️ **沙箱首选**。每个编辑 op 直接写进 DB 的新 revision,边写边入库 |
|
|
134
|
-
| **本地约定 store** | `--local` | 写 `SCRIPTCTL_OUTPUT_DIR` 下的约定 store |
|
|
139
|
+
```text
|
|
140
|
+
checkout → 本地查询/原子编辑/do/patch → validate → publish
|
|
141
|
+
```
|
|
135
142
|
|
|
136
|
-
-
|
|
137
|
-
-
|
|
143
|
+
- 已有远端剧本先 `scriptctl checkout --script-path draft.json`。
|
|
144
|
+
- 全程对同一个 `draft.json` 编辑。新增原子能力只需实现本地操作,不增加远端业务 API。
|
|
145
|
+
- `scriptctl status --script-path draft.json` 查看是否 dirty。
|
|
146
|
+
- `scriptctl publish --script-path draft.json` 使用 checkout 时记录的 base revision 严格发布;冲突不会覆盖本地稿。
|
|
147
|
+
- 本地新剧可直接 create/ingest 后首次 publish;没有 working-copy metadata 时只尝试从 revision 0 创建。
|
|
138
148
|
|
|
139
149
|
### 两份 script.json(洗稿 / 对标 / 转绘)
|
|
140
150
|
|
|
@@ -21,7 +21,7 @@ scriptctl ingest --source-path uploads/正片/
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
常用 flag:
|
|
24
|
-
- `--workspace-path <dir>`:工作区根(manifest + 各 pass 目录 + 产物)。**不传默认 `<cwd>/workspace
|
|
24
|
+
- `--workspace-path <dir>`:工作区根(manifest + 各 pass 目录 + 产物)。**不传默认 `<cwd>/workspace`**;显式传可放别处。`view` / `ingest status` 使用同一 workspace;publish 用 `--script-path <workspace>/script.json`。
|
|
25
25
|
- `--concurrency <n>`:文本/归并 fanout 并发(文本默认 80,视频默认 30)。
|
|
26
26
|
- `--video-concurrency <n>`:视频上传+转录并发。默认 10。
|
|
27
27
|
- `--fps <n>`:视频采样帧率(>1 解锁亚秒级时间码)。默认 3。
|
|
@@ -79,27 +79,26 @@ scriptctl summary --script-path workspace/script.json # 抽完当普通剧本
|
|
|
79
79
|
## 五、入库 `publish`
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
|
-
scriptctl publish
|
|
83
|
-
scriptctl publish --
|
|
84
|
-
scriptctl publish --remote --project-group-no 123 # 显式指定项目组
|
|
82
|
+
scriptctl publish --script-path workspace/script.json
|
|
83
|
+
scriptctl publish --script-path workspace/script.json --project-group-no 123
|
|
85
84
|
```
|
|
86
85
|
|
|
87
|
-
- `publish`
|
|
86
|
+
- `publish` 读本地 `--script-path`(默认 `workspace/script.json`),**再跑一遍 v3 校验**,通过才发布。
|
|
88
87
|
- 沙箱里转完**必须 publish**,否则后端/前端看不到剧本。
|
|
89
|
-
- `
|
|
90
|
-
- publish
|
|
88
|
+
- `REVISION_CONFLICT` = 远端被别人更新;本地稿保持不变。确认远端变化后合并,或明确 `checkout --discard-local` 放弃本地稿。
|
|
89
|
+
- 已管理且内容未变时 publish 本地直接 no-op,不发网络请求。服务端不保存 requestId,也不提供 publish 幂等重试。
|
|
91
90
|
|
|
92
91
|
## 六、复杂场景怎么接
|
|
93
92
|
|
|
94
93
|
- **转完先精修再入库**:`ingest` → 用 `--script-path workspace/script.json` 跑读写动词修(改归属、并角色、标龙套、补造型……)→ `publish`。
|
|
95
|
-
-
|
|
94
|
+
- **入库后再改**:继续编辑同一个受管本地文件再 publish;若工作副本不在,先 checkout,不能直接对 DB 跑原子命令。
|
|
96
95
|
- **视频复核**:`view` 出的 `review.html` 视频版支持点击台词跳到对应画面时间码,逐场核对发声源/造型。
|
|
97
96
|
- **超大剧集(几十集)**:并发默认已调好;失败就重跑到 `ingest status` 显示 `ready`。花名册归并只跑一次并复用,重跑很快。
|
|
98
97
|
- **归并质量**:跨集角色归并靠文字锚点,相似角色可能误合/合过头。用 `roster.review.md`(视频)核对归并决策;发现误合,入库后用 `merge` 拆分/改名修正。
|
|
99
98
|
|
|
100
99
|
## 七、抽完后的读写
|
|
101
100
|
|
|
102
|
-
抽取产物就是标准 v3 剧本,用 SKILL.md
|
|
101
|
+
抽取产物就是标准 v3 剧本,用 SKILL.md 里所有读写能力精修(目标指 `--script-path workspace/script.json`;远端稿先 checkout):
|
|
103
102
|
- `summary` / `episodes` 看整本 + 分集梗概;`actions --in <ep>` 带 timestamp 看节奏。
|
|
104
103
|
- `states <actor>` 看造型弧线;`actors --counts` 看出场频次决定 `importance`。
|
|
105
104
|
- 归属/造型修正:`dialogue <at> --actor/--kind`、`scene-ref <ep/scn> <kind:id> --state`。
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
开始每一集和每一场前:
|
|
17
17
|
|
|
18
18
|
1. 读取现有人物、地点、道具及其 states。
|
|
19
|
-
2. 确定本场唯一地点、全部出场人物、全部使用道具和各自实际 state
|
|
19
|
+
2. 确定本场唯一地点、全部出场人物、全部使用道具和各自实际 state;同一人物需要多份画面引用时列出每一份引用。
|
|
20
20
|
3. 注册缺少的资产,定义需要独立视觉参考的持久状态。
|
|
21
21
|
4. 建立 scene,绑定地点、人物、道具及实际 state。
|
|
22
22
|
5. 读回 scene-ref,确认正文中的每项资产都能映射到本场引用。
|
|
@@ -64,13 +64,13 @@
|
|
|
64
64
|
- 当前没有 montage group。使用相邻 scene、transition 和统一文本标记表达组合关系。
|
|
65
65
|
- 纯空镜或完全无人镜头单独建 scene,绑定实际 location 与关键道具,不创建虚假人物。
|
|
66
66
|
|
|
67
|
-
##
|
|
67
|
+
## 八、同场人物多引用与状态变化
|
|
68
68
|
|
|
69
|
-
- 一个 scene
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
69
|
+
- 一个 scene 可重复引用同一 actor;每份引用独立绑定一个有效 state,可使用相同或不同 state。
|
|
70
|
+
- 先用 `scene-ref <ep/scn> actor:<id> --state <state_id>` 建第一份引用,再用同一命令加 `--append` 建后续引用。
|
|
71
|
+
- 同一 actor 已有多份引用时,用 1-based `--occurrence <n>` 精确修改、清除或删除某一份;不要省略 occurrence 让目标产生歧义。
|
|
72
|
+
- 同一连续场内的造型变化保留在同一 scene:绑定变化前后所需的人物引用,在 Action 中写清变化顺序;需要生成过程画面时给该 Action 添加 `transition_prompt`。
|
|
73
|
+
- 地点切换、明显跳时、叙事线切换或明确转场时建立新 scene。location 仍是单值引用。
|
|
74
74
|
|
|
75
75
|
## 九、完成门
|
|
76
76
|
|
|
@@ -78,6 +78,6 @@
|
|
|
78
78
|
2. 检查每场唯一地点、全部出场人物、道具和 state 引用。
|
|
79
79
|
3. 检查 Action 的真名、动作链、空间结果和可拍摄性。
|
|
80
80
|
4. 检查 dialogue 的说话人、emotion 和群体发声类型。
|
|
81
|
-
5.
|
|
81
|
+
5. 检查场界、蒙太奇顺序、重复人物引用和状态变化顺序。
|
|
82
82
|
6. 提取与解析任务对照源素材复核对白原文,以及事实、因果、顺序、动作结果和状态。
|
|
83
83
|
7. 运行 `validate`。把结构通过与内容标准、源素材一致性分别报告。
|