@lingjingai/scriptctl 0.9.8 → 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 +11 -24
- package/dist/cli.js.map +1 -1
- package/dist/common.d.ts +0 -4
- package/dist/common.js +0 -8
- package/dist/common.js.map +1 -1
- package/dist/domain/direct-core.d.ts +1 -1
- package/dist/domain/script-core.d.ts +1 -0
- package/dist/domain/script-core.js +24 -9
- package/dist/domain/script-core.js.map +1 -1
- package/dist/help-text.js +35 -95
- package/dist/help-text.js.map +1 -1
- package/dist/infra/default-writing-prompt.d.ts +1 -1
- package/dist/infra/default-writing-prompt.js +1 -1
- package/dist/usecases/direct.d.ts +0 -6
- package/dist/usecases/direct.js +13 -478
- package/dist/usecases/direct.js.map +1 -1
- package/dist/usecases/episode.js +8 -5
- package/dist/usecases/episode.js.map +1 -1
- package/dist/usecases/script.d.ts +1 -4
- package/dist/usecases/script.js +72 -207
- package/dist/usecases/script.js.map +1 -1
- package/package.json +2 -2
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 {
|
|
5
|
-
import { CliError, EXIT_INPUT, EXIT_NEEDS_AGENT, EXIT_OK, EXIT_RUNTIME, EXIT_USAGE, directDir, exists, readJson, readText, scriptJsonPath, sha256Text, writeJson, } from "../common.js";
|
|
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) {
|
|
@@ -212,7 +201,7 @@ export function apiErrorToCli(title, exc) {
|
|
|
212
201
|
isConflict
|
|
213
202
|
? "Reload the latest script revision and retry."
|
|
214
203
|
: isMissing
|
|
215
|
-
? "Run `scriptctl
|
|
204
|
+
? "Run `scriptctl import push` to publish a script first."
|
|
216
205
|
: "Run `scriptctl doctor` to verify gateway configuration; retry if it was transient.",
|
|
217
206
|
],
|
|
218
207
|
errorCode: isConflict
|
|
@@ -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;
|
|
@@ -255,7 +248,7 @@ async function loadRemoteScript(opts, workspace) {
|
|
|
255
248
|
exitCode: EXIT_INPUT,
|
|
256
249
|
required: ["existing script-output project document"],
|
|
257
250
|
received: [`projectGroupNo=${client.projectGroupNo}`],
|
|
258
|
-
nextSteps: ["Run `scriptctl
|
|
251
|
+
nextSteps: ["Run `scriptctl import push` to publish a script first."],
|
|
259
252
|
errorCode: "SCRIPT_NOT_FOUND",
|
|
260
253
|
});
|
|
261
254
|
}
|
|
@@ -276,66 +269,22 @@ async function loadRemoteScript(opts, workspace) {
|
|
|
276
269
|
revision: Number((revision ?? {})["revision"] ?? 0),
|
|
277
270
|
});
|
|
278
271
|
}
|
|
279
|
-
async function loadScriptForEdit(opts) {
|
|
280
|
-
const workspace = strOf(opts["workspace_path"] || "workspace");
|
|
281
|
-
if (!opts["script_path"]) {
|
|
282
|
-
return loadRemoteScript(opts, workspace);
|
|
283
|
-
}
|
|
284
|
-
const p = scriptJsonPath(opts);
|
|
285
|
-
if (!exists(p)) {
|
|
286
|
-
throw new CliError("SCRIPT BLOCKED: Local script file not found", "Local script file not found.", {
|
|
287
|
-
exitCode: EXIT_INPUT,
|
|
288
|
-
required: ["--script-path existing local script JSON"],
|
|
289
|
-
received: [p],
|
|
290
|
-
nextSteps: ["Pass --script-path to an existing local intermediate script JSON, or omit --script-path to use DB-backed script-output."],
|
|
291
|
-
errorCode: "SCRIPT_NOT_FOUND",
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
let data;
|
|
295
|
-
try {
|
|
296
|
-
data = readJson(p);
|
|
297
|
-
}
|
|
298
|
-
catch (exc) {
|
|
299
|
-
throw new CliError("SCRIPT BLOCKED: script JSON invalid", "script JSON invalid.", {
|
|
300
|
-
exitCode: EXIT_INPUT,
|
|
301
|
-
required: ["valid JSON"],
|
|
302
|
-
received: [`${p}: ${exc.message}`],
|
|
303
|
-
nextSteps: ["Fix the local script JSON syntax before editing."],
|
|
304
|
-
errorCode: "SCRIPT_JSON_INVALID",
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
if (!isDict(data)) {
|
|
308
|
-
throw new CliError("SCRIPT BLOCKED: script root invalid", "script root invalid.", {
|
|
309
|
-
exitCode: EXIT_USAGE,
|
|
310
|
-
required: ["script root object"],
|
|
311
|
-
received: [Array.isArray(data) ? "array" : typeof data],
|
|
312
|
-
nextSteps: ["Use a valid script document object."],
|
|
313
|
-
errorCode: "SCRIPT_ROOT_INVALID",
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
return new ScriptEditSession({ workspace, scriptPath: p, script: data });
|
|
317
|
-
}
|
|
318
272
|
function validateSession(session, opts = {}) {
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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)
|
|
328
286
|
writeJson(path.join(directDir(session.workspace), "validation.json"), validation);
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
finally {
|
|
332
|
-
try {
|
|
333
|
-
fs.unlinkSync(tmpPath);
|
|
334
|
-
}
|
|
335
|
-
catch {
|
|
336
|
-
// ignore
|
|
337
|
-
}
|
|
338
|
-
}
|
|
287
|
+
return validation;
|
|
339
288
|
}
|
|
340
289
|
function validationIssuePath(issue) {
|
|
341
290
|
if (issue["path"])
|
|
@@ -425,12 +374,8 @@ function requestIdForScriptWrite(opts, op, payload) {
|
|
|
425
374
|
return `scriptctl:${op}:${sha256Text(canonical)}`;
|
|
426
375
|
}
|
|
427
376
|
async function saveScriptSession(session, opts, op) {
|
|
428
|
-
if (!session.remote) {
|
|
429
|
-
writeJson(session.scriptPath, session.script);
|
|
430
|
-
return [null, false];
|
|
431
|
-
}
|
|
432
377
|
if (session.client === null)
|
|
433
|
-
throw new Error("
|
|
378
|
+
throw new Error("script session missing client");
|
|
434
379
|
const baseRevision = Number(session.revision ?? 0);
|
|
435
380
|
const requestId = requestIdForScriptWrite(opts, op, { script: session.script });
|
|
436
381
|
let res;
|
|
@@ -488,67 +433,11 @@ function patchOperationsFromPayload(payload) {
|
|
|
488
433
|
return operations;
|
|
489
434
|
}
|
|
490
435
|
// ---------------------------------------------------------------------------
|
|
491
|
-
// command_patch (legacy: applies to script.initial.json directly with source)
|
|
492
|
-
// ---------------------------------------------------------------------------
|
|
493
|
-
export function commandPatch(opts) {
|
|
494
|
-
const workspace = strOf(opts["workspace_path"] || "workspace");
|
|
495
|
-
const patchPath = strOf(opts["patch"]);
|
|
496
|
-
const scriptPath = path.join(directDir(workspace), "script.initial.json");
|
|
497
|
-
const sourcePath = path.join(workspace, "source.txt");
|
|
498
|
-
if (!exists(patchPath)) {
|
|
499
|
-
throw new CliError("PATCH BLOCKED: Patch file not found", "Patch file not found.", {
|
|
500
|
-
exitCode: EXIT_INPUT,
|
|
501
|
-
required: ["--patch: existing JSON file"],
|
|
502
|
-
received: [patchPath],
|
|
503
|
-
nextSteps: ["Write a patch JSON file and rerun patch."],
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
if (!exists(scriptPath) || !exists(sourcePath)) {
|
|
507
|
-
throw new CliError("PATCH BLOCKED: Required artifact missing", "Required artifact missing.", {
|
|
508
|
-
exitCode: EXIT_INPUT,
|
|
509
|
-
required: ["source.txt and script.initial.json"],
|
|
510
|
-
received: [scriptPath, sourcePath],
|
|
511
|
-
nextSteps: ["Run scriptctl direct init first."],
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
let payload;
|
|
515
|
-
try {
|
|
516
|
-
payload = readJson(patchPath);
|
|
517
|
-
}
|
|
518
|
-
catch (exc) {
|
|
519
|
-
throw new CliError("PATCH BLOCKED: Patch JSON invalid", "Patch JSON invalid.", {
|
|
520
|
-
exitCode: EXIT_USAGE,
|
|
521
|
-
required: ["valid JSON patch file"],
|
|
522
|
-
received: [`${patchPath}: ${exc.message}`],
|
|
523
|
-
nextSteps: ["Fix patch JSON and rerun patch."],
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
const operations = patchOperationsFromPayload(payload);
|
|
527
|
-
const script = readJson(scriptPath);
|
|
528
|
-
const applied = applyPatchOperations(script, readText(sourcePath), operations);
|
|
529
|
-
writeJson(scriptPath, script);
|
|
530
|
-
markMetadataConfidenceReviewed(workspace, operations);
|
|
531
|
-
const validation = validateScript(workspace, scriptPath);
|
|
532
|
-
markPatched(workspace, applied.length);
|
|
533
|
-
const passed = Boolean(validation["passed"]);
|
|
534
|
-
const report = {
|
|
535
|
-
title: passed ? "PATCH APPLIED: Validation passed" : "PATCH APPLIED: Repair issues remain",
|
|
536
|
-
result: [
|
|
537
|
-
`operations: ${applied.length}`,
|
|
538
|
-
`validation: ${passed ? "passed" : "needs repair"}`,
|
|
539
|
-
],
|
|
540
|
-
artifacts: [scriptPath, path.join(directDir(workspace), "validation.json")],
|
|
541
|
-
issues: summarizeIssues(asList(validation["issues"])),
|
|
542
|
-
next: [passed ? "Export the final script." : "Inspect remaining issues and apply another patch."],
|
|
543
|
-
};
|
|
544
|
-
return [report, passed ? EXIT_OK : EXIT_NEEDS_AGENT];
|
|
545
|
-
}
|
|
546
|
-
// ---------------------------------------------------------------------------
|
|
547
436
|
// commandScriptValidate / commandScriptInspect
|
|
548
437
|
// ---------------------------------------------------------------------------
|
|
549
438
|
export async function commandScriptValidate(opts) {
|
|
550
439
|
const workspace = strOf(opts["workspace_path"] || "workspace");
|
|
551
|
-
const session = await
|
|
440
|
+
const session = await loadScript(opts);
|
|
552
441
|
const validation = validateSession(session);
|
|
553
442
|
await syncValidationResult(session, validation);
|
|
554
443
|
const stats = validation["stats"] ?? {};
|
|
@@ -678,13 +567,13 @@ export async function commandScriptPatch(opts) {
|
|
|
678
567
|
}
|
|
679
568
|
const operations = patchOperationsFromPayload(payload);
|
|
680
569
|
const dryRun = Boolean(opts["dry_run"]);
|
|
681
|
-
const session = await
|
|
570
|
+
const session = await loadScript(opts);
|
|
682
571
|
const script = session.script;
|
|
683
572
|
const applied = applyPatchOperations(script, scriptSourceText(workspace), operations);
|
|
684
573
|
if (dryRun) {
|
|
685
574
|
// Validate the in-memory mutated script WITHOUT calling saveScriptSession.
|
|
686
575
|
// Run validate but skip syncValidationResult (which writes to remote) too.
|
|
687
|
-
const validation = validateSession(session);
|
|
576
|
+
const validation = validateSession(session, { persist: false });
|
|
688
577
|
const passed = Boolean(validation["passed"]);
|
|
689
578
|
const report = {
|
|
690
579
|
title: passed ? "SCRIPT PATCH DRY-RUN PASSED" : "SCRIPT PATCH DRY-RUN: Validation needs repair",
|
|
@@ -706,12 +595,6 @@ export async function commandScriptPatch(opts) {
|
|
|
706
595
|
const [newRevision, idempotent] = await saveScriptSession(session, opts, applied.length === 1 ? applied[0] : "script.patch");
|
|
707
596
|
const validation = validateSession(session);
|
|
708
597
|
await syncValidationResult(session, validation, newRevision);
|
|
709
|
-
// Direct-stage local patches: keep run_state in sync so reviewBlockers can
|
|
710
|
-
// unblock direct export. Detect via script path under directDir.
|
|
711
|
-
if (!session.remote && session.scriptPath && session.scriptPath.startsWith(directDir(workspace))) {
|
|
712
|
-
markMetadataConfidenceReviewed(workspace, operations);
|
|
713
|
-
markPatched(workspace, applied.length);
|
|
714
|
-
}
|
|
715
598
|
const passed = Boolean(validation["passed"]);
|
|
716
599
|
const resultLines = [
|
|
717
600
|
`operations: ${applied.length}`,
|
|
@@ -739,7 +622,7 @@ export async function commandScriptPatch(opts) {
|
|
|
739
622
|
// ---------------------------------------------------------------------------
|
|
740
623
|
async function applySingleScriptOp(opts, op) {
|
|
741
624
|
const workspace = strOf(opts["workspace_path"] || "workspace");
|
|
742
|
-
const session = await
|
|
625
|
+
const session = await loadScript(opts);
|
|
743
626
|
const script = session.script;
|
|
744
627
|
const applied = applyPatchOperations(script, scriptSourceText(workspace), [op]);
|
|
745
628
|
const opName = applied[0] ?? strOf(op["op"]);
|
|
@@ -781,7 +664,7 @@ function summarizeScriptOp(_script, op) {
|
|
|
781
664
|
return `已执行 ${kind}`;
|
|
782
665
|
}
|
|
783
666
|
async function commandStateRefs(opts, plan = false) {
|
|
784
|
-
const session = await
|
|
667
|
+
const session = await loadScript(opts);
|
|
785
668
|
const script = session.script;
|
|
786
669
|
const args = asList(opts["_args"]);
|
|
787
670
|
const [targetKind, targetId, stateId] = parseStateTarget(args[0] ?? "");
|
|
@@ -820,72 +703,48 @@ async function commandStateRefs(opts, plan = false) {
|
|
|
820
703
|
return [report, EXIT_OK];
|
|
821
704
|
}
|
|
822
705
|
// ---------------------------------------------------------------------------
|
|
823
|
-
//
|
|
706
|
+
// command_push (import push)
|
|
824
707
|
// ---------------------------------------------------------------------------
|
|
825
|
-
export async function
|
|
708
|
+
export async function commandPush(opts) {
|
|
826
709
|
const workspace = strOf(opts["workspace_path"] || "workspace");
|
|
827
|
-
const force = Boolean(opts["force"]);
|
|
828
710
|
const scriptPath = path.join(directDir(workspace), "script.initial.json");
|
|
829
711
|
if (!exists(scriptPath)) {
|
|
830
|
-
throw new CliError("
|
|
712
|
+
throw new CliError("PUSH BLOCKED: script.initial.json not found", "script.initial.json not found.", {
|
|
831
713
|
exitCode: EXIT_INPUT,
|
|
832
714
|
required: ["workspace/draft/scriptctl/direct/script.initial.json"],
|
|
833
715
|
received: [scriptPath],
|
|
834
|
-
nextSteps: ["Run scriptctl
|
|
716
|
+
nextSteps: ["Run scriptctl import init first."],
|
|
835
717
|
});
|
|
836
718
|
}
|
|
837
719
|
const state = readRunState(workspace);
|
|
838
720
|
const provider = strOf(state["provider"]);
|
|
839
|
-
|
|
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) {
|
|
840
725
|
const report = {
|
|
841
|
-
title: "
|
|
842
|
-
result: ["script.initial.json was produced by --provider mock and was not
|
|
726
|
+
title: "PUSH BLOCKED: Mock provider result",
|
|
727
|
+
result: ["script.initial.json was produced by --provider mock and was not pushed."],
|
|
843
728
|
artifacts: [path.join(directDir(workspace), "run_state.json")],
|
|
844
729
|
next: ["Rerun init with --provider anthropic for deliverable conversion."],
|
|
845
730
|
};
|
|
846
731
|
return [report, EXIT_NEEDS_AGENT];
|
|
847
732
|
}
|
|
848
|
-
const missingReview = reviewBlockers(state);
|
|
849
|
-
if (missingReview.length > 0) {
|
|
850
|
-
const report = {
|
|
851
|
-
title: "EXPORT BLOCKED: Agent review incomplete",
|
|
852
|
-
result: ["script.initial.json was not exported.", `missing review: ${missingReview.join(", ")}`],
|
|
853
|
-
artifacts: [path.join(directDir(workspace), "run_state.json")],
|
|
854
|
-
next: ["Run inspect for each missing target, or apply a structured patch, then validate/export."],
|
|
855
|
-
};
|
|
856
|
-
return [report, EXIT_NEEDS_AGENT];
|
|
857
|
-
}
|
|
858
|
-
const validation = validateScript(workspace, scriptPath);
|
|
859
|
-
const blockingOrError = Boolean(validation["has_blocking"]) ||
|
|
860
|
-
asList(validation["issues"]).some((it) => isDict(it) && (it["severity"] === "blocking" || it["severity"] === "error"));
|
|
861
|
-
if (!validation["passed"] && (!force || blockingOrError)) {
|
|
862
|
-
const title = force
|
|
863
|
-
? "EXPORT BLOCKED: Validation errors require repair"
|
|
864
|
-
: "EXPORT BLOCKED: Validation needs agent repair";
|
|
865
|
-
const report = {
|
|
866
|
-
title,
|
|
867
|
-
result: ["script.initial.json was not exported."],
|
|
868
|
-
artifacts: [path.join(directDir(workspace), "validation.json")],
|
|
869
|
-
issues: summarizeIssues(asList(validation["issues"])),
|
|
870
|
-
next: ["Inspect issues and apply structured patches, then validate/export."],
|
|
871
|
-
};
|
|
872
|
-
return [report, EXIT_NEEDS_AGENT];
|
|
873
|
-
}
|
|
874
733
|
let script;
|
|
875
734
|
try {
|
|
876
735
|
script = readJson(scriptPath);
|
|
877
736
|
}
|
|
878
737
|
catch (exc) {
|
|
879
|
-
throw new CliError("
|
|
738
|
+
throw new CliError("PUSH BLOCKED: script.initial.json invalid", "script.initial.json invalid.", {
|
|
880
739
|
exitCode: EXIT_INPUT,
|
|
881
740
|
required: ["valid script.initial.json"],
|
|
882
741
|
received: [`${scriptPath}: ${exc.message}`],
|
|
883
|
-
nextSteps: ["Fix script.initial.json or rerun
|
|
742
|
+
nextSteps: ["Fix script.initial.json or rerun import init."],
|
|
884
743
|
errorCode: "SCRIPT_JSON_INVALID",
|
|
885
744
|
});
|
|
886
745
|
}
|
|
887
746
|
if (!isDict(script)) {
|
|
888
|
-
throw new CliError("
|
|
747
|
+
throw new CliError("PUSH BLOCKED: script root invalid", "script root invalid.", {
|
|
889
748
|
exitCode: EXIT_USAGE,
|
|
890
749
|
required: ["script root object"],
|
|
891
750
|
received: [Array.isArray(script) ? "array" : typeof script],
|
|
@@ -893,12 +752,14 @@ export async function commandExport(opts) {
|
|
|
893
752
|
errorCode: "SCRIPT_ROOT_INVALID",
|
|
894
753
|
});
|
|
895
754
|
}
|
|
755
|
+
// Validation is advisory, not a gate: push always publishes so the agent can
|
|
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 });
|
|
896
760
|
const client = scriptOutputClient(opts);
|
|
897
761
|
const baseRevision = await currentRevisionOrZero(client);
|
|
898
|
-
|
|
899
|
-
const sortedScript = sortDeep(script);
|
|
900
|
-
const scriptHash = sha256Text(JSON.stringify(sortedScript));
|
|
901
|
-
const requestId = strOf(opts["request_id"]).trim() || `scriptctl-direct-export:${scriptHash}`;
|
|
762
|
+
const requestId = requestIdForScriptWrite(opts, "import.push", { script });
|
|
902
763
|
let replaceRes;
|
|
903
764
|
try {
|
|
904
765
|
replaceRes = await client.replaceScript({
|
|
@@ -910,7 +771,7 @@ export async function commandExport(opts) {
|
|
|
910
771
|
}
|
|
911
772
|
catch (exc) {
|
|
912
773
|
if (exc instanceof ScriptOutputApiError) {
|
|
913
|
-
throw apiErrorToCli("SCRIPT API BLOCKED:
|
|
774
|
+
throw apiErrorToCli("SCRIPT API BLOCKED: Push write failed", exc);
|
|
914
775
|
}
|
|
915
776
|
throw exc;
|
|
916
777
|
}
|
|
@@ -924,17 +785,21 @@ export async function commandExport(opts) {
|
|
|
924
785
|
});
|
|
925
786
|
await syncValidationResult(remoteSession, validation, revision);
|
|
926
787
|
const outputLabel = remoteSession.artifactLabel;
|
|
927
|
-
updateRunState(workspace, { status: "
|
|
788
|
+
updateRunState(workspace, { status: "pushed", output_path: outputLabel });
|
|
789
|
+
const passed = Boolean(validation["passed"]);
|
|
928
790
|
const report = {
|
|
929
|
-
title: "
|
|
791
|
+
title: "PUSH COMPLETE: Final script stored in DB",
|
|
930
792
|
result: [
|
|
931
|
-
`validation: ${
|
|
793
|
+
`validation: ${passed ? "passed" : "advisory (review on DB)"}`,
|
|
932
794
|
`base_revision: ${baseRevision}`,
|
|
933
795
|
`revision: ${revision}`,
|
|
934
796
|
`idempotent: ${String(Boolean(replaceRes["idempotent"])).toLowerCase()}`,
|
|
935
797
|
],
|
|
798
|
+
issues: passed ? [] : summarizeIssues(asList(validation["issues"])),
|
|
936
799
|
artifacts: [outputLabel, path.join(directDir(workspace), "validation.json")],
|
|
937
|
-
next:
|
|
800
|
+
next: passed
|
|
801
|
+
? ["Self-review the DB script, then proceed to downstream asset or footage stages."]
|
|
802
|
+
: ["Self-review and repair the DB script (validate / issues --severity error), then proceed downstream."],
|
|
938
803
|
};
|
|
939
804
|
return [report, EXIT_OK];
|
|
940
805
|
}
|
|
@@ -1081,7 +946,7 @@ function buildReport(op, title, lines, artifactLabel, next) {
|
|
|
1081
946
|
}
|
|
1082
947
|
// ----- summary --------------------------------------------------------------
|
|
1083
948
|
export async function commandSummary(opts) {
|
|
1084
|
-
const session = await
|
|
949
|
+
const session = await loadScript(opts);
|
|
1085
950
|
const script = session.script;
|
|
1086
951
|
const episodes = asList(script["episodes"]);
|
|
1087
952
|
const scenes = [];
|
|
@@ -1104,7 +969,7 @@ export async function commandSummary(opts) {
|
|
|
1104
969
|
}
|
|
1105
970
|
// ----- episodes -------------------------------------------------------------
|
|
1106
971
|
export async function commandEpisodes(opts) {
|
|
1107
|
-
const session = await
|
|
972
|
+
const session = await loadScript(opts);
|
|
1108
973
|
const script = session.script;
|
|
1109
974
|
const itemId = strOf(opts["id"]).trim();
|
|
1110
975
|
const minChars = parseBound(opts["min_chars"]);
|
|
@@ -1158,7 +1023,7 @@ function formatScene(epId, scene, names) {
|
|
|
1158
1023
|
return `${epId}/${scene["scene_id"]} [${space} ${time}] location=${locations.join(",") || "-"} actors=${actors.join(",") || "-"} props=${props.join(",") || "-"} actions=${actionCount}`;
|
|
1159
1024
|
}
|
|
1160
1025
|
export async function commandScenes(opts) {
|
|
1161
|
-
const session = await
|
|
1026
|
+
const session = await loadScript(opts);
|
|
1162
1027
|
const script = session.script;
|
|
1163
1028
|
const inFilter = parseInFilter(strOf(opts["in"]));
|
|
1164
1029
|
const hasActor = strOf(opts["has_actor"]).trim();
|
|
@@ -1196,7 +1061,7 @@ export async function commandScenes(opts) {
|
|
|
1196
1061
|
}
|
|
1197
1062
|
// ----- actions --------------------------------------------------------------
|
|
1198
1063
|
export async function commandActions(opts) {
|
|
1199
|
-
const session = await
|
|
1064
|
+
const session = await loadScript(opts);
|
|
1200
1065
|
const script = session.script;
|
|
1201
1066
|
const inFilter = parseInFilter(strOf(opts["in"]));
|
|
1202
1067
|
const grep = strOf(opts["grep"]);
|
|
@@ -1381,7 +1246,7 @@ function listAssetsByKind(script, kind, opts) {
|
|
|
1381
1246
|
return lines;
|
|
1382
1247
|
}
|
|
1383
1248
|
export async function commandActors(opts) {
|
|
1384
|
-
const session = await
|
|
1249
|
+
const session = await loadScript(opts);
|
|
1385
1250
|
const lines = listAssetsByKind(session.script, "actor", {
|
|
1386
1251
|
id: strOf(opts["id"]).trim(),
|
|
1387
1252
|
name: strOf(opts["name"]).trim(),
|
|
@@ -1390,7 +1255,7 @@ export async function commandActors(opts) {
|
|
|
1390
1255
|
return [buildReport("query.actors", "ACTORS", lines, session.artifactLabel, ["Use `scriptctl rename actor:<id>` / `describe` / `merge` to edit."]), EXIT_OK];
|
|
1391
1256
|
}
|
|
1392
1257
|
export async function commandLocations(opts) {
|
|
1393
|
-
const session = await
|
|
1258
|
+
const session = await loadScript(opts);
|
|
1394
1259
|
const lines = listAssetsByKind(session.script, "location", {
|
|
1395
1260
|
id: strOf(opts["id"]).trim(),
|
|
1396
1261
|
name: strOf(opts["name"]).trim(),
|
|
@@ -1399,7 +1264,7 @@ export async function commandLocations(opts) {
|
|
|
1399
1264
|
return [buildReport("query.locations", "LOCATIONS", lines, session.artifactLabel, ["Use `scriptctl rename location:<id>` etc. to edit."]), EXIT_OK];
|
|
1400
1265
|
}
|
|
1401
1266
|
export async function commandProps(opts) {
|
|
1402
|
-
const session = await
|
|
1267
|
+
const session = await loadScript(opts);
|
|
1403
1268
|
const lines = listAssetsByKind(session.script, "prop", {
|
|
1404
1269
|
id: strOf(opts["id"]).trim(),
|
|
1405
1270
|
name: strOf(opts["name"]).trim(),
|
|
@@ -1408,7 +1273,7 @@ export async function commandProps(opts) {
|
|
|
1408
1273
|
return [buildReport("query.props", "PROPS", lines, session.artifactLabel, ["Use `scriptctl rename prop:<id>` etc. to edit."]), EXIT_OK];
|
|
1409
1274
|
}
|
|
1410
1275
|
export async function commandAssets(opts) {
|
|
1411
|
-
const session = await
|
|
1276
|
+
const session = await loadScript(opts);
|
|
1412
1277
|
const kindFilter = strOf(opts["kind"]).trim();
|
|
1413
1278
|
const nameOpt = strOf(opts["name"]).trim();
|
|
1414
1279
|
const idOpt = strOf(opts["id"]).trim();
|
|
@@ -1423,7 +1288,7 @@ export async function commandAssets(opts) {
|
|
|
1423
1288
|
}
|
|
1424
1289
|
// ----- speakers -------------------------------------------------------------
|
|
1425
1290
|
export async function commandSpeakers(opts) {
|
|
1426
|
-
const session = await
|
|
1291
|
+
const session = await loadScript(opts);
|
|
1427
1292
|
const script = session.script;
|
|
1428
1293
|
const idOpt = strOf(opts["id"]).trim();
|
|
1429
1294
|
const nameOpt = strOf(opts["name"]).trim();
|
|
@@ -1446,7 +1311,7 @@ export async function commandSpeakers(opts) {
|
|
|
1446
1311
|
}
|
|
1447
1312
|
// ----- issues ---------------------------------------------------------------
|
|
1448
1313
|
export async function commandIssues(opts) {
|
|
1449
|
-
const session = await
|
|
1314
|
+
const session = await loadScript(opts);
|
|
1450
1315
|
const severityFilter = strOf(opts["severity"]).trim();
|
|
1451
1316
|
const codeFilter = strOf(opts["code"]).trim();
|
|
1452
1317
|
const validation = validateSession(session);
|
|
@@ -1468,7 +1333,7 @@ export async function commandIssues(opts) {
|
|
|
1468
1333
|
}
|
|
1469
1334
|
// ----- refs (unified reverse lookup) ----------------------------------------
|
|
1470
1335
|
export async function commandRefs(opts) {
|
|
1471
|
-
const session = await
|
|
1336
|
+
const session = await loadScript(opts);
|
|
1472
1337
|
const script = session.script;
|
|
1473
1338
|
const args = asList(opts["_args"]);
|
|
1474
1339
|
const target = strOf(args[0]).trim();
|