spine-rigc 1.9.3 → 2.0.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/.claude-plugin/plugin.json +1 -1
- package/NOTICE.md +37 -21
- package/README.md +38 -20
- package/bin/rigc.cjs +45 -2
- package/cli_core.ts +35 -0
- package/docs/AUTHORING.md +27 -15
- package/package.json +4 -5
- package/skills/rigc/SKILL.md +8 -5
- package/src/assertions/bodies/a01.ts +23 -0
- package/src/assertions/bodies/a02.ts +21 -0
- package/src/assertions/bodies/a05.ts +56 -0
- package/src/assertions/bodies/a06.ts +53 -3
- package/src/assertions/bodies/a07.ts +68 -0
- package/src/assertions/bodies/a16.ts +24 -0
- package/src/assertions/bodies/a18.ts +62 -0
- package/src/assertions/bodies/a19.ts +44 -2
- package/src/assertions/bodies/a31.ts +61 -0
- package/src/assertions/bodies/a35.ts +160 -0
- package/src/assertions/emitted/index.ts +119 -0
- package/src/assertions/report.ts +64 -0
- package/src/cli/core_commands.ts +82 -7
- package/src/cli/shared.ts +567 -59
- package/src/cli/spine_commands.ts +36 -366
- package/src/validate.ts +16 -86
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The round trip's rules, restated over the text rigc emitted (issue #1060,
|
|
3
|
+
* step 4e of #380) — the half of the gate the entry that links none of
|
|
4
|
+
* spine-core runs beside the model side (`../model/index.ts`).
|
|
5
|
+
*
|
|
6
|
+
* ⭐ **Every one of the 49 is accounted for, by name.** The model side runs
|
|
7
|
+
* the moved assertions (`MOVED_ASSERTIONS`) over the document. What is left —
|
|
8
|
+
* the complement, read off `ASSERTION_KIND` rather than listed — is the
|
|
9
|
+
* round trip's own: A00, A01, A02, A05, A07, A16, A18, A31, A35 at the time of
|
|
10
|
+
* writing. Each of those but one reads the raw text, not what spine-core
|
|
11
|
+
* loaded: the atlas's line layout (A07), the skeleton JSON's draw-order
|
|
12
|
+
* offsets, deform runs, version label, legacy arrays, legacy bone key and
|
|
13
|
+
* curve arrays (A31, A35, A16, A01, A02, A05), and A18's second compile. So
|
|
14
|
+
* each is restated here over the same text, by a body under `../bodies/` that
|
|
15
|
+
* is the round trip's clause word for word, and prints the round trip's line
|
|
16
|
+
* (`RC28` holds the two on every recipe and on each assertion's mutants).
|
|
17
|
+
* The one that cannot be is **A00**: it is spine-core's parser loading the
|
|
18
|
+
* pair, and there is no second reader of the format to stand in for it. It
|
|
19
|
+
* SKIPs, naming spine-core and where the round trip does run — never a PASS
|
|
20
|
+
* on nothing. A code the complement grows by that this file does not know is
|
|
21
|
+
* an internal error, so a new round-trip rule cannot go unrun here in silence.
|
|
22
|
+
*
|
|
23
|
+
* Links nothing from the runtime.
|
|
24
|
+
*/
|
|
25
|
+
import { ASSERTION_KIND, type AssertionProfile } from '../kinds.ts';
|
|
26
|
+
import { verdictHarness, type VerdictLists } from '../harness.ts';
|
|
27
|
+
import { MOVED_ASSERTIONS } from '../model/index.ts';
|
|
28
|
+
import type { Verdicts } from '../harness.ts';
|
|
29
|
+
import type { Json } from '../values.ts';
|
|
30
|
+
import { a01NoLegacyToplevelConstraintArrays } from '../bodies/a01.ts';
|
|
31
|
+
import { a02NoBoneTransformKey } from '../bodies/a02.ts';
|
|
32
|
+
import { a05CurveArrayLength } from '../bodies/a05.ts';
|
|
33
|
+
import { a07AtlasTextShape } from '../bodies/a07.ts';
|
|
34
|
+
import { a16SkeletonVersion43 } from '../bodies/a16.ts';
|
|
35
|
+
import { a18DeterministicEmit } from '../bodies/a18.ts';
|
|
36
|
+
import { a31DrawOrderOffsetsResolve } from '../bodies/a31.ts';
|
|
37
|
+
import { a35DeformKeysFitTheAttachment } from '../bodies/a35.ts';
|
|
38
|
+
|
|
39
|
+
/** What the restated rules read: the emitted texts, the document written beside them, and a second compile's three. */
|
|
40
|
+
export interface EmittedTextInput {
|
|
41
|
+
skeletonText: string;
|
|
42
|
+
atlasText: string;
|
|
43
|
+
/** The document `build` writes — A18 compares it with the second compile's. */
|
|
44
|
+
modelText: string;
|
|
45
|
+
/** A second, independent compile's texts; absent, A18 SKIPs as the round trip's does on a re-gate. */
|
|
46
|
+
reEmit?: { skeletonText: string; atlasText: string; modelText: string };
|
|
47
|
+
profile: AssertionProfile;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The emitted texts as the bodies read them: the skeleton JSON parsed, or `null` where it does not parse. */
|
|
51
|
+
interface EmittedTexts {
|
|
52
|
+
raw: Json | null;
|
|
53
|
+
input: EmittedTextInput;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A00's SKIP here: what did not run, and where it does. */
|
|
57
|
+
export const SKIP_NO_ROUND_TRIP =
|
|
58
|
+
"spine-core's parser is the subject of this rule and this entry links none of it — `bun cli.ts build` and `rigc validate` run the round trip, and the selftest and CI run it on every corpus build";
|
|
59
|
+
|
|
60
|
+
/** The code this file SKIPs rather than restates. */
|
|
61
|
+
const A00 = 'A00_ROUNDTRIP_PARSE';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The restated rules, in `validate()`'s report order — A00's place among them
|
|
65
|
+
* (after A35, before A16) is the round trip's, so the report reads in the order
|
|
66
|
+
* the round trip's does.
|
|
67
|
+
*/
|
|
68
|
+
export const EMITTED_TEXT_RULES: ReadonlyArray<{ code: string; run: (v: Verdicts, texts: EmittedTexts) => void }> = [
|
|
69
|
+
{ code: 'A07_ATLAS_TEXT_SHAPE', run: (v, t) => a07AtlasTextShape(v, t.input.atlasText) },
|
|
70
|
+
{ code: 'A31_DRAW_ORDER_OFFSETS_RESOLVE', run: (v, t) => a31DrawOrderOffsetsResolve(v, t.raw) },
|
|
71
|
+
{ code: 'A35_DEFORM_KEYS_FIT_THE_ATTACHMENT', run: (v, t) => a35DeformKeysFitTheAttachment(v, t.raw) },
|
|
72
|
+
{ code: A00, run: (v) => v.skip(A00, SKIP_NO_ROUND_TRIP) },
|
|
73
|
+
{ code: 'A16_SKELETON_VERSION_4_3', run: (v, t) => a16SkeletonVersion43(v, t.raw) },
|
|
74
|
+
{ code: 'A01_NO_LEGACY_TOPLEVEL_CONSTRAINT_ARRAYS', run: (v, t) => a01NoLegacyToplevelConstraintArrays(v, t.raw) },
|
|
75
|
+
{ code: 'A02_NO_BONE_TRANSFORM_KEY', run: (v, t) => a02NoBoneTransformKey(v, t.raw) },
|
|
76
|
+
{ code: 'A05_CURVE_ARRAY_LENGTH', run: (v, t) => a05CurveArrayLength(v, t.raw) },
|
|
77
|
+
{
|
|
78
|
+
code: 'A18_DETERMINISTIC_EMIT',
|
|
79
|
+
run: (v, { input }) => {
|
|
80
|
+
const again = input.reEmit;
|
|
81
|
+
const encoding =
|
|
82
|
+
again === undefined
|
|
83
|
+
? []
|
|
84
|
+
: [
|
|
85
|
+
...(again.skeletonText !== input.skeletonText ? ['recompiling produced a different skeleton.json'] : []),
|
|
86
|
+
...(again.atlasText !== input.atlasText ? ['recompiling produced a different skeleton.atlas'] : []),
|
|
87
|
+
];
|
|
88
|
+
a18DeterministicEmit(v, { again: again !== undefined, encoding, first: input.modelText, second: again?.modelText ?? '' });
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
/** Every code the model side does not run, read off the registry — what this file must account for. */
|
|
94
|
+
export function roundTripOnlyCodes(): string[] {
|
|
95
|
+
const moved = new Set(MOVED_ASSERTIONS.map((m) => m.code));
|
|
96
|
+
return Object.keys(ASSERTION_KIND).filter((code) => !moved.has(code));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The restated rules over one build's emitted texts, in `validate()`'s report shape. */
|
|
100
|
+
export function validateEmittedText(input: EmittedTextInput): VerdictLists & { profile: AssertionProfile } {
|
|
101
|
+
const known = new Set(EMITTED_TEXT_RULES.map((r) => r.code));
|
|
102
|
+
const unrun = roundTripOnlyCodes().filter((code) => !known.has(code));
|
|
103
|
+
if (unrun.length > 0) {
|
|
104
|
+
throw new Error(`internal: ${unrun.join(', ')} runs on neither side here — the model side does not run it and src/assertions/emitted/index.ts neither restates nor SKIPs it`);
|
|
105
|
+
}
|
|
106
|
+
const h = verdictHarness(input.profile, ASSERTION_KIND, 'the emitted text');
|
|
107
|
+
let raw: Json | null = null;
|
|
108
|
+
try {
|
|
109
|
+
raw = JSON.parse(input.skeletonText) as Json;
|
|
110
|
+
} catch (err) {
|
|
111
|
+
h.fail(A00, `skeleton JSON is not parseable: ${(err as Error).message}`);
|
|
112
|
+
}
|
|
113
|
+
for (const rule of EMITTED_TEXT_RULES) {
|
|
114
|
+
if (rule.code === A00 && raw === null) continue;
|
|
115
|
+
h.check(rule.code, () => rule.run(h.verdicts, { raw, input }));
|
|
116
|
+
}
|
|
117
|
+
const { failures, passed, skipped, profileSkipped, stats } = h;
|
|
118
|
+
return { failures, passed, skipped, profileSkipped, stats, profile: input.profile };
|
|
119
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The profiles a gate judges under, the one the CLI uses when `--profile` is
|
|
3
|
+
* absent, and the printer of a gate's report — moved here unchanged from
|
|
4
|
+
* `src/validate.ts` (issue #1060), which re-exports all three: the entry that
|
|
5
|
+
* links none of spine-core gates a build on the model side
|
|
6
|
+
* (`./model/index.ts`) and prints its report with the same printer, and
|
|
7
|
+
* `src/validate.ts` links the runtime. Nothing here links it.
|
|
8
|
+
*/
|
|
9
|
+
import type { AssertionProfile } from './kinds.ts';
|
|
10
|
+
import type { VerdictLists } from './harness.ts';
|
|
11
|
+
|
|
12
|
+
export const VALIDATE_PROFILES: readonly AssertionProfile[] = ['spine', 'spine-html'];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* What the CLI uses when `--profile` is absent — and ONLY the CLI. This is not
|
|
16
|
+
* `validate()`'s default; that function has none (`ValidateProfile` in
|
|
17
|
+
* `src/validate.ts` says why).
|
|
18
|
+
*
|
|
19
|
+
* `spine` since issue #221. The published package's pitch is "the output imports
|
|
20
|
+
* into the Spine editor", which is exactly the question `spine` asks, and a
|
|
21
|
+
* stranger's first build was being judged instead against one renderer's policy
|
|
22
|
+
* and one project's canvas budget — 14 rules they have no stake in, with the
|
|
23
|
+
* escape hatch documented only in prose. Defaults beat prose. `spine-html` is
|
|
24
|
+
* still one flag away, and every report names the profile that judged it.
|
|
25
|
+
*/
|
|
26
|
+
export const CLI_DEFAULT_PROFILE: AssertionProfile = 'spine';
|
|
27
|
+
|
|
28
|
+
export function reportLines(report: VerdictLists & { profile: AssertionProfile }): string[] {
|
|
29
|
+
const lines: string[] = [];
|
|
30
|
+
// The profile goes FIRST and names what it left out. A report that says
|
|
31
|
+
// "green" without saying which rulebook produced it is the one thing this
|
|
32
|
+
// switch could make worse than no switch: `--profile spine` green means
|
|
33
|
+
// "valid Spine", never "passes the renderer policy".
|
|
34
|
+
const renderer = report.profileSkipped.filter((p) => p.kind === 'renderer').length;
|
|
35
|
+
const archetype = report.profileSkipped.filter((p) => p.kind === 'archetype').length;
|
|
36
|
+
lines.push(
|
|
37
|
+
report.profileSkipped.length === 0
|
|
38
|
+
? ` .. profile ${report.profile} — every assertion applies`
|
|
39
|
+
: ` .. profile ${report.profile} — ${renderer} renderer-policy and ${archetype} archetype assertion(s) do not apply`,
|
|
40
|
+
);
|
|
41
|
+
for (const name of report.passed) lines.push(` PASS ${name}`);
|
|
42
|
+
for (const s of report.skipped) lines.push(` SKIP ${s.assertion}: ${s.reason}`);
|
|
43
|
+
for (const p of report.profileSkipped) lines.push(` PROF ${p.assertion}: ${p.kind} rule, not in profile "${report.profile}"`);
|
|
44
|
+
for (const f of report.failures) lines.push(` FAIL ${f.assertion}: ${f.detail}`);
|
|
45
|
+
// How many of them MEASURED anything, which is the figure the rows above do
|
|
46
|
+
// not hand a reader (issue #568). Counting `PASS` lines answers a different
|
|
47
|
+
// question — before the sweep that closed #568 a run could print seven of
|
|
48
|
+
// them over a candidate on which five rules had not executed at all.
|
|
49
|
+
//
|
|
50
|
+
// ⚠️ Every figure here is a count of ASSERTIONS and not of rows, which is why
|
|
51
|
+
// the failed side is a Set: `fail()` is called once per finding, so one
|
|
52
|
+
// assertion can print six `FAIL` lines, and a line-count would report 47 of
|
|
53
|
+
// 42. The four buckets partition `ASSERTION_NAMES`, so the total is derived
|
|
54
|
+
// by adding them rather than stated — a `42` written here would be the one
|
|
55
|
+
// number in the report that no run could contradict.
|
|
56
|
+
const failed = new Set(report.failures.map((f) => f.assertion));
|
|
57
|
+
const measured = report.passed.length + failed.size;
|
|
58
|
+
const total = measured + report.skipped.length + report.profileSkipped.length;
|
|
59
|
+
lines.push(
|
|
60
|
+
` .. ${total} assertions: ${measured} measured (${report.passed.length} passed, ${failed.size} failed), ` +
|
|
61
|
+
`${report.skipped.length} skipped, ${report.profileSkipped.length} not in profile "${report.profile}"`,
|
|
62
|
+
);
|
|
63
|
+
return lines;
|
|
64
|
+
}
|
package/src/cli/core_commands.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* `ingest`, `diff`, `check`, `render`, `pose`, `chainfit` and `skills` —
|
|
4
4
|
* moved here unchanged from `cli.ts` (issue #1052, step 4e of #380), with the
|
|
5
5
|
* helpers only they call. Both entries register them (`CORE_COMMAND_RUNS`):
|
|
6
|
-
* `cli.ts` beside the runtime's, `cli_core.ts` alone.
|
|
6
|
+
* `cli.ts` beside the runtime's, `cli_core.ts` alone. And the body
|
|
7
|
+
* `cli_core.ts` runs as `build` (`CORE_ENTRY_RUNS`, issue #1060): `runBuild`
|
|
8
|
+
* with a gate that links none of the runtime.
|
|
7
9
|
*
|
|
8
10
|
* Which commands these are is not a list kept here: it is `COMMANDS`'s
|
|
9
11
|
* `runtime` field (`./shared.ts`), which `RC26` in `selftest.ts` holds to the
|
|
@@ -12,6 +14,9 @@
|
|
|
12
14
|
* absent, which `RC24` runs.
|
|
13
15
|
*/
|
|
14
16
|
import { parseAtlasText } from '../atlas.ts';
|
|
17
|
+
import { validateEmittedText } from '../assertions/emitted/index.ts';
|
|
18
|
+
import { validateModel } from '../assertions/model/index.ts';
|
|
19
|
+
import { reportLines } from '../assertions/report.ts';
|
|
15
20
|
import { chainFitLines, type ChainFitOptions, estimateChainFit } from '../chainfit.ts';
|
|
16
21
|
import { checkLines, CheckPlates } from '../check.ts';
|
|
17
22
|
import { writeCheckPictures } from '../checkpics.ts';
|
|
@@ -29,7 +34,7 @@ import { estimatePose, poseLines, type PoseOptions } from '../pose.ts';
|
|
|
29
34
|
import { BACKGROUND, contactSheet, type Frame, FRAMES_SIDECAR, FRAMES_SPEC, type FrameSet, type FramesSidecar, framingViewport, GEOMETRY_FILE, geometryFileOf, geometryText, loadCandidate, POSER_NAMES, type PoserName, PROTOCOL_FPS, refuseUnchosen, renderFrame, sampleAll, sampleAnimation, SETUP_POSE_DIR, SHEET_FILE, SHEET_TILE, sidecarViewport, type SkeletonFacts, type SlotSubset, SlotSubsetError, throughPoser } from '../render_shared.ts';
|
|
30
35
|
import { type CompileResult } from '../types.ts';
|
|
31
36
|
import { attachmentRegionJoins } from '../region_joins.ts';
|
|
32
|
-
import { ATLAS_ABSENT, COMMANDS, type CommandRun, PACKAGE_ROOT, DEFAULT_CHAINFIT_OUT, DEFAULT_POSE_OUT, DEFAULT_SKILLS_DIR, ExplainError, meshBudget, meshDepthNote, meshFit, meshInfluenceNote, PAGE_GRID_UNLOCATED, readAnimationFlag, readJsonFile, readPoserFlag, readSkeletonText, readVersion, resolveCut, resolveDrawable, runCheck, SkillsInstallError, STAGELESS_FRAMING, UsageError, writeJson } from './shared.ts';
|
|
37
|
+
import { ATLAS_ABSENT, COMMANDS, type CommandRun, resolveBuild, type BuildGate, runBuild, PACKAGE_ROOT, DEFAULT_CHAINFIT_OUT, DEFAULT_POSE_OUT, DEFAULT_SKILLS_DIR, ExplainError, meshBudget, meshDepthNote, meshFit, meshInfluenceNote, PAGE_GRID_UNLOCATED, readAnimationFlag, readJsonFile, readPoserFlag, readSkeletonText, readVersion, resolveCut, resolveDrawable, runCheck, SkillsInstallError, STAGELESS_FRAMING, UsageError, writeJson } from './shared.ts';
|
|
33
38
|
import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
34
39
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
35
40
|
|
|
@@ -321,11 +326,17 @@ function readAnimationPairs(values: string[], candidate: unknown, reference: unk
|
|
|
321
326
|
export function cmdDiff(flags: Record<string, string>, lists: Record<string, string[]>, positional: string[]): void {
|
|
322
327
|
const [candidate, reference] = positional;
|
|
323
328
|
if (!candidate || !reference) throw new UsageError('diff takes two paths: <candidate.json> <reference.json>');
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
+
// A file is read as the JSON it is; a directory is a build, and its skeleton is what is compared — through the one
|
|
330
|
+
// statement of a build (issue #1046), which refuses a directory holding none. A directory used to reach the JSON
|
|
331
|
+
// reader and die on an EISDIR and a stack.
|
|
332
|
+
const skeletonAt = (path: string): string => {
|
|
333
|
+
const at = resolve(path);
|
|
334
|
+
if (existsSync(at) && statSync(at).isDirectory()) return resolveBuild(at, undefined).skeletonPath;
|
|
335
|
+
if (!existsSync(at)) throw new UsageError(`nothing at ${at}`);
|
|
336
|
+
return at;
|
|
337
|
+
};
|
|
338
|
+
const candidatePath = skeletonAt(candidate);
|
|
339
|
+
const referencePath = skeletonAt(reference);
|
|
329
340
|
const candidateJson = readJsonFile(candidatePath);
|
|
330
341
|
const referenceJson = readJsonFile(referencePath);
|
|
331
342
|
const animationPairs = readAnimationPairs(lists.as ?? [], candidateJson, referenceJson);
|
|
@@ -696,6 +707,9 @@ export function cmdChainFit(flags: Record<string, string>): void {
|
|
|
696
707
|
'skeleton is all it needs of the candidate. Drop --atlas',
|
|
697
708
|
);
|
|
698
709
|
}
|
|
710
|
+
// The candidate through the one statement of a build (issue #1046): a path, or a directory holding no skeleton.json,
|
|
711
|
+
// is refused here as by every command that takes a build, rather than by `src/chainfit.ts`'s own reader in its own words.
|
|
712
|
+
resolveBuild(flags.candidate, undefined);
|
|
699
713
|
const options: ChainFitOptions = {
|
|
700
714
|
candidatePath: flags.candidate,
|
|
701
715
|
imagesDir: flags.images,
|
|
@@ -1515,6 +1529,67 @@ export function cmdSkills(flags: Record<string, string>, positional: string[]):
|
|
|
1515
1529
|
);
|
|
1516
1530
|
}
|
|
1517
1531
|
|
|
1532
|
+
// ---------------------------------------------------------------------------
|
|
1533
|
+
// build — the second entry's gate (issue #1060, step 4e of #380)
|
|
1534
|
+
// ---------------------------------------------------------------------------
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* The gate `cli_core.ts build` runs (`BuildGate`), where `cli.ts build` runs
|
|
1538
|
+
* the round trip: the model side over the document (`validateModel`), and the
|
|
1539
|
+
* round trip's own rules restated over the text the emitter wrote
|
|
1540
|
+
* (`validateEmittedText`) — A18 among them, comparing a second, independent
|
|
1541
|
+
* compile's skeleton, atlas and document byte for byte — with A00, spine-core's
|
|
1542
|
+
* parse, a SKIP naming it. One report, printed by the printer `cli.ts build`
|
|
1543
|
+
* prints with, and a line saying which rules ran here and which did not.
|
|
1544
|
+
*
|
|
1545
|
+
* ⚠️ The document the model side reads is the one this gate judges: spelled
|
|
1546
|
+
* from the atlas text the gate is handed, so under `--copy-images` it names
|
|
1547
|
+
* the pages where the compile measured them — the files the round trip's own
|
|
1548
|
+
* A17 reads on `cli.ts` — and A18 compares the documents as written, the
|
|
1549
|
+
* copies' names in them. Under `--pack` the two are one text.
|
|
1550
|
+
*/
|
|
1551
|
+
const MODEL_AND_TEXT_GATE: BuildGate = {
|
|
1552
|
+
heading: (profile) =>
|
|
1553
|
+
` .. validate (the model side over the document + the round trip's rules restated over the emitted text, profile ${profile}; this entry links no spine-core, so the round trip does not run)`,
|
|
1554
|
+
run: ({ result, atlasText, atlasDir, modelText, reEmit, profile }) => {
|
|
1555
|
+
const model = validateModel({ modelText: modelDocument(result.model, result.skeletonText, atlasText), atlasDir, profile });
|
|
1556
|
+
const text = validateEmittedText({ skeletonText: result.skeletonText, atlasText, modelText, reEmit, profile });
|
|
1557
|
+
const report = {
|
|
1558
|
+
failures: [...model.failures, ...text.failures],
|
|
1559
|
+
passed: [...model.passed, ...text.passed],
|
|
1560
|
+
skipped: [...model.skipped, ...text.skipped],
|
|
1561
|
+
profileSkipped: [...model.profileSkipped, ...text.profileSkipped],
|
|
1562
|
+
stats: { ...model.stats, ...text.stats },
|
|
1563
|
+
profile,
|
|
1564
|
+
};
|
|
1565
|
+
for (const line of reportLines(report)) console.log(line);
|
|
1566
|
+
console.log(
|
|
1567
|
+
` .. ${Object.entries(report.stats)
|
|
1568
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
1569
|
+
.join(' ')}`,
|
|
1570
|
+
);
|
|
1571
|
+
const ran = (r: { passed: string[]; failures: Array<{ assertion: string }>; skipped: Array<{ assertion: string }>; profileSkipped: Array<{ assertion: string }> }): number =>
|
|
1572
|
+
new Set([...r.passed, ...r.failures.map((f) => f.assertion), ...r.skipped.map((x) => x.assertion), ...r.profileSkipped.map((x) => x.assertion)]).size;
|
|
1573
|
+
const notRun = text.skipped.filter((x) => x.assertion === 'A00_ROUNDTRIP_PARSE').map((x) => x.assertion);
|
|
1574
|
+
console.log(
|
|
1575
|
+
` .. here: ${ran(model)} rule(s) on the model side over the document, ${ran(text) - notRun.length} of the round trip's own restated over the emitted text; ` +
|
|
1576
|
+
`not run: ${notRun.join(', ') || 'none'} — spine-core's parse, which only \`bun cli.ts build\` and \`rigc validate\` run`,
|
|
1577
|
+
);
|
|
1578
|
+
return report.failures.length;
|
|
1579
|
+
},
|
|
1580
|
+
look: (outDir) => `rigc: look at it: rigc render --candidate ${outDir}`,
|
|
1581
|
+
};
|
|
1582
|
+
|
|
1583
|
+
/**
|
|
1584
|
+
* The bodies the second entry runs under a name whose full-entry body is the
|
|
1585
|
+
* runtime's (`runtime.core` in `COMMANDS`, issue #1060) — registered by
|
|
1586
|
+
* `cli_core.ts` alone, beside `CORE_COMMAND_RUNS`. `build` is `runBuild` with
|
|
1587
|
+
* this entry's gate: it writes what `cli.ts build` writes.
|
|
1588
|
+
*/
|
|
1589
|
+
export const CORE_ENTRY_RUNS: Readonly<Record<string, CommandRun>> = {
|
|
1590
|
+
build: ({ flags }) => runBuild(flags, MODEL_AND_TEXT_GATE),
|
|
1591
|
+
};
|
|
1592
|
+
|
|
1518
1593
|
/**
|
|
1519
1594
|
* The bodies of every command whose `runtime` is `false` (`COMMANDS`), by
|
|
1520
1595
|
* name — what both entries register. Nothing this module imports links
|