stylegallery 0.1.0 → 0.1.2
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/AGENTS.md +4 -1
- package/DOMAINS.md +2 -6
- package/GOVERNANCE.md +10 -9
- package/README.md +29 -127
- package/consumer-reference/agent-native/v2/admission-policy.json +646 -0
- package/consumer-reference/agent-native/v2/material-registry.json +1 -0
- package/consumer-reference/agent-native/v2/schema/admission-policy.schema.json +50 -0
- package/consumer-reference/agent-native/v2/schema/material-manifest.schema.json +17 -0
- package/consumer-reference/agent-native/v2/schema/material-record.schema.json +20 -0
- package/consumer-reference/contract.md +1 -1
- package/consumer-reference/index.md +5 -3
- package/layout/index.md +124 -0
- package/log.md +57 -0
- package/package.json +183 -22
- package/scripts/agent-native/v2/experimental-extension-registry.mjs +143 -0
- package/scripts/agent-native/v2/extensions/a2a-projection.mjs +264 -0
- package/scripts/agent-native/v2/extensions/agui-projection.mjs +191 -0
- package/scripts/agent-native/v2/material-admission.mjs +295 -0
- package/scripts/agent-native/v2/material-cli-adapter.mjs +134 -0
- package/scripts/agent-native/v2/material-context.mjs +251 -0
- package/scripts/agent-native/v2/material-identity.mjs +71 -0
- package/scripts/agent-native/v2/material-mcp-adapter.mjs +253 -0
- package/scripts/agent-native/v2/material-operation-registry.mjs +123 -0
- package/scripts/agent-native/v2/material-queries.mjs +398 -0
- package/scripts/agent-native/v2/material-registry.mjs +319 -0
- package/scripts/sg-material-mcp.mjs +98 -0
- package/scripts/sg-material.mjs +27 -0
- package/consumer-reference/adapters/style-dictionary.config.mjs +0 -36
- package/consumer-reference/baselines/calibration.json +0 -260
- package/consumer-reference/baselines/manifest.json +0 -21
- package/consumer-reference/fixtures/consumer-conformance/e2e-fixture.mjs +0 -282
- package/consumer-reference/fixtures/consumer-conformance/manifest.json +0 -102
- package/consumer-reference/fixtures/consumer-conformance/runtime-proof.mjs +0 -32
- package/consumer-reference/fixtures/consumer-conformance/valid-migration-result.json +0 -18
- package/consumer-reference/fixtures/consumer-conformance/valid-migration.json +0 -142
- package/consumer-reference/fixtures/invalid-stable-ended-support.json +0 -19
- package/consumer-reference/fixtures/promotion/invalid-blocking-evidence.json +0 -26
- package/consumer-reference/fixtures/promotion/invalid-count-only.json +0 -29
- package/consumer-reference/fixtures/promotion/invalid-missing-owner-migration.json +0 -25
- package/consumer-reference/fixtures/promotion/invalid-normative-bypass-no-regression.json +0 -25
- package/consumer-reference/fixtures/promotion/invalid-related-counted-independent.json +0 -45
- package/consumer-reference/fixtures/promotion/invalid-stable-ended-support.json +0 -26
- package/consumer-reference/fixtures/promotion/manifest.json +0 -15
- package/consumer-reference/fixtures/promotion/valid-deferred-example.json +0 -67
- package/consumer-reference/fixtures/promotion/valid-normative-bypass.json +0 -67
- package/consumer-reference/fixtures/token-portability/invalid-cycle.json +0 -7
- package/consumer-reference/fixtures/token-portability/invalid-dangling-alias.json +0 -6
- package/consumer-reference/fixtures/token-portability/invalid-extends.json +0 -4
- package/consumer-reference/fixtures/token-portability/invalid-json-pointer.json +0 -7
- package/consumer-reference/fixtures/token-portability/invalid-resolver.json +0 -9
- package/consumer-reference/fixtures/token-portability/invalid-type-mismatch.json +0 -10
- package/consumer-reference/fixtures/token-portability/invalid-unknown-reserved.json +0 -6
- package/consumer-reference/fixtures/token-portability/invalid-untested-type.json +0 -6
- package/consumer-reference/fixtures/token-portability/valid-border.json +0 -14
- package/consumer-reference/fixtures/token-portability/valid-reference.json +0 -32
- package/consumer-reference/fixtures/token-portability/valid-whole-alias.json +0 -7
- package/consumer-reference/fixtures/valid-deprecated.json +0 -21
- package/consumer-reference/fixtures/valid-experimental.json +0 -19
- package/consumer-reference/generated/manifest.json +0 -40
- package/consumer-reference/generated/tokens.css +0 -12
- package/consumer-reference/policies/shared-experimental.json +0 -37
- package/consumer-reference/schema/ax-evidence.schema.json +0 -30
- package/consumer-reference/schema/baseline-manifest.schema.json +0 -55
- package/consumer-reference/schema/calibration-record.schema.json +0 -190
- package/consumer-reference/schema/capture-session.schema.json +0 -161
- package/consumer-reference/schema/component-state.schema.json +0 -150
- package/consumer-reference/schema/consumer-conformance-record.schema.json +0 -365
- package/consumer-reference/schema/dom-evidence.schema.json +0 -38
- package/consumer-reference/schema/evidence-record.schema.json +0 -81
- package/consumer-reference/schema/fixture-manifest.schema.json +0 -32
- package/consumer-reference/schema/item.schema.json +0 -188
- package/consumer-reference/schema/page-evidence-manifest.schema.json +0 -216
- package/consumer-reference/schema/page-evidence-session.schema.json +0 -119
- package/consumer-reference/schema/portable-tokens.schema.json +0 -104
- package/consumer-reference/schema/promotion-rfc.schema.json +0 -219
- package/consumer-reference/schema/runtime-evidence-manifest.schema.json +0 -33
- package/consumer-reference/schema/visual-evidence.schema.json +0 -31
- package/design-engineering/consumer-migration-readiness.md +0 -124
- package/design-engineering/interface-craft.md +0 -107
- package/design-engineering/reference-profiles/external-adaptation/index.md +0 -12
- package/design-engineering/reference-profiles/governed-local/editorial/components/button.component.json +0 -125
- package/design-engineering/reference-profiles/governed-local/editorial/evidence/button.evidence.json +0 -3504
- package/design-engineering/reference-profiles/governed-local/editorial/fixtures/button.fixture.json +0 -65
- package/design-engineering/reference-profiles/governed-local/editorial/generated/evidence-coverage.md +0 -23
- package/design-engineering/reference-profiles/governed-local/editorial/generated/keyboard-matrix.md +0 -19
- package/design-engineering/reference-profiles/governed-local/editorial/generated/state-matrix.md +0 -21
- package/design-engineering/reference-profiles/governed-local/editorial/local-foundations.json +0 -12
- package/design-engineering/reference-profiles/governed-local/editorial/states/button.states.json +0 -195
- package/design-engineering/reference-profiles/governed-local/editorial/tokens.dtcg.json +0 -17
- package/design-engineering/reference-profiles/governed-local/terminal/components/button.component.json +0 -125
- package/design-engineering/reference-profiles/governed-local/terminal/evidence/button.evidence.json +0 -3504
- package/design-engineering/reference-profiles/governed-local/terminal/fixtures/button.fixture.json +0 -65
- package/design-engineering/reference-profiles/governed-local/terminal/generated/evidence-coverage.md +0 -23
- package/design-engineering/reference-profiles/governed-local/terminal/generated/keyboard-matrix.md +0 -19
- package/design-engineering/reference-profiles/governed-local/terminal/generated/state-matrix.md +0 -21
- package/design-engineering/reference-profiles/governed-local/terminal/local-foundations.json +0 -12
- package/design-engineering/reference-profiles/governed-local/terminal/profile.json +0 -43
- package/design-engineering/reference-profiles/governed-local/terminal/states/button.states.json +0 -195
- package/design-engineering/reference-profiles/governed-local/terminal/tokens.dtcg.json +0 -17
- package/game-ui/classification.md +0 -82
- package/game-ui/reference-record.md +0 -62
- package/game-ui/screen-hierarchy.md +0 -107
- package/game-ui/unity/architecture.md +0 -166
- package/game-ui/unity/cli-loop.md +0 -84
- package/game-ui/unity/data/unity-technologies-public-repositories.json +0 -26654
- package/game-ui/unity/org-wiki.md +0 -146
- package/game-ui/unity/repository-map.md +0 -102
- package/game-ui/unity/ui-systems.md +0 -85
- package/motion/practice-reference.md +0 -145
- package/motion/review-workflow.md +0 -86
- package/motion/vocabulary.md +0 -82
- package/platform-guides/apple-interaction.md +0 -89
- package/scripts/agent-native/events.mjs +0 -113
- package/scripts/agent-native/immutable-store.mjs +0 -102
- package/scripts/artifact-metadata.mjs +0 -20
- package/scripts/audit-evidence-freshness.mjs +0 -308
- package/scripts/baseline-contract.mjs +0 -189
- package/scripts/baseline-schema-parity.mjs +0 -40
- package/scripts/build-reference-artifacts.mjs +0 -127
- package/scripts/calibration-raw-contract.mjs +0 -172
- package/scripts/capture-session-contract.mjs +0 -187
- package/scripts/component-state-artifact-fixture.mjs +0 -44
- package/scripts/component-state-contract.mjs +0 -99
- package/scripts/component-state-semantics.mjs +0 -128
- package/scripts/component-state-workflow-contract.mjs +0 -67
- package/scripts/component-state-workflow-negative-cases.mjs +0 -64
- package/scripts/consumer-conformance-contract.mjs +0 -198
- package/scripts/consumer-evidence-governance-contract.mjs +0 -210
- package/scripts/consumer-reference-case-runner.mjs +0 -93
- package/scripts/consumer-reference-schema.mjs +0 -155
- package/scripts/create-component-state-session.mjs +0 -100
- package/scripts/create-page-evidence-session.mjs +0 -139
- package/scripts/domain-document-boundaries.mjs +0 -40
- package/scripts/domain-validation-checks.mjs +0 -141
- package/scripts/evidence-artifact-contract.mjs +0 -191
- package/scripts/finalize-component-state-evidence.mjs +0 -165
- package/scripts/finalize-page-evidence.mjs +0 -197
- package/scripts/generate-consumer-reference-evidence.mjs +0 -154
- package/scripts/generate-patterns.mjs +0 -218
- package/scripts/governance-matrix-contract.mjs +0 -73
- package/scripts/governance-matrix-negative-cases.mjs +0 -114
- package/scripts/governance-policy-contract.mjs +0 -49
- package/scripts/governance-test-fixture.mjs +0 -321
- package/scripts/governance-workflow-negative-cases.mjs +0 -241
- package/scripts/governed-profile-registry.mjs +0 -125
- package/scripts/json-schema-formats.mjs +0 -31
- package/scripts/markdown-structure.mjs +0 -45
- package/scripts/page-artifact-metadata.mjs +0 -124
- package/scripts/page-evidence-contract.mjs +0 -297
- package/scripts/page-evidence-fixture.mjs +0 -271
- package/scripts/pattern-data.mjs +0 -325
- package/scripts/profile-record-contract.mjs +0 -140
- package/scripts/promotion-attestation-contract.mjs +0 -91
- package/scripts/promotion-boundary-test-contract.mjs +0 -92
- package/scripts/promotion-fixture-inventory.mjs +0 -109
- package/scripts/promotion-governance-contract.mjs +0 -28
- package/scripts/reference-artifact-path-contract.mjs +0 -28
- package/scripts/reference-profile-contract.mjs +0 -114
- package/scripts/reference-token-contract.mjs +0 -156
- package/scripts/run-consumer-page-evidence-ci.mjs +0 -170
- package/scripts/summarize-sentinel-calibration.mjs +0 -98
- package/scripts/test-agent-adapter-conformance.mjs +0 -250
- package/scripts/test-agent-conformance-receipt.mjs +0 -106
- package/scripts/test-agent-events.mjs +0 -81
- package/scripts/test-agent-execution-effects.mjs +0 -114
- package/scripts/test-agent-execution-fixtures.mjs +0 -140
- package/scripts/test-agent-execution-kernel.mjs +0 -39
- package/scripts/test-agent-execution-learning.mjs +0 -130
- package/scripts/test-agent-execution-lifecycle.mjs +0 -143
- package/scripts/test-agent-execution-safety.mjs +0 -158
- package/scripts/test-agent-execution-views.mjs +0 -117
- package/scripts/test-agent-identity-integrity-cases.mjs +0 -160
- package/scripts/test-agent-knowledge-kernel.mjs +0 -183
- package/scripts/test-agent-learning.mjs +0 -246
- package/scripts/test-agent-mcp.mjs +0 -246
- package/scripts/test-agent-native-schemas.mjs +0 -215
- package/scripts/test-agent-projections.mjs +0 -249
- package/scripts/test-agent-registry-mcp-integrity.mjs +0 -215
- package/scripts/test-agent-registry-mutations.mjs +0 -110
- package/scripts/test-audit-evidence-freshness.mjs +0 -177
- package/scripts/test-component-state-sentinel.mjs +0 -35
- package/scripts/test-component-state-source-contract.mjs +0 -83
- package/scripts/test-consumer-conformance-sentinel.mjs +0 -165
- package/scripts/test-consumer-reference-sentinel.mjs +0 -31
- package/scripts/test-generate-consumer-reference-evidence.mjs +0 -124
- package/scripts/test-json-cli-output.mjs +0 -151
- package/scripts/test-json-schema-formats.mjs +0 -40
- package/scripts/test-reference-adapters.mjs +0 -199
- package/scripts/test-renderer-purity.mjs +0 -34
- package/scripts/test-sg-cli.mjs +0 -129
- package/scripts/test-summarize-sentinel-calibration.mjs +0 -226
- package/scripts/test-validate-baseline-manifest.mjs +0 -234
- package/scripts/test-validate-component-state-artifacts.mjs +0 -243
- package/scripts/test-validate-component-state.mjs +0 -106
- package/scripts/test-validate-consumer-conformance.mjs +0 -427
- package/scripts/test-validate-consumer-reference-cli.mjs +0 -69
- package/scripts/test-validate-consumer-reference.mjs +0 -263
- package/scripts/test-validate-domains.mjs +0 -357
- package/scripts/test-validate-governance.mjs +0 -292
- package/scripts/test-validate-ia.mjs +0 -262
- package/scripts/test-validate-okf.mjs +0 -73
- package/scripts/test-validate-page-evidence.mjs +0 -302
- package/scripts/test-validate-patterns.mjs +0 -157
- package/scripts/test-validate-promotion-rfc.mjs +0 -250
- package/scripts/test-validate-reference-profiles.mjs +0 -170
- package/scripts/test-validate-unity-org-wiki.mjs +0 -57
- package/scripts/test-validate-unity-source-contracts.mjs +0 -67
- package/scripts/test-validate-vocabulary.mjs +0 -174
- package/scripts/test-validate-webpage-workflow.mjs +0 -293
- package/scripts/validate-baseline-manifest.mjs +0 -44
- package/scripts/validate-catalog.mjs +0 -65
- package/scripts/validate-component-state.mjs +0 -141
- package/scripts/validate-consumer-conformance.mjs +0 -477
- package/scripts/validate-consumer-reference.mjs +0 -288
- package/scripts/validate-domains.mjs +0 -237
- package/scripts/validate-governance.mjs +0 -248
- package/scripts/validate-ia.mjs +0 -136
- package/scripts/validate-links.mjs +0 -66
- package/scripts/validate-okf.mjs +0 -104
- package/scripts/validate-page-evidence.mjs +0 -191
- package/scripts/validate-patterns.mjs +0 -246
- package/scripts/validate-promotion-rfc.mjs +0 -224
- package/scripts/validate-reference-artifacts.mjs +0 -125
- package/scripts/validate-renderer-purity.mjs +0 -56
- package/scripts/validate-unity-org-wiki.mjs +0 -146
- package/scripts/validate-unity-source-contracts.mjs +0 -107
- package/scripts/validate-vocabulary.mjs +0 -115
- package/scripts/validate-webpage-workflow.mjs +0 -254
- package/scripts/visual-expectation-contract.mjs +0 -20
- package/scripts/workflow-action-contract.mjs +0 -163
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import {
|
|
7
|
-
addSchemaFindings,
|
|
8
|
-
canonicalSourceManifest,
|
|
9
|
-
compilePageEvidenceSchemas,
|
|
10
|
-
dirtyRelevantSources,
|
|
11
|
-
finding,
|
|
12
|
-
gitIdentity,
|
|
13
|
-
listArtifactFiles,
|
|
14
|
-
metadata,
|
|
15
|
-
normalizeReference,
|
|
16
|
-
PAGE_EVIDENCE_MANIFEST,
|
|
17
|
-
PAGE_EVIDENCE_RECEIPT,
|
|
18
|
-
readJsonFile,
|
|
19
|
-
readContainedBytes,
|
|
20
|
-
readReceipt,
|
|
21
|
-
resolveContained,
|
|
22
|
-
sameJson,
|
|
23
|
-
sameStringSet,
|
|
24
|
-
withinSession,
|
|
25
|
-
} from "./page-evidence-contract.mjs";
|
|
26
|
-
import { parseStrictJson } from "./strict-json.mjs";
|
|
27
|
-
|
|
28
|
-
const styleGalleryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
29
|
-
const options = { artifactRoot: undefined, json: false, manifest: undefined, priorManifest: undefined, root: undefined };
|
|
30
|
-
const argumentFailures = [];
|
|
31
|
-
|
|
32
|
-
for (let index = 2; index < process.argv.length; index += 1) {
|
|
33
|
-
const argument = process.argv[index];
|
|
34
|
-
if (argument === "--json") options.json = true;
|
|
35
|
-
else if (["--artifact-root", "--manifest", "--prior-manifest", "--root"].includes(argument)) {
|
|
36
|
-
const value = process.argv[index + 1];
|
|
37
|
-
if (!value || value.startsWith("--")) argumentFailures.push(finding("argument_value_required", `${argument} requires a value`, "<cli>"));
|
|
38
|
-
else {
|
|
39
|
-
if (argument === "--artifact-root") options.artifactRoot = path.resolve(process.cwd(), value);
|
|
40
|
-
if (argument === "--manifest") options.manifest = path.resolve(process.cwd(), value);
|
|
41
|
-
if (argument === "--prior-manifest") options.priorManifest = path.resolve(process.cwd(), value);
|
|
42
|
-
if (argument === "--root") options.root = path.resolve(process.cwd(), value);
|
|
43
|
-
index += 1;
|
|
44
|
-
}
|
|
45
|
-
} else argumentFailures.push(finding("argument_unknown", `unsupported argument ${argument}`, "<cli>"));
|
|
46
|
-
}
|
|
47
|
-
for (const [name, value] of [["--root", options.root], ["--artifact-root", options.artifactRoot]]) {
|
|
48
|
-
if (!value) argumentFailures.push(finding("argument_value_required", `${name} is required`, "<cli>"));
|
|
49
|
-
}
|
|
50
|
-
if (!options.manifest && options.artifactRoot) options.manifest = path.join(options.artifactRoot, PAGE_EVIDENCE_MANIFEST);
|
|
51
|
-
|
|
52
|
-
let schemas;
|
|
53
|
-
try { schemas = compilePageEvidenceSchemas(path.join(styleGalleryRoot, "consumer-reference/schema")); }
|
|
54
|
-
catch (error) { argumentFailures.push(finding("page_evidence_schema_invalid", error instanceof Error ? error.message : String(error), "consumer-reference/schema")); }
|
|
55
|
-
|
|
56
|
-
function checkedArtifact(root, artifact, failures) {
|
|
57
|
-
const resolved = readContainedBytes({ prefix: "page_evidence_artifact", reference: artifact?.path, root }, failures);
|
|
58
|
-
if (!resolved || !artifact?.media_type) return undefined;
|
|
59
|
-
const bytes = resolved.bytes;
|
|
60
|
-
const actual = metadata(bytes, artifact.media_type, artifact.path);
|
|
61
|
-
if (!sameJson(actual, artifact)) failures.push(finding("page_evidence_artifact_integrity", "artifact bytes or metadata differ from the manifest", artifact.path));
|
|
62
|
-
if (artifact.media_type === "image/png" && (!actual.width || !actual.height)) failures.push(finding("page_evidence_png_invalid", "captured PNG must contain a valid IHDR/IDAT/IEND chunk stream", artifact.path));
|
|
63
|
-
return { actual, bytes, file: resolved.file };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function validatePacket({ allowReuse, artifactRoot, manifestFile, priorManifestFile, root, seen }) {
|
|
67
|
-
const failures = [];
|
|
68
|
-
const artifactReference = normalizeReference(root, artifactRoot);
|
|
69
|
-
if (!artifactReference) failures.push(finding("page_evidence_artifact_escape", "artifact root must be inside the consumer repository", artifactRoot));
|
|
70
|
-
else if (fs.existsSync(artifactRoot) && fs.lstatSync(artifactRoot).isSymbolicLink()) failures.push(finding("page_evidence_artifact_redirect", "artifact root must not be a filesystem redirect", artifactReference));
|
|
71
|
-
else resolveContained({ expectedType: "directory", prefix: "page_evidence_artifact", reference: artifactReference, root }, failures);
|
|
72
|
-
const artifactFiles = failures.length === 0 ? listArtifactFiles(artifactRoot, failures) : [];
|
|
73
|
-
if (failures.some((entry) => entry.code === "page_evidence_artifact_limit")) return { failures, manifest: undefined };
|
|
74
|
-
const manifestReference = normalizeReference(artifactRoot, manifestFile);
|
|
75
|
-
if (manifestReference !== PAGE_EVIDENCE_MANIFEST) failures.push(finding("page_evidence_manifest_path_invalid", `manifest must be ${PAGE_EVIDENCE_MANIFEST} at artifact-root`, manifestFile));
|
|
76
|
-
const manifestRecord = manifestReference ? readJsonFile({ prefix: "page_evidence_manifest", reference: manifestReference, root: artifactRoot }, failures) : undefined;
|
|
77
|
-
const receiptRecord = readReceipt(artifactRoot, schemas, failures);
|
|
78
|
-
const receipt = receiptRecord?.valid ? receiptRecord : undefined;
|
|
79
|
-
if (!manifestRecord || !receipt) return { failures, manifest: manifestRecord?.value };
|
|
80
|
-
if (!addSchemaFindings(schemas.manifest, manifestRecord.value, manifestReference, "page_evidence_manifest_schema_invalid", failures)) return { failures, manifest: manifestRecord.value };
|
|
81
|
-
const manifest = manifestRecord.value;
|
|
82
|
-
const manifestDigest = metadata(manifestRecord.bytes, "application/json", manifestReference).sha256;
|
|
83
|
-
if (seen.has(manifestDigest)) {
|
|
84
|
-
failures.push(finding("page_evidence_reuse_cycle", "prior manifest chain contains a cycle", manifestReference));
|
|
85
|
-
return { failures, manifest };
|
|
86
|
-
}
|
|
87
|
-
seen.add(manifestDigest);
|
|
88
|
-
|
|
89
|
-
const session = manifest.session;
|
|
90
|
-
const expectedSession = session ? {
|
|
91
|
-
attempt: receipt.value.attempt,
|
|
92
|
-
branch: receipt.value.branch,
|
|
93
|
-
completed_at: session.completed_at,
|
|
94
|
-
conformance_record: receipt.value.conformance_record,
|
|
95
|
-
environment: receipt.value.environment,
|
|
96
|
-
intended_scenario_ids: receipt.value.intended_scenario_ids,
|
|
97
|
-
nonce: receipt.value.nonce,
|
|
98
|
-
receipt: receipt.artifact,
|
|
99
|
-
receipt_sha256: receipt.digest,
|
|
100
|
-
repository: receipt.value.repository,
|
|
101
|
-
revision: receipt.value.revision,
|
|
102
|
-
run_id: receipt.value.run_id,
|
|
103
|
-
session_id: receipt.value.session_id,
|
|
104
|
-
source: receipt.value.source,
|
|
105
|
-
started_at: receipt.value.started_at,
|
|
106
|
-
} : undefined;
|
|
107
|
-
if (!sameJson(session, expectedSession)) failures.push(finding("page_evidence_session_mismatch", "completed manifest session differs from its immutable start receipt", manifestReference));
|
|
108
|
-
if (manifest.repository !== receipt.value.repository) failures.push(finding("page_evidence_repository_mismatch", "manifest repository differs from the receipt", manifestReference));
|
|
109
|
-
if (manifest.revision !== receipt.value.revision || manifest.run?.revision !== receipt.value.revision) failures.push(finding("page_evidence_revision_mismatch", "manifest revision differs from the receipt", manifestReference));
|
|
110
|
-
if (manifest.run?.id !== receipt.value.run_id || manifest.run?.attempt !== receipt.value.attempt || manifest.run?.repository !== receipt.value.repository) failures.push(finding("page_evidence_run_mismatch", "manifest run differs from the receipt", manifestReference));
|
|
111
|
-
if (manifest.completed_at !== session?.completed_at || !withinSession(session?.completed_at, receipt.value.started_at, session?.completed_at)) failures.push(finding("page_evidence_capture_outside_session", "manifest completion precedes session start", manifestReference));
|
|
112
|
-
|
|
113
|
-
const git = gitIdentity(root, failures);
|
|
114
|
-
if (git && git.revision !== receipt.value.revision) failures.push(finding("page_evidence_revision_mismatch", "checked-out revision differs from the evidence receipt", manifestReference));
|
|
115
|
-
if (git?.repository && git.repository !== receipt.value.repository) failures.push(finding("page_evidence_repository_mismatch", "Git remote identity differs from the evidence receipt", manifestReference));
|
|
116
|
-
const currentSource = canonicalSourceManifest(root, receipt.value.source.files.map((entry) => entry.path), failures);
|
|
117
|
-
if (currentSource && !sameJson(currentSource, receipt.value.source)) failures.push(finding("page_evidence_source_drift", "relevant source bytes differ from the evidence receipt", manifestReference));
|
|
118
|
-
if (dirtyRelevantSources(root, receipt.value.source.files.map((entry) => entry.path)).length > 0) failures.push(finding("page_evidence_source_drift", "relevant sources are dirty while validating evidence", manifestReference));
|
|
119
|
-
const conformance = readJsonFile({ prefix: "page_evidence_conformance", reference: receipt.value.conformance_record.path, root }, failures);
|
|
120
|
-
if (conformance && !sameJson(metadata(conformance.bytes, "application/json", conformance.reference), receipt.value.conformance_record)) failures.push(finding("page_evidence_conformance_drift", "conformance record differs from the evidence receipt", conformance.reference));
|
|
121
|
-
|
|
122
|
-
let priorPacket;
|
|
123
|
-
if (priorManifestFile) {
|
|
124
|
-
const priorRoot = path.dirname(priorManifestFile);
|
|
125
|
-
priorPacket = validatePacket({ allowReuse: false, artifactRoot: priorRoot, manifestFile: priorManifestFile, priorManifestFile: undefined, root, seen });
|
|
126
|
-
failures.push(...priorPacket.failures);
|
|
127
|
-
}
|
|
128
|
-
const scenarios = Array.isArray(manifest.scenarios) ? manifest.scenarios : [];
|
|
129
|
-
const controlFiles = new Set([PAGE_EVIDENCE_MANIFEST, PAGE_EVIDENCE_RECEIPT, ...scenarios.map((scenario) => scenario.runner_result.path)]);
|
|
130
|
-
const expectedFiles = new Set(controlFiles);
|
|
131
|
-
const scenarioIds = [];
|
|
132
|
-
for (const scenario of scenarios) {
|
|
133
|
-
scenarioIds.push(scenario.id);
|
|
134
|
-
const runnerArtifact = checkedArtifact(artifactRoot, scenario.runner_result, failures);
|
|
135
|
-
if (!runnerArtifact) continue;
|
|
136
|
-
let runner;
|
|
137
|
-
try { runner = parseStrictJson(runnerArtifact.bytes.toString("utf8")); }
|
|
138
|
-
catch (error) { failures.push(finding("page_evidence_runner_json_invalid", error instanceof Error ? error.message : String(error), scenario.runner_result.path)); continue; }
|
|
139
|
-
if (!addSchemaFindings(schemas.runner, runner, scenario.runner_result.path, "page_evidence_runner_schema_invalid", failures)) continue;
|
|
140
|
-
if (runner.status !== "passed") failures.push(finding("page_evidence_runner_failed", `${scenario.id} runner did not pass`, scenario.runner_result.path));
|
|
141
|
-
if (runner.session_id !== receipt.value.session_id || runner.nonce !== receipt.value.nonce || runner.receipt_sha256 !== receipt.digest) failures.push(finding("page_evidence_session_mismatch", `${scenario.id} runner session differs from the receipt`, scenario.runner_result.path));
|
|
142
|
-
if (runner.revision !== receipt.value.revision || runner.run?.revision !== receipt.value.revision) failures.push(finding("page_evidence_revision_mismatch", `${scenario.id} runner revision differs from the receipt`, scenario.runner_result.path));
|
|
143
|
-
if (runner.repository !== receipt.value.repository || runner.run?.repository !== receipt.value.repository) failures.push(finding("page_evidence_repository_mismatch", `${scenario.id} runner repository differs from the receipt`, scenario.runner_result.path));
|
|
144
|
-
if (runner.source_sha256 !== receipt.value.source.sha256) failures.push(finding("page_evidence_source_mismatch", `${scenario.id} runner source differs from the receipt`, scenario.runner_result.path));
|
|
145
|
-
if (!sameJson({ ...runner.run, status: "passed" }, manifest.run)) failures.push(finding("page_evidence_run_mismatch", `${scenario.id} runner identity differs from the closed run`, scenario.runner_result.path));
|
|
146
|
-
if (runner.scenario_id !== scenario.id || runner.recorded_at !== scenario.recorded_at || !sameJson(runner.semantic_environment, scenario.semantic_environment)) failures.push(finding("page_evidence_scenario_mismatch", `${scenario.id} runner scenario differs from the manifest`, scenario.runner_result.path));
|
|
147
|
-
if (!withinSession(runner.recorded_at, receipt.value.started_at, session?.completed_at)) failures.push(finding("page_evidence_capture_outside_session", `${scenario.id} runner was recorded outside the session interval`, scenario.runner_result.path));
|
|
148
|
-
if (scenario.evidence?.kind === "captured") {
|
|
149
|
-
const declared = runner.evidence?.kind === "captured" ? runner.evidence.artifacts : [];
|
|
150
|
-
const captured = scenario.evidence.artifacts ?? [];
|
|
151
|
-
if (!sameJson(declared, captured.map(({ path: artifactPath, media_type: mediaType }) => ({ media_type: mediaType, path: artifactPath })))) failures.push(finding("page_evidence_scenario_mismatch", `${scenario.id} artifact declarations differ from the runner result`, scenario.runner_result.path));
|
|
152
|
-
for (const artifact of captured) {
|
|
153
|
-
if (controlFiles.has(artifact.path)) failures.push(finding("page_evidence_artifact_control_collision", "captured artifacts must be disjoint from manifest, receipt, and runner-result controls", artifact.path));
|
|
154
|
-
expectedFiles.add(artifact.path);
|
|
155
|
-
checkedArtifact(artifactRoot, artifact, failures);
|
|
156
|
-
}
|
|
157
|
-
} else if (scenario.evidence?.kind === "reused") {
|
|
158
|
-
if (!allowReuse) failures.push(finding("page_evidence_reuse_chain_unsupported", "a reused manifest cannot be reused again", manifestReference));
|
|
159
|
-
if (!sameJson(runner.evidence, scenario.evidence)) failures.push(finding("page_evidence_reuse_identity_mismatch", `${scenario.id} reuse differs from the runner result`, scenario.runner_result.path));
|
|
160
|
-
if (!priorPacket?.manifest) failures.push(finding("page_evidence_reuse_manifest_required", "reused evidence requires a valid prior manifest", manifestReference));
|
|
161
|
-
else {
|
|
162
|
-
const prior = priorPacket.manifest;
|
|
163
|
-
const priorScenario = prior.scenarios?.find((candidate) => candidate.id === scenario.evidence.prior_scenario_id);
|
|
164
|
-
const priorBytes = fs.readFileSync(priorManifestFile);
|
|
165
|
-
if (scenario.evidence.prior_manifest_sha256 !== metadata(priorBytes, "application/json", PAGE_EVIDENCE_MANIFEST).sha256) failures.push(finding("page_evidence_reuse_manifest_mismatch", "prior manifest digest does not match", manifestReference));
|
|
166
|
-
if (scenario.evidence.prior_session_id !== prior.session?.session_id || scenario.evidence.prior_run_id !== prior.run?.id) failures.push(finding("page_evidence_reuse_identity_mismatch", "prior session or run identity does not match", manifestReference));
|
|
167
|
-
if (!priorScenario || priorScenario.id !== scenario.id || priorScenario.evidence?.kind !== "captured") failures.push(finding("page_evidence_reuse_scenario_mismatch", "reuse must select the same captured scenario", manifestReference));
|
|
168
|
-
if (!sameJson(prior.session?.source, receipt.value.source)) failures.push(finding("page_evidence_reuse_source_mismatch", "reuse requires byte-identical relevant sources", manifestReference));
|
|
169
|
-
if (priorScenario && !sameJson(priorScenario.semantic_environment, scenario.semantic_environment)) failures.push(finding("page_evidence_reuse_environment_mismatch", "reuse requires the same semantic environment", manifestReference));
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
if (!sameStringSet(scenarioIds, receipt.value.intended_scenario_ids)) failures.push(finding("page_evidence_pass_membership_invalid", "PASS scenarios must exactly match the start intent", manifestReference));
|
|
174
|
-
for (const reference of artifactFiles) if (!expectedFiles.has(reference)) failures.push(finding("page_evidence_artifact_unmanifested", "artifact root contains a file outside the closed manifest", reference));
|
|
175
|
-
seen.delete(manifestDigest);
|
|
176
|
-
return { failures, manifest };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
let report = { failures: argumentFailures, manifest: undefined };
|
|
180
|
-
if (argumentFailures.length === 0 && schemas) report = validatePacket({ allowReuse: true, artifactRoot: options.artifactRoot, manifestFile: options.manifest, priorManifestFile: options.priorManifest, root: options.root, seen: new Set() });
|
|
181
|
-
const result = {
|
|
182
|
-
artifactCount: report.manifest?.scenarios?.reduce((count, scenario) => count + (scenario.evidence?.artifacts?.length ?? 0), 0) ?? 0,
|
|
183
|
-
failures: report.failures,
|
|
184
|
-
ok: report.failures.length === 0,
|
|
185
|
-
scenarioCount: report.manifest?.scenarios?.length ?? 0,
|
|
186
|
-
sessionId: report.manifest?.session?.session_id,
|
|
187
|
-
};
|
|
188
|
-
if (options.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
189
|
-
else if (result.ok) process.stdout.write(`validated ${result.scenarioCount} page evidence scenarios\n`);
|
|
190
|
-
else process.stderr.write(`${result.failures.map((issue) => `${issue.code}: ${issue.path}: ${issue.message}`).join("\n")}\n`);
|
|
191
|
-
if (!result.ok) process.exitCode = 1;
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
|
|
6
|
-
const args = new Set(process.argv.slice(2));
|
|
7
|
-
const minCountArg = process.argv.find((arg) => arg.startsWith("--min-count="));
|
|
8
|
-
const minCountIndex = process.argv.indexOf("--min-count");
|
|
9
|
-
const minCount = minCountArg ? Number(minCountArg.split("=")[1]) : minCountIndex === -1 ? 1 : Number(process.argv[minCountIndex + 1] || 1);
|
|
10
|
-
const json = args.has("--json");
|
|
11
|
-
const root = process.cwd();
|
|
12
|
-
const patternsDir = path.join(root, "patterns");
|
|
13
|
-
|
|
14
|
-
const requiredFields = [
|
|
15
|
-
"name",
|
|
16
|
-
"category",
|
|
17
|
-
"primary_spatial_problem",
|
|
18
|
-
"secondary_spatial_problems",
|
|
19
|
-
"layout_axis",
|
|
20
|
-
"content_shape",
|
|
21
|
-
"responsiveness",
|
|
22
|
-
"constraints",
|
|
23
|
-
"scroll_ownership",
|
|
24
|
-
"source_lineage",
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
const requiredSections = [
|
|
28
|
-
"## When To Use",
|
|
29
|
-
"## HTML",
|
|
30
|
-
"## CSS",
|
|
31
|
-
"## Core Properties",
|
|
32
|
-
"## Properties That Break The Layout If Removed",
|
|
33
|
-
"## Constraints And Change Points",
|
|
34
|
-
"## Scroll Ownership",
|
|
35
|
-
"## Accessibility And Source Order Notes",
|
|
36
|
-
"## Browser And Fallback Notes",
|
|
37
|
-
"## Composition Notes",
|
|
38
|
-
"## Anti-patterns",
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
const recommendedAccessibilityDetails = [
|
|
42
|
-
"Semantic role expectation:",
|
|
43
|
-
"DOM order expectation:",
|
|
44
|
-
"Focus risk:",
|
|
45
|
-
"Scroll expectation:",
|
|
46
|
-
"Cognitive risk:",
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
const forbiddenProperties = [
|
|
50
|
-
"animation",
|
|
51
|
-
"background",
|
|
52
|
-
"border",
|
|
53
|
-
"border-radius",
|
|
54
|
-
"box-shadow",
|
|
55
|
-
"color",
|
|
56
|
-
"filter",
|
|
57
|
-
"font",
|
|
58
|
-
"font-family",
|
|
59
|
-
"font-size",
|
|
60
|
-
"font-weight",
|
|
61
|
-
"opacity",
|
|
62
|
-
"text-align",
|
|
63
|
-
"text-decoration",
|
|
64
|
-
"transition",
|
|
65
|
-
"transform",
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
const forbiddenHtmlPhrases = [
|
|
69
|
-
"Alpha item",
|
|
70
|
-
"Beta item",
|
|
71
|
-
"Gamma item",
|
|
72
|
-
"First item",
|
|
73
|
-
"Second item",
|
|
74
|
-
"Third item",
|
|
75
|
-
"Item 1",
|
|
76
|
-
"Main content",
|
|
77
|
-
"Main region",
|
|
78
|
-
"Media slot",
|
|
79
|
-
"Primary content",
|
|
80
|
-
"Primary region",
|
|
81
|
-
"Readable content block",
|
|
82
|
-
"Readable content region",
|
|
83
|
-
"Region 1",
|
|
84
|
-
"Region 2",
|
|
85
|
-
"Region 3",
|
|
86
|
-
"Related supporting notes",
|
|
87
|
-
"Responsive media slot",
|
|
88
|
-
"Secondary content",
|
|
89
|
-
"Secondary region",
|
|
90
|
-
"Section actions",
|
|
91
|
-
"Section heading",
|
|
92
|
-
"Scrollable content",
|
|
93
|
-
"Scrollable content region",
|
|
94
|
-
"Supporting aside",
|
|
95
|
-
"Tertiary",
|
|
96
|
-
];
|
|
97
|
-
|
|
98
|
-
const failures = [];
|
|
99
|
-
const warnings = [];
|
|
100
|
-
|
|
101
|
-
function walk(dir) {
|
|
102
|
-
if (!fs.existsSync(dir)) return [];
|
|
103
|
-
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
104
|
-
const next = path.join(dir, entry.name);
|
|
105
|
-
if (entry.isDirectory()) return walk(next);
|
|
106
|
-
if (!entry.isFile() || !entry.name.endsWith(".md")) return [];
|
|
107
|
-
return entry.name === "index.md" || entry.name === "log.md" ? [] : [next];
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function frontmatter(content) {
|
|
112
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n/);
|
|
113
|
-
if (!match) return null;
|
|
114
|
-
return Object.fromEntries(
|
|
115
|
-
match[1].split("\n").map((line) => {
|
|
116
|
-
const index = line.indexOf(":");
|
|
117
|
-
if (index === -1) return [line.trim(), ""];
|
|
118
|
-
return [line.slice(0, index).trim(), line.slice(index + 1).trim()];
|
|
119
|
-
}),
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function codeBlock(content, lang) {
|
|
124
|
-
const match = content.match(new RegExp(`\\\`\\\`\\\`${lang}\\n([\\s\\S]*?)\\n\\\`\\\`\\\``));
|
|
125
|
-
return match?.[1] ?? "";
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function stripFencedCodeBlocks(content) {
|
|
129
|
-
return content.replace(/```[\s\S]*?```/g, "");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function stripCssComments(css) {
|
|
133
|
-
return css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function hasHeading(content, heading) {
|
|
137
|
-
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
138
|
-
return new RegExp(`^${escaped}$`, "m").test(content);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function checkCss(file, css) {
|
|
142
|
-
const rulePattern = /([^{}]+)\{([^{}]+)\}/g;
|
|
143
|
-
let match;
|
|
144
|
-
let ruleCount = 0;
|
|
145
|
-
while ((match = rulePattern.exec(css)) !== null) {
|
|
146
|
-
ruleCount += 1;
|
|
147
|
-
const selector = match[1].trim();
|
|
148
|
-
if (selector.includes("#")) failures.push(`${file}: selector uses id: ${selector}`);
|
|
149
|
-
const declarations = match[2].split(";").map((part) => part.trim()).filter(Boolean);
|
|
150
|
-
const properties = declarations.map((declaration) => declaration.split(":")[0].trim());
|
|
151
|
-
const sorted = [...properties].sort((a, b) => a.localeCompare(b));
|
|
152
|
-
if (properties.join("|") !== sorted.join("|")) {
|
|
153
|
-
failures.push(`${file}: declarations are not alphabetical in ${selector}: ${properties.join(", ")}`);
|
|
154
|
-
}
|
|
155
|
-
for (const property of properties) {
|
|
156
|
-
if (forbiddenProperties.includes(property) || forbiddenProperties.some((blocked) => property.startsWith(`${blocked}-`))) {
|
|
157
|
-
failures.push(`${file}: forbidden decorative property ${property} in ${selector}`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (ruleCount === 0) failures.push(`${file}: no CSS rules found`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function checkHtmlCssHooks(file, html, css) {
|
|
165
|
-
const htmlClasses = new Set([...html.matchAll(/class="([^"]+)"/g)].flatMap((match) => match[1].split(/\s+/)));
|
|
166
|
-
const cssClasses = [...new Set([...css.matchAll(/\.([a-zA-Z_][a-zA-Z0-9_-]*)/g)].map((match) => match[1]))];
|
|
167
|
-
for (const className of cssClasses) {
|
|
168
|
-
if (!htmlClasses.has(className)) failures.push(`${file}: css class .${className} is missing from HTML block`);
|
|
169
|
-
}
|
|
170
|
-
for (const className of htmlClasses) {
|
|
171
|
-
if (!cssClasses.includes(className)) failures.push(`${file}: html class .${className} has no CSS rule`);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function checkHtmlSample(file, html) {
|
|
176
|
-
for (const phrase of forbiddenHtmlPhrases) {
|
|
177
|
-
if (html.includes(phrase)) failures.push(`${file}: placeholder sample phrase "${phrase}" in HTML block`);
|
|
178
|
-
}
|
|
179
|
-
if (html.includes("media-placeholder")) failures.push(`${file}: placeholder media reference in HTML block`);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function checkReflowClaim(file, meta, css) {
|
|
183
|
-
if (meta.responsiveness !== "reflow") return;
|
|
184
|
-
const inspectableCss = stripCssComments(css);
|
|
185
|
-
const hasReflowMechanic = [
|
|
186
|
-
"@container",
|
|
187
|
-
"@media",
|
|
188
|
-
"auto-fit",
|
|
189
|
-
"auto-fill",
|
|
190
|
-
"flex-wrap",
|
|
191
|
-
].some((mechanic) => inspectableCss.includes(mechanic));
|
|
192
|
-
const hasNarrowGuard = inspectableCss.includes("min(") || inspectableCss.includes("100%") || inspectableCss.includes("flex-wrap");
|
|
193
|
-
if (!hasReflowMechanic || !hasNarrowGuard) {
|
|
194
|
-
failures.push(`${file}: responsiveness reflow requires a stack, wrap, container query, media query, or intrinsic repeat guard`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const files = walk(patternsDir);
|
|
199
|
-
for (const absolute of files) {
|
|
200
|
-
const file = path.relative(root, absolute);
|
|
201
|
-
const content = fs.readFileSync(absolute, "utf8");
|
|
202
|
-
const meta = frontmatter(content);
|
|
203
|
-
if (!meta) {
|
|
204
|
-
failures.push(`${file}: missing frontmatter`);
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
for (const field of requiredFields) {
|
|
208
|
-
if (!meta[field]) failures.push(`${file}: missing ${field}`);
|
|
209
|
-
}
|
|
210
|
-
const markdown = stripFencedCodeBlocks(content);
|
|
211
|
-
for (const section of requiredSections) {
|
|
212
|
-
if (!hasHeading(markdown, section)) failures.push(`${file}: missing ${section.replace("## ", "")} section`);
|
|
213
|
-
}
|
|
214
|
-
for (const detail of recommendedAccessibilityDetails) {
|
|
215
|
-
if (!markdown.includes(detail)) warnings.push(`${file}: missing recommended accessibility detail ${detail}`);
|
|
216
|
-
}
|
|
217
|
-
if (hasHeading(markdown, "## Accessibility Notes")) warnings.push(`${file}: legacy accessibility heading; prefer Accessibility And Source Order Notes`);
|
|
218
|
-
const html = codeBlock(content, "html");
|
|
219
|
-
if (!html) failures.push(`${file}: missing html code block`);
|
|
220
|
-
const css = codeBlock(content, "css");
|
|
221
|
-
if (!css) failures.push(`${file}: missing css code block`);
|
|
222
|
-
if (html) checkHtmlSample(file, html);
|
|
223
|
-
if (css) checkCss(file, css);
|
|
224
|
-
if (css) checkReflowClaim(file, meta, css);
|
|
225
|
-
if (html && css) checkHtmlCssHooks(file, html, css);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (files.length < minCount) failures.push(`expected at least ${minCount} pattern files, found ${files.length}`);
|
|
229
|
-
|
|
230
|
-
const result = {
|
|
231
|
-
ok: failures.length === 0,
|
|
232
|
-
count: files.length,
|
|
233
|
-
minCount,
|
|
234
|
-
failures,
|
|
235
|
-
warnings,
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
if (json) {
|
|
239
|
-
console.log(JSON.stringify(result, null, 2));
|
|
240
|
-
} else if (result.ok) {
|
|
241
|
-
console.log(`ok: ${files.length} pattern files (${warnings.length} warnings)`);
|
|
242
|
-
} else {
|
|
243
|
-
console.error(result.failures.join("\n"));
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
process.exitCode = result.ok ? 0 : 1;
|