@lingjingai/scriptctl 0.10.0 → 0.10.1
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 +6 -6
- package/dist/cli.js +0 -1
- package/dist/cli.js.map +1 -1
- package/dist/common.d.ts +0 -1
- package/dist/common.js +0 -1
- package/dist/common.js.map +1 -1
- package/dist/domain/script-core.d.ts +1 -0
- package/dist/domain/script-core.js +20 -5
- package/dist/domain/script-core.js.map +1 -1
- package/dist/help-text.js +1 -2
- package/dist/help-text.js.map +1 -1
- package/dist/usecases/direct.d.ts +0 -1
- package/dist/usecases/direct.js +3 -24
- package/dist/usecases/direct.js.map +1 -1
- package/dist/usecases/episode.js +6 -3
- package/dist/usecases/episode.js.map +1 -1
- package/dist/usecases/script.d.ts +0 -2
- package/dist/usecases/script.js +50 -75
- package/dist/usecases/script.js.map +1 -1
- package/package.json +1 -1
package/dist/usecases/script.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as os from "node:os";
|
|
3
1
|
import * as path from "node:path";
|
|
4
|
-
import { randomUUID } from "node:crypto";
|
|
5
2
|
import { CliError, EXIT_INPUT, EXIT_NEEDS_AGENT, EXIT_OK, EXIT_RUNTIME, EXIT_USAGE, directDir, exists, readJson, readText, sha256Text, writeJson, } from "../common.js";
|
|
6
3
|
import { applyPatchOperations, collectAssetRefs, collectStateRefs, parseAnyAddress, parseStateTarget, validateScript, PATCH_OP_SCHEMA, } from "../domain/script-core.js";
|
|
7
4
|
import { RemoteScriptOutputStore } from "../infra/script-output-api.js";
|
|
8
5
|
import { LocalScriptOutputStore } from "../infra/local-script-output-store.js";
|
|
9
6
|
import { ScriptOutputApiError, resolveOutputMode, } from "../infra/script-output-store.js";
|
|
10
|
-
import {
|
|
7
|
+
import { readRunState, summarizeIssues, updateRunState, } from "./direct.js";
|
|
11
8
|
function strOf(v) {
|
|
12
9
|
if (v === null || v === undefined)
|
|
13
10
|
return "";
|
|
@@ -65,14 +62,12 @@ function episodeCharCounts(scenes) {
|
|
|
65
62
|
export class ScriptEditSession {
|
|
66
63
|
workspace;
|
|
67
64
|
script;
|
|
68
|
-
scriptPath;
|
|
69
65
|
client;
|
|
70
66
|
projectGroupNo;
|
|
71
67
|
revision;
|
|
72
68
|
constructor(opts) {
|
|
73
69
|
this.workspace = opts.workspace;
|
|
74
70
|
this.script = opts.script;
|
|
75
|
-
this.scriptPath = opts.scriptPath ?? null;
|
|
76
71
|
this.client = opts.client ?? null;
|
|
77
72
|
this.projectGroupNo = opts.projectGroupNo ?? null;
|
|
78
73
|
this.revision = opts.revision ?? null;
|
|
@@ -81,16 +76,10 @@ export class ScriptEditSession {
|
|
|
81
76
|
return this.client !== null;
|
|
82
77
|
}
|
|
83
78
|
get artifactLabel() {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// distinguishable in user-visible report.artifacts entries. The project
|
|
89
|
-
// id is not a secret: the user supplies it themselves via
|
|
90
|
-
// `--project-group-no` or env config.
|
|
91
|
-
return `script-output/${this.projectGroupNo}@revision/${this.revision ?? 0}`;
|
|
92
|
-
}
|
|
93
|
-
return this.scriptPath ?? "";
|
|
79
|
+
// Public label — keeps the project id + revision (not secret; the user
|
|
80
|
+
// supplies the project id via --project-group-no/env) so two project groups
|
|
81
|
+
// at the same revision stay distinguishable in report.artifacts.
|
|
82
|
+
return `script-output/${this.projectGroupNo}@revision/${this.revision ?? 0}`;
|
|
94
83
|
}
|
|
95
84
|
}
|
|
96
85
|
export function scriptOutputClient(opts) {
|
|
@@ -236,7 +225,11 @@ export async function currentRevisionOrZero(client) {
|
|
|
236
225
|
throw exc;
|
|
237
226
|
}
|
|
238
227
|
}
|
|
239
|
-
|
|
228
|
+
// All reads/edits operate on the DB-backed final script. The intermediate
|
|
229
|
+
// script.initial.json is a private artifact between `import init` and
|
|
230
|
+
// `import push`; it is not an editable surface.
|
|
231
|
+
async function loadScript(opts) {
|
|
232
|
+
const workspace = strOf(opts["workspace_path"] || "workspace");
|
|
240
233
|
const client = scriptOutputClient(opts);
|
|
241
234
|
let script;
|
|
242
235
|
let revision;
|
|
@@ -276,34 +269,22 @@ async function loadRemoteScript(opts, workspace) {
|
|
|
276
269
|
revision: Number((revision ?? {})["revision"] ?? 0),
|
|
277
270
|
});
|
|
278
271
|
}
|
|
279
|
-
async function loadScriptForEdit(opts) {
|
|
280
|
-
// All reads/edits operate on the DB-backed final script. The intermediate
|
|
281
|
-
// script.initial.json is a private artifact between `import init` and
|
|
282
|
-
// `import push`; it is no longer an editable surface.
|
|
283
|
-
const workspace = strOf(opts["workspace_path"] || "workspace");
|
|
284
|
-
return loadRemoteScript(opts, workspace);
|
|
285
|
-
}
|
|
286
272
|
function validateSession(session, opts = {}) {
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
273
|
+
const persist = opts.persist ?? true;
|
|
274
|
+
// Validate the in-memory DB script directly (no tmp file). scriptData also
|
|
275
|
+
// tells validateScript not to read the local asset_metadata.json/episode_plan.json
|
|
276
|
+
// parse artifacts — those describe the local initial.json, not the DB script.
|
|
277
|
+
// requireSource:false (DB validation never needs source.txt). persist:false so
|
|
278
|
+
// validateScript doesn't write; we own the write below to set the nicer label.
|
|
279
|
+
const validation = validateScript(session.workspace, null, {
|
|
280
|
+
requireSource: false,
|
|
281
|
+
scriptData: session.script,
|
|
282
|
+
persist: false,
|
|
283
|
+
});
|
|
284
|
+
validation["script_path"] = session.artifactLabel;
|
|
285
|
+
if (persist)
|
|
296
286
|
writeJson(path.join(directDir(session.workspace), "validation.json"), validation);
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
finally {
|
|
300
|
-
try {
|
|
301
|
-
fs.unlinkSync(tmpPath);
|
|
302
|
-
}
|
|
303
|
-
catch {
|
|
304
|
-
// ignore
|
|
305
|
-
}
|
|
306
|
-
}
|
|
287
|
+
return validation;
|
|
307
288
|
}
|
|
308
289
|
function validationIssuePath(issue) {
|
|
309
290
|
if (issue["path"])
|
|
@@ -393,12 +374,8 @@ function requestIdForScriptWrite(opts, op, payload) {
|
|
|
393
374
|
return `scriptctl:${op}:${sha256Text(canonical)}`;
|
|
394
375
|
}
|
|
395
376
|
async function saveScriptSession(session, opts, op) {
|
|
396
|
-
if (!session.remote) {
|
|
397
|
-
writeJson(session.scriptPath, session.script);
|
|
398
|
-
return [null, false];
|
|
399
|
-
}
|
|
400
377
|
if (session.client === null)
|
|
401
|
-
throw new Error("
|
|
378
|
+
throw new Error("script session missing client");
|
|
402
379
|
const baseRevision = Number(session.revision ?? 0);
|
|
403
380
|
const requestId = requestIdForScriptWrite(opts, op, { script: session.script });
|
|
404
381
|
let res;
|
|
@@ -460,7 +437,7 @@ function patchOperationsFromPayload(payload) {
|
|
|
460
437
|
// ---------------------------------------------------------------------------
|
|
461
438
|
export async function commandScriptValidate(opts) {
|
|
462
439
|
const workspace = strOf(opts["workspace_path"] || "workspace");
|
|
463
|
-
const session = await
|
|
440
|
+
const session = await loadScript(opts);
|
|
464
441
|
const validation = validateSession(session);
|
|
465
442
|
await syncValidationResult(session, validation);
|
|
466
443
|
const stats = validation["stats"] ?? {};
|
|
@@ -590,13 +567,13 @@ export async function commandScriptPatch(opts) {
|
|
|
590
567
|
}
|
|
591
568
|
const operations = patchOperationsFromPayload(payload);
|
|
592
569
|
const dryRun = Boolean(opts["dry_run"]);
|
|
593
|
-
const session = await
|
|
570
|
+
const session = await loadScript(opts);
|
|
594
571
|
const script = session.script;
|
|
595
572
|
const applied = applyPatchOperations(script, scriptSourceText(workspace), operations);
|
|
596
573
|
if (dryRun) {
|
|
597
574
|
// Validate the in-memory mutated script WITHOUT calling saveScriptSession.
|
|
598
575
|
// Run validate but skip syncValidationResult (which writes to remote) too.
|
|
599
|
-
const validation = validateSession(session);
|
|
576
|
+
const validation = validateSession(session, { persist: false });
|
|
600
577
|
const passed = Boolean(validation["passed"]);
|
|
601
578
|
const report = {
|
|
602
579
|
title: passed ? "SCRIPT PATCH DRY-RUN PASSED" : "SCRIPT PATCH DRY-RUN: Validation needs repair",
|
|
@@ -618,9 +595,6 @@ export async function commandScriptPatch(opts) {
|
|
|
618
595
|
const [newRevision, idempotent] = await saveScriptSession(session, opts, applied.length === 1 ? applied[0] : "script.patch");
|
|
619
596
|
const validation = validateSession(session);
|
|
620
597
|
await syncValidationResult(session, validation, newRevision);
|
|
621
|
-
// Patching worldview/role/description clears the parse-time
|
|
622
|
-
// LOW_CONFIDENCE_METADATA advisory (asset_metadata.json low → medium).
|
|
623
|
-
markMetadataConfidenceReviewed(workspace, operations);
|
|
624
598
|
const passed = Boolean(validation["passed"]);
|
|
625
599
|
const resultLines = [
|
|
626
600
|
`operations: ${applied.length}`,
|
|
@@ -648,7 +622,7 @@ export async function commandScriptPatch(opts) {
|
|
|
648
622
|
// ---------------------------------------------------------------------------
|
|
649
623
|
async function applySingleScriptOp(opts, op) {
|
|
650
624
|
const workspace = strOf(opts["workspace_path"] || "workspace");
|
|
651
|
-
const session = await
|
|
625
|
+
const session = await loadScript(opts);
|
|
652
626
|
const script = session.script;
|
|
653
627
|
const applied = applyPatchOperations(script, scriptSourceText(workspace), [op]);
|
|
654
628
|
const opName = applied[0] ?? strOf(op["op"]);
|
|
@@ -690,7 +664,7 @@ function summarizeScriptOp(_script, op) {
|
|
|
690
664
|
return `已执行 ${kind}`;
|
|
691
665
|
}
|
|
692
666
|
async function commandStateRefs(opts, plan = false) {
|
|
693
|
-
const session = await
|
|
667
|
+
const session = await loadScript(opts);
|
|
694
668
|
const script = session.script;
|
|
695
669
|
const args = asList(opts["_args"]);
|
|
696
670
|
const [targetKind, targetId, stateId] = parseStateTarget(args[0] ?? "");
|
|
@@ -744,7 +718,10 @@ export async function commandPush(opts) {
|
|
|
744
718
|
}
|
|
745
719
|
const state = readRunState(workspace);
|
|
746
720
|
const provider = strOf(state["provider"]);
|
|
747
|
-
|
|
721
|
+
// SCRIPTCTL_ALLOW_MOCK_PUSH is the current name; SCRIPTCTL_ALLOW_MOCK_EXPORT is
|
|
722
|
+
// still honored for back-compat with environments configured before the rename.
|
|
723
|
+
const allowMock = process.env.SCRIPTCTL_ALLOW_MOCK_PUSH === "1" || process.env.SCRIPTCTL_ALLOW_MOCK_EXPORT === "1";
|
|
724
|
+
if (provider === "mock" && !allowMock) {
|
|
748
725
|
const report = {
|
|
749
726
|
title: "PUSH BLOCKED: Mock provider result",
|
|
750
727
|
result: ["script.initial.json was produced by --provider mock and was not pushed."],
|
|
@@ -776,15 +753,13 @@ export async function commandPush(opts) {
|
|
|
776
753
|
});
|
|
777
754
|
}
|
|
778
755
|
// Validation is advisory, not a gate: push always publishes so the agent can
|
|
779
|
-
// self-review and repair on the DB script afterwards.
|
|
780
|
-
//
|
|
781
|
-
|
|
756
|
+
// self-review and repair on the DB script afterwards. requireSource:false so a
|
|
757
|
+
// missing source.txt never aborts the publish. Results are still computed and
|
|
758
|
+
// synced so the review subagent has the issue list to act on.
|
|
759
|
+
const validation = validateScript(workspace, scriptPath, { requireSource: false });
|
|
782
760
|
const client = scriptOutputClient(opts);
|
|
783
761
|
const baseRevision = await currentRevisionOrZero(client);
|
|
784
|
-
|
|
785
|
-
const sortedScript = sortDeep(script);
|
|
786
|
-
const scriptHash = sha256Text(JSON.stringify(sortedScript));
|
|
787
|
-
const requestId = strOf(opts["request_id"]).trim() || `scriptctl-import-push:${scriptHash}`;
|
|
762
|
+
const requestId = requestIdForScriptWrite(opts, "import.push", { script });
|
|
788
763
|
let replaceRes;
|
|
789
764
|
try {
|
|
790
765
|
replaceRes = await client.replaceScript({
|
|
@@ -971,7 +946,7 @@ function buildReport(op, title, lines, artifactLabel, next) {
|
|
|
971
946
|
}
|
|
972
947
|
// ----- summary --------------------------------------------------------------
|
|
973
948
|
export async function commandSummary(opts) {
|
|
974
|
-
const session = await
|
|
949
|
+
const session = await loadScript(opts);
|
|
975
950
|
const script = session.script;
|
|
976
951
|
const episodes = asList(script["episodes"]);
|
|
977
952
|
const scenes = [];
|
|
@@ -994,7 +969,7 @@ export async function commandSummary(opts) {
|
|
|
994
969
|
}
|
|
995
970
|
// ----- episodes -------------------------------------------------------------
|
|
996
971
|
export async function commandEpisodes(opts) {
|
|
997
|
-
const session = await
|
|
972
|
+
const session = await loadScript(opts);
|
|
998
973
|
const script = session.script;
|
|
999
974
|
const itemId = strOf(opts["id"]).trim();
|
|
1000
975
|
const minChars = parseBound(opts["min_chars"]);
|
|
@@ -1048,7 +1023,7 @@ function formatScene(epId, scene, names) {
|
|
|
1048
1023
|
return `${epId}/${scene["scene_id"]} [${space} ${time}] location=${locations.join(",") || "-"} actors=${actors.join(",") || "-"} props=${props.join(",") || "-"} actions=${actionCount}`;
|
|
1049
1024
|
}
|
|
1050
1025
|
export async function commandScenes(opts) {
|
|
1051
|
-
const session = await
|
|
1026
|
+
const session = await loadScript(opts);
|
|
1052
1027
|
const script = session.script;
|
|
1053
1028
|
const inFilter = parseInFilter(strOf(opts["in"]));
|
|
1054
1029
|
const hasActor = strOf(opts["has_actor"]).trim();
|
|
@@ -1086,7 +1061,7 @@ export async function commandScenes(opts) {
|
|
|
1086
1061
|
}
|
|
1087
1062
|
// ----- actions --------------------------------------------------------------
|
|
1088
1063
|
export async function commandActions(opts) {
|
|
1089
|
-
const session = await
|
|
1064
|
+
const session = await loadScript(opts);
|
|
1090
1065
|
const script = session.script;
|
|
1091
1066
|
const inFilter = parseInFilter(strOf(opts["in"]));
|
|
1092
1067
|
const grep = strOf(opts["grep"]);
|
|
@@ -1271,7 +1246,7 @@ function listAssetsByKind(script, kind, opts) {
|
|
|
1271
1246
|
return lines;
|
|
1272
1247
|
}
|
|
1273
1248
|
export async function commandActors(opts) {
|
|
1274
|
-
const session = await
|
|
1249
|
+
const session = await loadScript(opts);
|
|
1275
1250
|
const lines = listAssetsByKind(session.script, "actor", {
|
|
1276
1251
|
id: strOf(opts["id"]).trim(),
|
|
1277
1252
|
name: strOf(opts["name"]).trim(),
|
|
@@ -1280,7 +1255,7 @@ export async function commandActors(opts) {
|
|
|
1280
1255
|
return [buildReport("query.actors", "ACTORS", lines, session.artifactLabel, ["Use `scriptctl rename actor:<id>` / `describe` / `merge` to edit."]), EXIT_OK];
|
|
1281
1256
|
}
|
|
1282
1257
|
export async function commandLocations(opts) {
|
|
1283
|
-
const session = await
|
|
1258
|
+
const session = await loadScript(opts);
|
|
1284
1259
|
const lines = listAssetsByKind(session.script, "location", {
|
|
1285
1260
|
id: strOf(opts["id"]).trim(),
|
|
1286
1261
|
name: strOf(opts["name"]).trim(),
|
|
@@ -1289,7 +1264,7 @@ export async function commandLocations(opts) {
|
|
|
1289
1264
|
return [buildReport("query.locations", "LOCATIONS", lines, session.artifactLabel, ["Use `scriptctl rename location:<id>` etc. to edit."]), EXIT_OK];
|
|
1290
1265
|
}
|
|
1291
1266
|
export async function commandProps(opts) {
|
|
1292
|
-
const session = await
|
|
1267
|
+
const session = await loadScript(opts);
|
|
1293
1268
|
const lines = listAssetsByKind(session.script, "prop", {
|
|
1294
1269
|
id: strOf(opts["id"]).trim(),
|
|
1295
1270
|
name: strOf(opts["name"]).trim(),
|
|
@@ -1298,7 +1273,7 @@ export async function commandProps(opts) {
|
|
|
1298
1273
|
return [buildReport("query.props", "PROPS", lines, session.artifactLabel, ["Use `scriptctl rename prop:<id>` etc. to edit."]), EXIT_OK];
|
|
1299
1274
|
}
|
|
1300
1275
|
export async function commandAssets(opts) {
|
|
1301
|
-
const session = await
|
|
1276
|
+
const session = await loadScript(opts);
|
|
1302
1277
|
const kindFilter = strOf(opts["kind"]).trim();
|
|
1303
1278
|
const nameOpt = strOf(opts["name"]).trim();
|
|
1304
1279
|
const idOpt = strOf(opts["id"]).trim();
|
|
@@ -1313,7 +1288,7 @@ export async function commandAssets(opts) {
|
|
|
1313
1288
|
}
|
|
1314
1289
|
// ----- speakers -------------------------------------------------------------
|
|
1315
1290
|
export async function commandSpeakers(opts) {
|
|
1316
|
-
const session = await
|
|
1291
|
+
const session = await loadScript(opts);
|
|
1317
1292
|
const script = session.script;
|
|
1318
1293
|
const idOpt = strOf(opts["id"]).trim();
|
|
1319
1294
|
const nameOpt = strOf(opts["name"]).trim();
|
|
@@ -1336,7 +1311,7 @@ export async function commandSpeakers(opts) {
|
|
|
1336
1311
|
}
|
|
1337
1312
|
// ----- issues ---------------------------------------------------------------
|
|
1338
1313
|
export async function commandIssues(opts) {
|
|
1339
|
-
const session = await
|
|
1314
|
+
const session = await loadScript(opts);
|
|
1340
1315
|
const severityFilter = strOf(opts["severity"]).trim();
|
|
1341
1316
|
const codeFilter = strOf(opts["code"]).trim();
|
|
1342
1317
|
const validation = validateSession(session);
|
|
@@ -1358,7 +1333,7 @@ export async function commandIssues(opts) {
|
|
|
1358
1333
|
}
|
|
1359
1334
|
// ----- refs (unified reverse lookup) ----------------------------------------
|
|
1360
1335
|
export async function commandRefs(opts) {
|
|
1361
|
-
const session = await
|
|
1336
|
+
const session = await loadScript(opts);
|
|
1362
1337
|
const script = session.script;
|
|
1363
1338
|
const args = asList(opts["_args"]);
|
|
1364
1339
|
const target = strOf(args[0]).trim();
|