@lingjingai/scriptctl 0.1.0 → 0.2.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.
- package/README.md +71 -0
- package/dist/cli.js +305 -396
- package/dist/cli.js.map +1 -1
- package/dist/domain/asset-registry.d.ts +141 -0
- package/dist/domain/asset-registry.js +318 -0
- package/dist/domain/asset-registry.js.map +1 -0
- package/dist/domain/collision-detector.d.ts +83 -0
- package/dist/domain/collision-detector.js +248 -0
- package/dist/domain/collision-detector.js.map +1 -0
- package/dist/domain/direct-core.d.ts +13 -1
- package/dist/domain/direct-core.js +14 -4
- package/dist/domain/direct-core.js.map +1 -1
- package/dist/domain/script-core.d.ts +11 -0
- package/dist/domain/script-core.js +34 -19
- package/dist/domain/script-core.js.map +1 -1
- package/dist/help-text.js +283 -0
- package/dist/help-text.js.map +1 -1
- package/dist/infra/converters.js +21 -7
- package/dist/infra/converters.js.map +1 -1
- package/dist/infra/default-writing-prompt.d.ts +31 -0
- package/dist/infra/default-writing-prompt.js +50 -0
- package/dist/infra/default-writing-prompt.js.map +1 -0
- package/dist/infra/default-writing-prompt.md +115 -0
- package/dist/infra/gemini-writer.d.ts +107 -0
- package/dist/infra/gemini-writer.js +207 -0
- package/dist/infra/gemini-writer.js.map +1 -0
- package/dist/infra/providers.d.ts +36 -0
- package/dist/infra/providers.js +186 -2
- package/dist/infra/providers.js.map +1 -1
- package/dist/usecases/episode.d.ts +47 -0
- package/dist/usecases/episode.js +1188 -0
- package/dist/usecases/episode.js.map +1 -0
- package/dist/usecases/script.d.ts +6 -2
- package/dist/usecases/script.js +4 -4
- package/dist/usecases/script.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset collision detector for cross-episode incremental drafting.
|
|
3
|
+
*
|
|
4
|
+
* When a freshly-parsed fragment declares a new asset (in `## 人物`/`## 场景`/`## 道具`/
|
|
5
|
+
* `## 发声源`) whose name already exists in the registry, the detector raises a
|
|
6
|
+
* structured report. The orchestrator (episode subcommand) is expected to surface this
|
|
7
|
+
* to the agent, which decides among three resolutions:
|
|
8
|
+
* - accept_identity: same entity, drop the duplicate declaration (refs use existing id)
|
|
9
|
+
* - distinguish_rename: different entity, rename the new one to disambiguate
|
|
10
|
+
* - promote_to_state: same entity, different state — add a state to existing asset
|
|
11
|
+
*
|
|
12
|
+
* Only exact-name collisions are detected. The kind reflects whether the agent
|
|
13
|
+
* should treat it as blocking or a warning, based on description similarity:
|
|
14
|
+
* - hard: exact name match (description similar enough that it's almost certainly
|
|
15
|
+
* the same entity). Blocks commit; expects agent resolution.
|
|
16
|
+
* - soft: exact name match but descriptions clearly diverge — could be intentional
|
|
17
|
+
* reuse of a generic label (e.g. two unrelated "路人甲"). Warn but don't block.
|
|
18
|
+
*
|
|
19
|
+
* The detector is a pure function: it reads the fragment and the registry,
|
|
20
|
+
* never mutates either.
|
|
21
|
+
*/
|
|
22
|
+
import { lookupActor, lookupLocation, lookupProp, lookupSpeaker, } from "./asset-registry.js";
|
|
23
|
+
const DEFAULT_SOFT_THRESHOLD = 0.4;
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Similarity scoring (lightweight, dependency-free)
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
/**
|
|
28
|
+
* Character-level Jaccard similarity. Trims and normalises punctuation/whitespace.
|
|
29
|
+
* Tuned for short Chinese/English descriptions (≤ 100 chars), which is the typical
|
|
30
|
+
* scriptctl asset description length.
|
|
31
|
+
*
|
|
32
|
+
* Returns 1.0 when both strings are empty (or one is empty — to avoid false soft
|
|
33
|
+
* collisions when an existing entry has no description yet).
|
|
34
|
+
*/
|
|
35
|
+
function descriptionSimilarity(a, b) {
|
|
36
|
+
const aa = (a ?? "").replace(/[\s\p{P}]+/gu, "");
|
|
37
|
+
const bb = (b ?? "").replace(/[\s\p{P}]+/gu, "");
|
|
38
|
+
if (aa === "" || bb === "")
|
|
39
|
+
return 1.0;
|
|
40
|
+
const setA = new Set([...aa]);
|
|
41
|
+
const setB = new Set([...bb]);
|
|
42
|
+
let intersect = 0;
|
|
43
|
+
for (const ch of setA)
|
|
44
|
+
if (setB.has(ch))
|
|
45
|
+
intersect += 1;
|
|
46
|
+
const union = setA.size + setB.size - intersect;
|
|
47
|
+
return union === 0 ? 1.0 : intersect / union;
|
|
48
|
+
}
|
|
49
|
+
function classifyMatch(fragmentDesc, registryDesc, softThreshold) {
|
|
50
|
+
const sim = descriptionSimilarity(fragmentDesc, registryDesc);
|
|
51
|
+
if (sim < softThreshold)
|
|
52
|
+
return { kind: "soft", similarity: sim };
|
|
53
|
+
return { kind: "hard", similarity: sim };
|
|
54
|
+
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Entry → report builders
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
function toRegistryReport(entry) {
|
|
59
|
+
const base = {
|
|
60
|
+
id: entry.id,
|
|
61
|
+
name: entry.name,
|
|
62
|
+
description: entry.description,
|
|
63
|
+
firstSeenEpisode: entry.firstSeenEpisode,
|
|
64
|
+
firstSeenScene: entry.firstSeenScene,
|
|
65
|
+
firstSeenAction: entry.firstSeenAction,
|
|
66
|
+
lastSeenEpisode: entry.lastSeenEpisode,
|
|
67
|
+
appearanceCount: entry.appearanceCount,
|
|
68
|
+
states: entry.states.map((s) => ({ id: s.id, name: s.name })),
|
|
69
|
+
};
|
|
70
|
+
if ("sourceKind" in entry)
|
|
71
|
+
base.sourceKind = entry.sourceKind;
|
|
72
|
+
return base;
|
|
73
|
+
}
|
|
74
|
+
function findFragmentFirstSeen(fragment, episode, predicate, actionPicker) {
|
|
75
|
+
const scenes = fragment.scenes ?? [];
|
|
76
|
+
for (let i = 0; i < scenes.length; i++) {
|
|
77
|
+
if (!predicate(i))
|
|
78
|
+
continue;
|
|
79
|
+
const sceneNum = typeof scenes[i].scene_num === "number" ? scenes[i].scene_num : i + 1;
|
|
80
|
+
const sceneId = `ep_${String(episode).padStart(3, "0")}/scn_${String(sceneNum).padStart(3, "0")}`;
|
|
81
|
+
return { scene: sceneId, action: actionPicker(i) };
|
|
82
|
+
}
|
|
83
|
+
return { scene: "", action: "" };
|
|
84
|
+
}
|
|
85
|
+
function locateActorFirstSeen(fragment, episode, name) {
|
|
86
|
+
return findFragmentFirstSeen(fragment, episode, (i) => (fragment.scenes?.[i]?.actor_names ?? []).includes(name), (i) => {
|
|
87
|
+
const firstAction = (fragment.scenes?.[i]?.actions ?? []).find((a) => typeof a.content === "string" && a.content.includes(name));
|
|
88
|
+
return firstAction ? String(firstAction.content ?? "").trim() : "";
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function locateLocationFirstSeen(fragment, episode, name) {
|
|
92
|
+
return findFragmentFirstSeen(fragment, episode, (i) => fragment.scenes?.[i]?.location_name === name, (i) => String(fragment.scenes?.[i]?.actions?.[0]?.content ?? "").trim());
|
|
93
|
+
}
|
|
94
|
+
function locatePropFirstSeen(fragment, episode, name) {
|
|
95
|
+
return findFragmentFirstSeen(fragment, episode, (i) => (fragment.scenes?.[i]?.prop_names ?? []).includes(name), (i) => {
|
|
96
|
+
const firstAction = (fragment.scenes?.[i]?.actions ?? []).find((a) => typeof a.content === "string" && a.content.includes(name));
|
|
97
|
+
return firstAction ? String(firstAction.content ?? "").trim() : "";
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function locateSpeakerFirstSeen(fragment, episode, name) {
|
|
101
|
+
return findFragmentFirstSeen(fragment, episode, (i) => (fragment.scenes?.[i]?.actions ?? []).some((a) => typeof a.speaker === "string" && a.speaker === name), (i) => {
|
|
102
|
+
const firstAction = (fragment.scenes?.[i]?.actions ?? []).find((a) => typeof a.speaker === "string" && a.speaker === name);
|
|
103
|
+
return firstAction ? String(firstAction.content ?? "").trim() : "";
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function buildReport(kind, assetType, decl, firstSeen, entry, similarity) {
|
|
107
|
+
const resolutions = assetType === "actor"
|
|
108
|
+
? ["accept_identity", "distinguish_rename", "promote_to_state"]
|
|
109
|
+
: ["accept_identity", "distinguish_rename"];
|
|
110
|
+
return {
|
|
111
|
+
kind,
|
|
112
|
+
assetType,
|
|
113
|
+
fragmentDeclaration: {
|
|
114
|
+
name: decl.name,
|
|
115
|
+
description: decl.description ?? null,
|
|
116
|
+
firstSeenScene: firstSeen.scene || undefined,
|
|
117
|
+
firstSeenAction: firstSeen.action || undefined,
|
|
118
|
+
},
|
|
119
|
+
registryEntry: toRegistryReport(entry),
|
|
120
|
+
descriptionSimilarity: similarity,
|
|
121
|
+
resolutions,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Public API
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
/**
|
|
128
|
+
* Detect collisions between a freshly-parsed fragment and the existing registry.
|
|
129
|
+
*
|
|
130
|
+
* Returns a (possibly empty) list of reports. Caller (episode subcommand) treats:
|
|
131
|
+
* - `kind: "hard"` as blocking — agent must resolve before commit
|
|
132
|
+
* - `kind: "soft"` as warning — agent should consider but may proceed
|
|
133
|
+
* - `kind: "hint"` as info — surfaced for awareness only
|
|
134
|
+
*
|
|
135
|
+
* The episode number is taken from `fragment.episode` if available, otherwise
|
|
136
|
+
* passed via `opts.episode` (caller responsibility).
|
|
137
|
+
*/
|
|
138
|
+
export function detectCollisions(fragment, registry, opts = {}) {
|
|
139
|
+
const softThreshold = opts.softSimilarityThreshold ?? DEFAULT_SOFT_THRESHOLD;
|
|
140
|
+
const episode = opts.episode ?? (typeof fragment.episode === "number" ? fragment.episode : 0);
|
|
141
|
+
const reports = [];
|
|
142
|
+
for (const decl of fragment.actors ?? []) {
|
|
143
|
+
const existing = lookupActor(registry, decl.name);
|
|
144
|
+
if (existing) {
|
|
145
|
+
const { kind, similarity } = classifyMatch(decl.description, existing.description, softThreshold);
|
|
146
|
+
const firstSeen = locateActorFirstSeen(fragment, episode, decl.name);
|
|
147
|
+
reports.push(buildReport(kind, "actor", decl, firstSeen, existing, similarity));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const decl of fragment.locations ?? []) {
|
|
151
|
+
const existing = lookupLocation(registry, decl.name);
|
|
152
|
+
if (existing) {
|
|
153
|
+
const { kind, similarity } = classifyMatch(decl.description, existing.description, softThreshold);
|
|
154
|
+
const firstSeen = locateLocationFirstSeen(fragment, episode, decl.name);
|
|
155
|
+
reports.push(buildReport(kind, "location", decl, firstSeen, existing, similarity));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const decl of fragment.props ?? []) {
|
|
159
|
+
const existing = lookupProp(registry, decl.name);
|
|
160
|
+
if (existing) {
|
|
161
|
+
const { kind, similarity } = classifyMatch(decl.description, existing.description, softThreshold);
|
|
162
|
+
const firstSeen = locatePropFirstSeen(fragment, episode, decl.name);
|
|
163
|
+
reports.push(buildReport(kind, "prop", decl, firstSeen, existing, similarity));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
for (const decl of fragment.speakers ?? []) {
|
|
167
|
+
const existing = lookupSpeaker(registry, decl.name, decl.sourceKind);
|
|
168
|
+
if (existing) {
|
|
169
|
+
const { kind, similarity } = classifyMatch(decl.description, existing.description, softThreshold);
|
|
170
|
+
const firstSeen = locateSpeakerFirstSeen(fragment, episode, decl.name);
|
|
171
|
+
reports.push(buildReport(kind, "speaker", decl, firstSeen, existing, similarity));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return reports;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Whether any report has `kind: "hard"` — convenience for episode.ts commit gating.
|
|
178
|
+
*/
|
|
179
|
+
export function hasHardCollision(reports) {
|
|
180
|
+
return reports.some((r) => r.kind === "hard");
|
|
181
|
+
}
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Human-readable summary (for CLI stderr output)
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
/**
|
|
186
|
+
* Render a list of CollisionReports as a human-readable markdown summary suitable
|
|
187
|
+
* for stderr output. The machine-readable JSON is expected to be written to
|
|
188
|
+
* `workspace/episodes/ep<n>.collision.json` by the orchestrator.
|
|
189
|
+
*/
|
|
190
|
+
export function summarizeReports(reports) {
|
|
191
|
+
if (reports.length === 0)
|
|
192
|
+
return "";
|
|
193
|
+
const lines = [`检测到 ${reports.length} 个资产冲突:`, ""];
|
|
194
|
+
for (let i = 0; i < reports.length; i++) {
|
|
195
|
+
const r = reports[i];
|
|
196
|
+
lines.push(`### [${i + 1}/${reports.length}] ${r.kind.toUpperCase()} · ${r.assetType} · "${r.fragmentDeclaration.name}"`);
|
|
197
|
+
lines.push("");
|
|
198
|
+
lines.push("新声明:");
|
|
199
|
+
lines.push(` 名字: ${r.fragmentDeclaration.name}`);
|
|
200
|
+
if (r.fragmentDeclaration.description) {
|
|
201
|
+
lines.push(` 描述: ${r.fragmentDeclaration.description}`);
|
|
202
|
+
}
|
|
203
|
+
if (r.fragmentDeclaration.firstSeenScene) {
|
|
204
|
+
lines.push(` 首次出现: ${r.fragmentDeclaration.firstSeenScene}`);
|
|
205
|
+
}
|
|
206
|
+
if (r.fragmentDeclaration.firstSeenAction) {
|
|
207
|
+
lines.push(` 首次动作: ${r.fragmentDeclaration.firstSeenAction}`);
|
|
208
|
+
}
|
|
209
|
+
lines.push("");
|
|
210
|
+
lines.push(`已存在 (${r.registryEntry.id}):`);
|
|
211
|
+
lines.push(` 名字: ${r.registryEntry.name}`);
|
|
212
|
+
if (r.registryEntry.description) {
|
|
213
|
+
lines.push(` 描述: ${r.registryEntry.description}`);
|
|
214
|
+
}
|
|
215
|
+
if (r.registryEntry.sourceKind) {
|
|
216
|
+
lines.push(` 来源类型: ${r.registryEntry.sourceKind}`);
|
|
217
|
+
}
|
|
218
|
+
lines.push(` 首次出现: ${r.registryEntry.firstSeenScene} (ep${r.registryEntry.firstSeenEpisode})`);
|
|
219
|
+
if (r.registryEntry.firstSeenAction) {
|
|
220
|
+
lines.push(` 首次动作: ${r.registryEntry.firstSeenAction}`);
|
|
221
|
+
}
|
|
222
|
+
lines.push(` 最近出现: ep${r.registryEntry.lastSeenEpisode}`);
|
|
223
|
+
lines.push(` 出现总集数: ${r.registryEntry.appearanceCount}`);
|
|
224
|
+
if (r.registryEntry.states.length > 0) {
|
|
225
|
+
lines.push(` 已注册状态: [${r.registryEntry.states.map((s) => s.name).join(", ")}]`);
|
|
226
|
+
}
|
|
227
|
+
lines.push(` 描述相似度: ${(r.descriptionSimilarity * 100).toFixed(0)}%`);
|
|
228
|
+
lines.push("");
|
|
229
|
+
lines.push("可能处置:");
|
|
230
|
+
if (r.resolutions.includes("accept_identity")) {
|
|
231
|
+
lines.push(` [A] 同一${r.assetType === "actor" ? "人" : "项"},沿用 ${r.registryEntry.id}`);
|
|
232
|
+
lines.push(` → 编辑 ep<n>.md,删除资产段中重复声明,refs 不动`);
|
|
233
|
+
}
|
|
234
|
+
if (r.resolutions.includes("distinguish_rename")) {
|
|
235
|
+
lines.push(` [B] 不同${r.assetType === "actor" ? "人" : "项"},需区分`);
|
|
236
|
+
lines.push(` → 把这集所有 "${r.fragmentDeclaration.name}" 改名为更具体的名字`);
|
|
237
|
+
}
|
|
238
|
+
if (r.resolutions.includes("promote_to_state")) {
|
|
239
|
+
lines.push(` [C] 同一人不同形态/装扮`);
|
|
240
|
+
lines.push(` → scriptctl script state add actor:${r.registryEntry.id} --state-id <id> --name <name> --description <desc>`);
|
|
241
|
+
}
|
|
242
|
+
lines.push("");
|
|
243
|
+
}
|
|
244
|
+
lines.push("完整碰撞详情见 workspace/episodes/ep<n>.collision.json");
|
|
245
|
+
lines.push("解决后重跑:scriptctl episode draft <n> --resume (跳过 Gemini,仅重跑 parser + collisionCheck)");
|
|
246
|
+
return lines.join("\n");
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=collision-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collision-detector.js","sourceRoot":"","sources":["../../src/domain/collision-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -86,7 +86,19 @@ export declare function parseOverlapMarkdownLines(content: string, scene: Dict):
|
|
|
86
86
|
speaker_kind: string;
|
|
87
87
|
content: string;
|
|
88
88
|
}>;
|
|
89
|
-
export
|
|
89
|
+
export interface ParseMarkdownBatchOptions {
|
|
90
|
+
/**
|
|
91
|
+
* When true, the parser treats input as a single-episode fragment:
|
|
92
|
+
* - batchPlan fields beyond `episode` may be omitted (sensible defaults applied)
|
|
93
|
+
* - the `# 资产` section is optional (already accepted by the strict parser, but explicitly tolerated here)
|
|
94
|
+
* - scene numbers need not start from 1 or be consecutive
|
|
95
|
+
* - error messages use "fragment" terminology
|
|
96
|
+
*
|
|
97
|
+
* Default false preserves existing direct init behavior (zero regression).
|
|
98
|
+
*/
|
|
99
|
+
fragmentMode?: boolean;
|
|
100
|
+
}
|
|
101
|
+
export declare function parseMarkdownBatch(text: string, batchPlan: Dict, opts?: ParseMarkdownBatchOptions): Dict;
|
|
90
102
|
export declare function expandCompactEpisodeResult(result: Dict, episodePlan: Dict): Dict;
|
|
91
103
|
export declare function compactEpisodeResult(result: Dict): Dict;
|
|
92
104
|
export declare function compactBatchResult(result: Dict): Dict;
|
|
@@ -1205,9 +1205,13 @@ export function parseSpeakerRef(raw) {
|
|
|
1205
1205
|
if (!value)
|
|
1206
1206
|
return ["actor", ""];
|
|
1207
1207
|
if (value.includes(":") || value.includes(":")) {
|
|
1208
|
+
// reSplit only emits the separator slot when the pattern has a capture group;
|
|
1209
|
+
// /[::]/ doesn't, so the result is ["<kind>", "<name>"] — name lives at [1].
|
|
1210
|
+
// (Earlier code read parts[2], which is always undefined here and silently
|
|
1211
|
+
// dropped the name, leaving e.g. `system:李老板` as ["system", ""].)
|
|
1208
1212
|
const parts = reSplit(/[::]/, value, 1);
|
|
1209
1213
|
const left = parts[0] ?? "";
|
|
1210
|
-
const right = parts[
|
|
1214
|
+
const right = parts[1] ?? "";
|
|
1211
1215
|
const kind = _MD_SPEAKER_KIND_NORM[left.trim().toLowerCase()] ?? _MD_SPEAKER_KIND_NORM[left.trim()];
|
|
1212
1216
|
if (kind)
|
|
1213
1217
|
return [kind, right.trim()];
|
|
@@ -1388,8 +1392,11 @@ function applyMarkdownDialogueAnchor(action, scene, rawSpeakerArg, rawTag) {
|
|
|
1388
1392
|
if (emotion)
|
|
1389
1393
|
action["emotion"] = emotion;
|
|
1390
1394
|
}
|
|
1391
|
-
export function parseMarkdownBatch(text, batchPlan) {
|
|
1392
|
-
const
|
|
1395
|
+
export function parseMarkdownBatch(text, batchPlan, opts = {}) {
|
|
1396
|
+
const fragmentMode = opts.fragmentMode === true;
|
|
1397
|
+
const labelKind = fragmentMode ? "fragment" : "batch";
|
|
1398
|
+
const labelPart = batchPlan["part"] === undefined && fragmentMode ? 1 : batchPlan["part"];
|
|
1399
|
+
const label = `${batchPlan["batch_id"] || labelKind} episode ${batchPlan["episode"]} part ${labelPart}`;
|
|
1393
1400
|
let section = null;
|
|
1394
1401
|
let assetSubsection = null;
|
|
1395
1402
|
const scenes = [];
|
|
@@ -1603,7 +1610,10 @@ export function parseMarkdownBatch(text, batchPlan) {
|
|
|
1603
1610
|
}
|
|
1604
1611
|
}
|
|
1605
1612
|
if (scenes.length === 0) {
|
|
1606
|
-
|
|
1613
|
+
const hint = fragmentMode
|
|
1614
|
+
? "no scenes parsed in fragment; expected at least one '## 场景 N' header under '# 剧本'"
|
|
1615
|
+
: "no scenes parsed; expected '## 场景 N' headers under '# 剧本'";
|
|
1616
|
+
throw _mdInvalid(label, hint);
|
|
1607
1617
|
}
|
|
1608
1618
|
return {
|
|
1609
1619
|
episode: Number(batchPlan["episode"] ?? 0),
|