@lingjingai/scriptctl 0.11.3 → 0.11.5

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,324 @@
1
+ /**
2
+ * `scriptctl parse` — 实验性、与 direct 完全解耦的入库路径。
3
+ *
4
+ * 设计原则:md 是唯一真相源。subagent 写好一个 md 工作区(每集正文 md + 人物/场景/
5
+ * 道具/发声源 md + 可选 梗概.md/元信息.md),parse 确定性地解析、装配成 script,校验
6
+ * 通过后(`--publish`)直接写进 ScriptOutputStore。出问题就改 md 重跑——不经 direct 的
7
+ * inspect/patch/export,也不写 direct 的 run_state / directDir / source.txt。
8
+ *
9
+ * 依赖只触及更底层:domain(md 解析/装配、校验)+ infra(输出 store)+ common。
10
+ * 不 import 任何 sibling usecase(direct.ts / script.ts),可随时整体移除。
11
+ */
12
+ import * as fs from "node:fs";
13
+ import * as path from "node:path";
14
+ import { CliError, EXIT_INPUT, EXIT_NEEDS_AGENT, EXIT_OK, PARSE_MD_SPEC, WORLDVIEW_VALUES, exists, readText, sha256Text, writeJson, } from "../common.js";
15
+ import { cleanName, mergeEpisodeResults, parseAssetDoc, parseMarkdownBatch } from "../domain/direct-core.js";
16
+ import { validateScript } from "../domain/script-core.js";
17
+ import { LocalScriptOutputStore } from "../infra/local-script-output-store.js";
18
+ import { RemoteScriptOutputStore } from "../infra/script-output-api.js";
19
+ import { ScriptOutputApiError, resolveOutputMode } from "../infra/script-output-store.js";
20
+ function strOf(v) {
21
+ return v === null || v === undefined ? "" : String(v);
22
+ }
23
+ function isDict(v) {
24
+ return typeof v === "object" && v !== null && !Array.isArray(v);
25
+ }
26
+ function asList(v) {
27
+ return Array.isArray(v) ? v : [];
28
+ }
29
+ // ---------------------------------------------------------------------------
30
+ // md 工作区布局
31
+ // ---------------------------------------------------------------------------
32
+ const _EP_FILE_RE = /^ep[_-]?0*(\d+)\.(?:md|markdown)$/i;
33
+ const ASSET_DOC_SPECS = [
34
+ { kind: "actors", names: ["人物.md", "角色.md", "characters.md", "actors.md"] },
35
+ { kind: "locations", names: ["场景.md", "地点.md", "locations.md"] },
36
+ { kind: "props", names: ["道具.md", "props.md"] },
37
+ { kind: "speakers", names: ["发声源.md", "speakers.md"] },
38
+ ];
39
+ const SYNOPSIS_DOC_NAMES = ["梗概.md", "全文梗概.md", "synopsis.md"];
40
+ const META_DOC_NAMES = ["元信息.md", "meta.md"];
41
+ function firstExisting(dir, names) {
42
+ for (const n of names) {
43
+ const p = path.join(dir, n);
44
+ if (exists(p) && fs.statSync(p).isFile())
45
+ return p;
46
+ }
47
+ return null;
48
+ }
49
+ function collectEpisodeMdFiles(dir) {
50
+ if (!exists(dir) || !fs.statSync(dir).isDirectory())
51
+ return [];
52
+ const out = [];
53
+ for (const name of fs.readdirSync(dir)) {
54
+ const m = _EP_FILE_RE.exec(name);
55
+ if (!m)
56
+ continue;
57
+ const full = path.join(dir, name);
58
+ if (!fs.statSync(full).isFile())
59
+ continue;
60
+ out.push({ path: full, episode: parseInt(m[1], 10) });
61
+ }
62
+ out.sort((a, b) => a.episode - b.episode);
63
+ return out;
64
+ }
65
+ // Whole-script metadata from 元信息.md: worldview / style / title / 主角.
66
+ function parseMetaDoc(text) {
67
+ const out = {};
68
+ for (const line of text.split(/\r?\n/)) {
69
+ const m = /^\s*[-*]?\s*(title|worldview|style|protagonists|标题|世界观|风格|主角|主角列表)\s*[::]\s*(.+?)\s*$/i.exec(line);
70
+ if (!m)
71
+ continue;
72
+ const key = m[1].toLowerCase();
73
+ const val = m[2].trim();
74
+ if (!val)
75
+ continue;
76
+ if (key === "title" || key === "标题")
77
+ out.title = val;
78
+ else if (key === "worldview" || key === "世界观")
79
+ out.worldview = val;
80
+ else if (key === "style" || key === "风格")
81
+ out.style = val;
82
+ else if (key === "protagonists" || key === "主角" || key === "主角列表") {
83
+ out.protagonists = val.split(/[,,、]/).map((s) => s.trim()).filter(Boolean);
84
+ }
85
+ }
86
+ return out;
87
+ }
88
+ function summarizeIssues(issues) {
89
+ if (issues.length === 0)
90
+ return [];
91
+ const counts = {};
92
+ for (const item of issues) {
93
+ const sev = strOf(item["severity"]);
94
+ counts[sev] = (counts[sev] ?? 0) + 1;
95
+ }
96
+ const parts = Object.entries(counts).sort(([a], [b]) => a.localeCompare(b)).map(([sev, c]) => `${sev}: ${c}`);
97
+ const first = issues[0];
98
+ return [parts.join("; "), `first: ${first["code"]} - ${first["summary"]}`];
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // 输出 store(直接从 infra 建,不依赖 usecases/script.ts)
102
+ // ---------------------------------------------------------------------------
103
+ function makeOutputStore(opts) {
104
+ const mode = resolveOutputMode().mode;
105
+ try {
106
+ return mode === "local"
107
+ ? LocalScriptOutputStore.fromEnv(strOf(opts["workspace_path"]).trim() || undefined)
108
+ : RemoteScriptOutputStore.fromEnv(strOf(opts["project_group_no"]).trim() || null);
109
+ }
110
+ catch (exc) {
111
+ const raw = exc instanceof Error ? exc.message : String(exc);
112
+ throw new CliError("PARSE PUBLISH BLOCKED: 输出未配置", "Output store not configured.", {
113
+ exitCode: EXIT_INPUT,
114
+ required: [mode === "local" ? "SCRIPTCTL_OUTPUT_DIR" : "gateway 配置(AWB_BASE_URL / ACCESS_KEY / project-group-no)"],
115
+ received: [raw],
116
+ nextSteps: ["运行 `scriptctl doctor` 检查输出配置。"],
117
+ errorCode: "SCRIPT_API_CONFIG_MISSING",
118
+ });
119
+ }
120
+ }
121
+ async function currentRevisionOrZero(client) {
122
+ try {
123
+ // getRevision() returns `{ revision: N } | null`, not a bare number.
124
+ const revision = await client.getRevision();
125
+ if (!revision)
126
+ return 0;
127
+ return Number(revision["revision"] ?? 0);
128
+ }
129
+ catch {
130
+ return 0;
131
+ }
132
+ }
133
+ // 稳定序列化(键递归排序),用于 requestId 幂等哈希。
134
+ function sortDeep(value) {
135
+ if (Array.isArray(value))
136
+ return value.map(sortDeep);
137
+ if (value && typeof value === "object") {
138
+ const out = {};
139
+ for (const k of Object.keys(value).sort())
140
+ out[k] = sortDeep(value[k]);
141
+ return out;
142
+ }
143
+ return value;
144
+ }
145
+ // ---------------------------------------------------------------------------
146
+ // command_parse
147
+ // ---------------------------------------------------------------------------
148
+ export async function commandParse(opts) {
149
+ if (opts["spec"]) {
150
+ return [{ title: "PARSE SPEC: md 工作区写法", body: PARSE_MD_SPEC }, EXIT_OK];
151
+ }
152
+ const workspace = strOf(opts["workspace_path"] || "workspace");
153
+ const args = asList(opts["_args"]);
154
+ const mdDir = strOf(opts["md_dir"] || args[0] || path.join(workspace, "parse"));
155
+ if (!exists(mdDir) || !fs.statSync(mdDir).isDirectory()) {
156
+ throw new CliError("PARSE BLOCKED: md workspace not found", "md workspace not found.", {
157
+ exitCode: EXIT_INPUT,
158
+ required: ["a directory with per-episode 正文 md + 人物/场景/道具/发声源 md"],
159
+ received: [mdDir],
160
+ nextSteps: ["Pass the md workspace dir: scriptctl parse <dir>. Run `scriptctl parse --spec` for the format."],
161
+ });
162
+ }
163
+ let episodesDir = strOf(opts["episodes_dir"]).trim();
164
+ if (!episodesDir) {
165
+ const sub = path.join(mdDir, "episodes");
166
+ episodesDir = exists(sub) && fs.statSync(sub).isDirectory() ? sub : mdDir;
167
+ }
168
+ const bodyFiles = collectEpisodeMdFiles(episodesDir);
169
+ if (bodyFiles.length === 0) {
170
+ throw new CliError("PARSE BLOCKED: no episode md found", "no episode md found.", {
171
+ exitCode: EXIT_INPUT,
172
+ required: ["per-episode body md named like ep_001.md"],
173
+ received: [episodesDir],
174
+ nextSteps: ["Add per-episode 正文 md (ep_001.md, ep_002.md, ...). Run `scriptctl parse --spec` for the format."],
175
+ });
176
+ }
177
+ // 每类资产各自一份;正文 md 不登资产。汇成一个 bible fragment。
178
+ const bible = { actors: [], locations: [], props: [], speakers: [], state_definitions: [] };
179
+ const assetDocsFound = [];
180
+ for (const spec of ASSET_DOC_SPECS) {
181
+ const p = firstExisting(mdDir, spec.names);
182
+ if (!p)
183
+ continue;
184
+ assetDocsFound.push(path.basename(p));
185
+ const parsed = parseAssetDoc(readText(p), spec.kind);
186
+ for (const key of ["actors", "locations", "props", "speakers", "state_definitions"]) {
187
+ bible[key].push(...asList(parsed[key]));
188
+ }
189
+ }
190
+ // 可选全文梗概(梗概.md);去掉开头的 `# 梗概` 标题行。
191
+ let globalSynopsis = "";
192
+ const synPath = firstExisting(mdDir, SYNOPSIS_DOC_NAMES);
193
+ if (synPath)
194
+ globalSynopsis = readText(synPath).replace(/^\s*#\s+\S[^\n]*\n/, "").trim();
195
+ // 可选整部元信息(元信息.md):worldview / style / title / 主角。
196
+ const metaPath = firstExisting(mdDir, META_DOC_NAMES);
197
+ const meta = metaPath ? parseMetaDoc(readText(metaPath)) : {};
198
+ // 解析每集正文 md(确定性,无 LLM)。
199
+ const results = [];
200
+ for (const file of bodyFiles) {
201
+ try {
202
+ results.push(parseMarkdownBatch(readText(file.path), { episode: file.episode, part: 1 }, { fragmentMode: true }));
203
+ }
204
+ catch (exc) {
205
+ const e = exc;
206
+ throw new CliError("PARSE BLOCKED: episode md invalid", "episode md invalid.", {
207
+ exitCode: EXIT_INPUT,
208
+ required: ["per-episode 正文 md following `scriptctl parse --spec`"],
209
+ received: [`${path.basename(file.path)}: ${(e?.message ?? "").slice(0, 200)}`],
210
+ nextSteps: ["Fix the episode md and re-run parse."],
211
+ });
212
+ }
213
+ }
214
+ results.sort((a, b) => Number(a["episode"] ?? 0) - Number(b["episode"] ?? 0));
215
+ // 把登记的资产折进第一集 result,描述/状态随之进入装配;名字全局去重,
216
+ // bible-first 让登记表(规范)描述优先于正文里隐含的引用。
217
+ if (results.length > 0) {
218
+ const first = results[0];
219
+ for (const key of ["actors", "locations", "props", "speakers", "state_definitions"]) {
220
+ first[key] = [...asList(bible[key]), ...asList(first[key])];
221
+ }
222
+ }
223
+ const title = meta.title || strOf(opts["title"]).trim() || path.basename(path.resolve(mdDir));
224
+ const script = mergeEpisodeResults(results, title);
225
+ if (globalSynopsis)
226
+ script["synopsis"] = globalSynopsis;
227
+ if (meta.worldview) {
228
+ if (!WORLDVIEW_VALUES.includes(meta.worldview)) {
229
+ throw new CliError("PARSE BLOCKED: worldview 非法", "Invalid worldview in 元信息.md.", {
230
+ exitCode: EXIT_INPUT,
231
+ required: [`worldview: 取枚举之一 ${WORLDVIEW_VALUES.join(" / ")}`],
232
+ received: [`worldview: ${meta.worldview}`],
233
+ nextSteps: ["改 元信息.md 的 worldview 为合法枚举值,再重跑 parse。"],
234
+ });
235
+ }
236
+ script["worldview"] = meta.worldview;
237
+ script["worldview_raw"] = meta.worldview;
238
+ }
239
+ if (meta.style)
240
+ script["style"] = meta.style;
241
+ // role_type 由 元信息.md `主角:` 驱动;两边都 cleanName 后比对,避免注解/空白把主角误判成配角。
242
+ if (meta.protagonists && meta.protagonists.length > 0) {
243
+ const leads = new Set(meta.protagonists.map((n) => cleanName(n)));
244
+ for (const actor of asList(script["actors"])) {
245
+ actor["role_type"] = leads.has(cleanName(strOf(actor["actor_name"]))) ? "主角" : "配角";
246
+ }
247
+ }
248
+ // 产物落在 parse 自己的位置(不写 directDir / run_state / source.txt)。
249
+ const scriptPath = path.join(mdDir, "script.json");
250
+ writeJson(scriptPath, script);
251
+ const validation = validateScript(workspace, null, { requireSource: false, scriptData: script, persist: false });
252
+ const passed = Boolean(validation["passed"]);
253
+ const stats = validation["stats"] ?? {};
254
+ const blockingOrError = Boolean(validation["has_blocking"]) ||
255
+ asList(validation["issues"]).some((it) => isDict(it) && (it["severity"] === "blocking" || it["severity"] === "error"));
256
+ // --publish:md → 校验 → 直接入库。校验不过就报问题、不发布,让 agent 改 md 重跑。
257
+ if (opts["publish"]) {
258
+ if (blockingOrError) {
259
+ return [{
260
+ title: "PARSE PUBLISH BLOCKED: 校验未过,改 md 后重跑",
261
+ result: [`asset docs: ${assetDocsFound.join(" / ") || "(none)"}`, "validation: needs repair"],
262
+ artifacts: [scriptPath],
263
+ issues: summarizeIssues(asList(validation["issues"])),
264
+ next: ["按 issue 直接改对应的 ep_*.md / 人物·场景·道具·发声源.md / 元信息.md,再重跑 `scriptctl parse <dir> --publish`。"],
265
+ }, EXIT_NEEDS_AGENT];
266
+ }
267
+ const client = makeOutputStore(opts);
268
+ const baseRevision = await currentRevisionOrZero(client);
269
+ const scriptHash = sha256Text(JSON.stringify(sortDeep(script)));
270
+ const requestId = strOf(opts["request_id"]).trim() || `scriptctl-parse:${scriptHash}`;
271
+ let replaceRes;
272
+ try {
273
+ replaceRes = await client.replaceScript({ requestId, baseRevision, script, source: "ctl" });
274
+ }
275
+ catch (exc) {
276
+ if (exc instanceof ScriptOutputApiError) {
277
+ const conflict = /版本冲突|revision/i.test(exc.message);
278
+ throw new CliError("PARSE PUBLISH BLOCKED: 入库写入失败", conflict ? "Script has been updated by another revision; reload and retry." : exc.message, {
279
+ exitCode: EXIT_INPUT,
280
+ required: ["可写入的 ScriptOutputStore"],
281
+ received: [exc.message],
282
+ nextSteps: conflict ? ["剧本已被其他写入更新,重跑 parse --publish。"] : ["检查输出配置后重试。"],
283
+ errorCode: conflict ? "SCRIPT_REVISION_CONFLICT" : "SCRIPT_API_WRITE_FAILED",
284
+ });
285
+ }
286
+ throw exc;
287
+ }
288
+ return [{
289
+ title: "PARSE PUBLISHED: 剧本已入库",
290
+ result: [
291
+ `episodes: ${stats["episodes"] ?? results.length}`,
292
+ `scenes: ${stats["scenes"] ?? 0}`,
293
+ `actions: ${stats["actions"] ?? 0}`,
294
+ `worldview: ${meta.worldview || "(unset)"}`,
295
+ `base_revision: ${baseRevision}`,
296
+ `revision: ${replaceRes["revision"]}`,
297
+ `idempotent: ${replaceRes["idempotent"] ?? false}`,
298
+ ],
299
+ artifacts: [scriptPath],
300
+ next: ["剧本已入库。若发现问题,直接改 md 再 `scriptctl parse <dir> --publish` 覆盖。"],
301
+ }, EXIT_OK];
302
+ }
303
+ return [{
304
+ title: passed
305
+ ? "PARSE COMPLETE: 已生成 script.json(加 --publish 直接入库)"
306
+ : "PARSE NEEDS AGENT: 校验有问题,直接改 md 再 parse",
307
+ result: [
308
+ `episodes: ${stats["episodes"] ?? results.length}`,
309
+ `scenes: ${stats["scenes"] ?? 0}`,
310
+ `actions: ${stats["actions"] ?? 0}`,
311
+ `asset docs: ${assetDocsFound.join(" / ") || "(none)"}`,
312
+ `worldview: ${meta.worldview || "(unset)"}`,
313
+ `style: ${meta.style || "(unset)"}`,
314
+ `synopsis: ${globalSynopsis ? "yes" : "no"}`,
315
+ `validation: ${passed ? "passed" : "needs repair"}`,
316
+ ],
317
+ artifacts: [scriptPath],
318
+ issues: summarizeIssues(asList(validation["issues"])),
319
+ next: passed
320
+ ? ["加 `--publish` 直接入库。"]
321
+ : ["按 issue 直接改对应的 md(ep_*.md / 资产 md / 元信息.md),再重跑 parse。"],
322
+ }, passed ? EXIT_OK : EXIT_NEEDS_AGENT];
323
+ }
324
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/usecases/parse.ts"],"names":[],"mappings":"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"}
@@ -825,6 +825,7 @@ async function commandStateRefs(opts, plan = false) {
825
825
  export async function commandExport(opts) {
826
826
  const workspace = strOf(opts["workspace_path"] || "workspace");
827
827
  const force = Boolean(opts["force"]);
828
+ const allowIncomplete = Boolean(opts["allow_incomplete"]);
828
829
  const scriptPath = path.join(directDir(workspace), "script.initial.json");
829
830
  if (!exists(scriptPath)) {
830
831
  throw new CliError("EXPORT BLOCKED: script.initial.json not found", "script.initial.json not found.", {
@@ -845,8 +846,29 @@ export async function commandExport(opts) {
845
846
  };
846
847
  return [report, EXIT_NEEDS_AGENT];
847
848
  }
849
+ const heldOut = asList(state["held_out_episodes"]).map((n) => Number(n)).filter((n) => !Number.isNaN(n));
850
+ // A run with terminally-blocked (content-filtered) episodes can still ship the
851
+ // completed ones, but only when the operator explicitly opts in. This also
852
+ // bypasses the review/validation gates for the gaps those held-out episodes
853
+ // create (e.g. episode-number discontinuities).
854
+ if (heldOut.length > 0 && !allowIncomplete) {
855
+ const report = {
856
+ title: "EXPORT BLOCKED: Episodes held out",
857
+ result: [
858
+ "script.initial.json was not exported.",
859
+ `held out (terminal): ${heldOut.join(", ")}`,
860
+ ],
861
+ artifacts: [path.join(directDir(workspace), "run_state.json")],
862
+ next: [
863
+ "Provide the missing episodes with `direct override ep_NNN --from <file>` then rerun init,",
864
+ "or export the completed episodes with `direct export --allow-incomplete`.",
865
+ ],
866
+ };
867
+ return [report, EXIT_NEEDS_AGENT];
868
+ }
869
+ const incompleteOverride = allowIncomplete && heldOut.length > 0;
848
870
  const missingReview = reviewBlockers(state);
849
- if (missingReview.length > 0) {
871
+ if (missingReview.length > 0 && !incompleteOverride) {
850
872
  const report = {
851
873
  title: "EXPORT BLOCKED: Agent review incomplete",
852
874
  result: ["script.initial.json was not exported.", `missing review: ${missingReview.join(", ")}`],
@@ -858,7 +880,7 @@ export async function commandExport(opts) {
858
880
  const validation = validateScript(workspace, scriptPath);
859
881
  const blockingOrError = Boolean(validation["has_blocking"]) ||
860
882
  asList(validation["issues"]).some((it) => isDict(it) && (it["severity"] === "blocking" || it["severity"] === "error"));
861
- if (!validation["passed"] && (!force || blockingOrError)) {
883
+ if (!validation["passed"] && (!force || blockingOrError) && !incompleteOverride) {
862
884
  const title = force
863
885
  ? "EXPORT BLOCKED: Validation errors require repair"
864
886
  : "EXPORT BLOCKED: Validation needs agent repair";
@@ -924,7 +946,7 @@ export async function commandExport(opts) {
924
946
  });
925
947
  await syncValidationResult(remoteSession, validation, revision);
926
948
  const outputLabel = remoteSession.artifactLabel;
927
- updateRunState(workspace, { status: "exported", output_path: outputLabel });
949
+ updateRunState(workspace, { status: "exported", output_path: outputLabel, exported_incomplete: incompleteOverride, held_out_episodes: heldOut });
928
950
  const report = {
929
951
  title: "EXPORT COMPLETE: Final script stored in DB",
930
952
  result: [