@domandigital/craft 0.10.0 → 0.11.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/CHANGELOG.md +19 -0
- package/README.md +35 -0
- package/dist/{audit-EGDNWSQB.js → audit-QIPXL7E4.js} +1 -1
- package/dist/audit.cjs +3 -1
- package/dist/audit.js +3 -1
- package/dist/{chunk-JKIDWHG3.js → chunk-2AJB27BJ.js} +282 -29
- package/dist/{chunk-APO4BT2F.js → chunk-3OZQ4C5R.js} +5 -4
- package/dist/{chunk-2NQFIDLE.js → chunk-HOHHQB4N.js} +7 -6
- package/dist/{chunk-TSKRBIOC.js → chunk-JP75P2DU.js} +3 -1
- package/dist/chunk-MSQ57PJU.js +206 -0
- package/dist/{cli-PRWTNFVP.js → cli-4ESXOLKV.js} +2 -2
- package/dist/{cli-ADQQURNR.js → cli-DBZMVYYV.js} +17 -11
- package/dist/{cli-EK3GSAOW.js → cli-T5RD2BTK.js} +6 -4
- package/dist/{cli-2WBGTBKD.js → cli-U3SZVHSK.js} +4 -3
- package/dist/{cli-VQUNNIZM.js → cli-VREYWCT4.js} +3 -3
- package/dist/cli.js +1 -1
- package/dist/index.cjs +216 -0
- package/dist/index.d.cts +185 -1
- package/dist/index.d.ts +185 -1
- package/dist/index.js +202 -0
- package/package.json +13 -6
- package/dist/chunk-U2JTFQKO.js +0 -112
package/dist/index.d.ts
CHANGED
|
@@ -1188,6 +1188,190 @@ declare function namedSources(sentence: string): string[];
|
|
|
1188
1188
|
declare function findClaims(files: SourceFile[]): ClaimsReport;
|
|
1189
1189
|
declare function formatClaims(report: ClaimsReport, label?: string): string;
|
|
1190
1190
|
|
|
1191
|
+
/**
|
|
1192
|
+
* Baselines: adopt craft on a repo that already has findings, and fail only
|
|
1193
|
+
* on new ones.
|
|
1194
|
+
*
|
|
1195
|
+
* A finding is keyed on its tell, its path and a hash of its excerpt with the
|
|
1196
|
+
* whitespace collapsed, never on its line: an edit above a known finding
|
|
1197
|
+
* moves it, and a moved finding is not a new one. Keys are counted, so a file
|
|
1198
|
+
* with two identical findings and a baseline of one reports the second.
|
|
1199
|
+
*
|
|
1200
|
+
* Pure. The command line reads and writes the file.
|
|
1201
|
+
*/
|
|
1202
|
+
|
|
1203
|
+
declare const BASELINE_VERSION = 1;
|
|
1204
|
+
interface BaselineEntry {
|
|
1205
|
+
tell: string;
|
|
1206
|
+
path: string;
|
|
1207
|
+
/** FNV-1a of the excerpt, whitespace collapsed. */
|
|
1208
|
+
excerpt: string;
|
|
1209
|
+
count: number;
|
|
1210
|
+
}
|
|
1211
|
+
interface Baseline {
|
|
1212
|
+
version: typeof BASELINE_VERSION;
|
|
1213
|
+
catalogueVersion: string;
|
|
1214
|
+
entries: BaselineEntry[];
|
|
1215
|
+
}
|
|
1216
|
+
/** The key a finding is matched on. Also the SARIF partial fingerprint. */
|
|
1217
|
+
declare function findingKey(f: Finding): string;
|
|
1218
|
+
/** Every finding in a report, as a baseline. */
|
|
1219
|
+
declare function createBaseline(report: CheckReport): Baseline;
|
|
1220
|
+
/** Validate a parsed baseline file. */
|
|
1221
|
+
declare function parseBaseline(data: unknown, source?: string): Baseline;
|
|
1222
|
+
/**
|
|
1223
|
+
* The report without the findings the baseline already knows about.
|
|
1224
|
+
* `baselined` is how many were removed, so the output can say so: a
|
|
1225
|
+
* baseline that hides findings silently is a hole.
|
|
1226
|
+
*/
|
|
1227
|
+
declare function applyBaseline(report: CheckReport, baseline: Baseline): CheckReport & {
|
|
1228
|
+
baselined: number;
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* `craft.config.json`: what a repo tells craft about itself.
|
|
1233
|
+
*
|
|
1234
|
+
* Pure: the command line reads the file and hands the parsed JSON here. The
|
|
1235
|
+
* design choices live in `art-direction.json`; this file is about the tool:
|
|
1236
|
+
* which paths are not the site, where the copy is, and a repo's own decisions
|
|
1237
|
+
* about a tell's severity.
|
|
1238
|
+
*
|
|
1239
|
+
* {
|
|
1240
|
+
* "ignore": ["docs/archive/**", "*.stories.tsx"],
|
|
1241
|
+
* "copyPaths": ["content", "src/app"],
|
|
1242
|
+
* "severity": {
|
|
1243
|
+
* "ai-violet": { "level": "off", "because": "The client's shopfront has been this violet since 1998." },
|
|
1244
|
+
* "em-dash": { "level": "block", "because": "House style for this client bans them outright." }
|
|
1245
|
+
* }
|
|
1246
|
+
* }
|
|
1247
|
+
*
|
|
1248
|
+
* A severity change needs a `because`, like an exception in
|
|
1249
|
+
* `art-direction.json`, and for the same reason: a tell switched off with no
|
|
1250
|
+
* reason is a guard nobody can audit. `off` becomes an exception, so the
|
|
1251
|
+
* report still lists what it silenced.
|
|
1252
|
+
*/
|
|
1253
|
+
|
|
1254
|
+
type SeverityLevel = "warn" | "block" | "off";
|
|
1255
|
+
interface SeverityOverride {
|
|
1256
|
+
level: SeverityLevel;
|
|
1257
|
+
because: string;
|
|
1258
|
+
}
|
|
1259
|
+
interface CraftConfig {
|
|
1260
|
+
/** Glob patterns for paths that are not the site. Added to `DEFAULT_IGNORE`. */
|
|
1261
|
+
ignore: string[];
|
|
1262
|
+
/** Where `craft copy` looks when no path is given. */
|
|
1263
|
+
copyPaths?: string[];
|
|
1264
|
+
severity: Record<string, SeverityOverride>;
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Agent and tool folders: instructions for the people and models building
|
|
1268
|
+
* the site, not copy a visitor reads. Always ignored by a directory walk.
|
|
1269
|
+
*/
|
|
1270
|
+
declare const DEFAULT_IGNORE: string[];
|
|
1271
|
+
/**
|
|
1272
|
+
* Validate a parsed `craft.config.json`. Throws with the file name and the
|
|
1273
|
+
* key at fault: a config that half-applies is worse than one that stops the
|
|
1274
|
+
* run.
|
|
1275
|
+
*/
|
|
1276
|
+
declare function parseCraftConfig(data: unknown, knownTells: ReadonlySet<string>, source?: string): CraftConfig;
|
|
1277
|
+
/**
|
|
1278
|
+
* A glob as a regular expression over a `/`-separated relative path.
|
|
1279
|
+
* `*` is one segment's worth, `**` any depth, `?` one character. A pattern
|
|
1280
|
+
* with no `/` matches a file or folder of that name anywhere; otherwise it is
|
|
1281
|
+
* anchored at the repo root. Anything under a matched folder matches too.
|
|
1282
|
+
*/
|
|
1283
|
+
declare function globToRegExp(glob: string): RegExp;
|
|
1284
|
+
/** A matcher for a set of globs. Paths are normalised to `/` before matching. */
|
|
1285
|
+
declare function ignoreMatcher(patterns: string[]): (path: string) => boolean;
|
|
1286
|
+
/** `off` entries, as exceptions: applied, and listed as silenced in the report. */
|
|
1287
|
+
declare function severityExceptions(config: CraftConfig): TellException[];
|
|
1288
|
+
/** `warn` and `block` entries, applied to a finished report. */
|
|
1289
|
+
declare function applySeverity(report: CheckReport, config: CraftConfig): CheckReport;
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* A report as SARIF 2.1.0, for GitHub code scanning and any other tool that
|
|
1293
|
+
* reads it. Pure.
|
|
1294
|
+
*
|
|
1295
|
+
* One rule per tell the report names, carrying the tell's `why` and `fix`.
|
|
1296
|
+
* `block` findings are errors, `warn` findings warnings. A finding on a
|
|
1297
|
+
* rendered page has the URL as its location and no line: GitHub code
|
|
1298
|
+
* scanning only shows results in files of the repo, so those appear in the
|
|
1299
|
+
* SARIF file but not in the Security tab.
|
|
1300
|
+
*/
|
|
1301
|
+
|
|
1302
|
+
interface SarifTell {
|
|
1303
|
+
id: string;
|
|
1304
|
+
name: string;
|
|
1305
|
+
why: string;
|
|
1306
|
+
fix: string;
|
|
1307
|
+
}
|
|
1308
|
+
interface SarifOptions {
|
|
1309
|
+
/** The catalogue, for each rule's description. */
|
|
1310
|
+
tells: readonly SarifTell[];
|
|
1311
|
+
/** Where a reader learns more. */
|
|
1312
|
+
informationUri?: string;
|
|
1313
|
+
}
|
|
1314
|
+
declare function toSarif(report: CheckReport, options: SarifOptions): {
|
|
1315
|
+
$schema: string;
|
|
1316
|
+
version: "2.1.0";
|
|
1317
|
+
runs: {
|
|
1318
|
+
tool: {
|
|
1319
|
+
driver: {
|
|
1320
|
+
name: string;
|
|
1321
|
+
version: string;
|
|
1322
|
+
informationUri: string;
|
|
1323
|
+
rules: {
|
|
1324
|
+
fullDescription?: {
|
|
1325
|
+
text: string;
|
|
1326
|
+
} | undefined;
|
|
1327
|
+
help?: {
|
|
1328
|
+
text: string;
|
|
1329
|
+
} | undefined;
|
|
1330
|
+
id: string;
|
|
1331
|
+
name: string;
|
|
1332
|
+
shortDescription: {
|
|
1333
|
+
text: string;
|
|
1334
|
+
};
|
|
1335
|
+
}[];
|
|
1336
|
+
};
|
|
1337
|
+
};
|
|
1338
|
+
results: {
|
|
1339
|
+
ruleId: string;
|
|
1340
|
+
ruleIndex: number;
|
|
1341
|
+
level: "error" | "warning";
|
|
1342
|
+
message: {
|
|
1343
|
+
text: string;
|
|
1344
|
+
};
|
|
1345
|
+
locations: {
|
|
1346
|
+
physicalLocation: {
|
|
1347
|
+
region?: {
|
|
1348
|
+
startLine: number;
|
|
1349
|
+
snippet: {
|
|
1350
|
+
text: string;
|
|
1351
|
+
};
|
|
1352
|
+
} | undefined;
|
|
1353
|
+
artifactLocation: {
|
|
1354
|
+
uri: string;
|
|
1355
|
+
};
|
|
1356
|
+
};
|
|
1357
|
+
}[];
|
|
1358
|
+
partialFingerprints: {
|
|
1359
|
+
"craftFinding/v1": string;
|
|
1360
|
+
};
|
|
1361
|
+
}[];
|
|
1362
|
+
}[];
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* The `--json` contract. Every JSON document a craft command prints carries
|
|
1367
|
+
* `schemaVersion`, so a script reading it can refuse a shape it does not
|
|
1368
|
+
* know instead of misreading it. Bump it when a field is removed or changes
|
|
1369
|
+
* meaning; adding a field is not a bump.
|
|
1370
|
+
*/
|
|
1371
|
+
declare const JSON_SCHEMA_VERSION = 1;
|
|
1372
|
+
/** A command's JSON output, versioned. */
|
|
1373
|
+
declare function toJson(data: object): string;
|
|
1374
|
+
|
|
1191
1375
|
/**
|
|
1192
1376
|
* The rendered detection path for tells that can be seen on a live page.
|
|
1193
1377
|
*
|
|
@@ -1820,4 +2004,4 @@ declare function characterReport(input: CharacterInputs, copyTells?: Set<string>
|
|
|
1820
2004
|
|
|
1821
2005
|
declare function retrofitPlan(report: CharacterReport, direction?: ArtDirection | null): string;
|
|
1822
2006
|
|
|
1823
|
-
export { AI_PHRASES, AI_VIOLET, AI_WORDS, type AccentFork, type AccentForkOptions, type Action, type Area, type ArtDirection, BUZZWORDS, CATALOGUE, CATALOGUE_VERSION, CHOICE_KEYS, type CandidateKind, type CharacterInputs, type CharacterReport, type CheckOptions, type CheckReport, type CheckRestraintInput, type ChoiceKey, type Claim, type ClaimsReport, type ContrastCheck, type ContrastReport, type CopyTell, type CraftTokensInput, type CraftTokensResult, DENSITIES, DENSITY_NAMES, DIRECTION_VERSION, DISTANCE_WEIGHTS, DURATION_MS, DURATION_S, type DensityCssOptions, type DensityName, type DirectionChoice, type DirectionProblem, type DirectionReport, type DirectionSource, type DurationName, EASE, EASE_TUPLE, ESTATE_VERSION, EXIT_RATIO, type EaseName, type EmitCssOptions, type EstateMatch, type EstatePair, type EstateRegister, type EstateSite, FINGERPRINT_VERSION, type Fact, type FactComparison, type FactKind, type Finding, type Fingerprint, type FingerprintDistance, type FluidSpaceOptions, type FluidTypeOptions, type FluidTypeResult, type FontClass, type Gamut, type Generation, HOUSE, HOUSE_BUDGET, HOUSE_TYPE, type HarvestCandidate, type HarvestOptions, type Hit, type HouseRule, type HouseTier, LIGHTNESS_CURVE, MIN_DECIDED, MIN_RUNS, type MotionDecision, type MotionKind, NEGATIVE_REASSURANCE, NULL_VERSION, type NullModel, type NullRun, type Oklch, type OklchToHexOptions, PILL_LIMIT, PLAINER_WORDS, PROPOSED, type PaletteColour, type Proposal, type ProposeInput, RAMP_STEPS, REFLEX_FONTS_1, REFLEX_FONTS_2, RENDERED_PATHS, REVEAL_LIMIT, REVIEW_PHRASES, type Ramp, type RampFromAnchorsOptions, type RampOptions, type RampStep, type RestraintBudget, type RestraintReport, type RestraintSeverity, type RestraintViolation, type Rgb, SCALE, SHADCN_LIMIT, SHADCN_PRIMITIVES, SIBLING_AT, SNAPSHOT_VERSION, SOURCE_KINDS, SPACE_STEPS, SPRING, STOCK_PHRASES, type SectionKind, type SectionRhythmOptions, type SemanticInput, type SemanticResult, type Severity, type ShouldAnimateInput, type SignalResult, type Snapshot, type SnapshotColour, type SnapshotControl, type SnapshotEffects, type SnapshotFont, type SnapshotGradient, type SnapshotHeading, type SnapshotMotion, type SnapshotPatch, type SnapshotSection, type SourceFile, type SourceKind, type SourceTell, type SpaceStep, type Surface, TELL_HEAVY, TYPE_STEPS, TYPICAL_AT, type TailwindPreset, type Tell, type TellException, type TypeStep, type Typicality, VAGUE_WORDS, type ValidateContext, type Verdict, accentFork, addSite, apcaContrast, applyHouseGate, auditSnapshot, catalogueTable, characterReport, checkCopy, checkPair, checkRestraint, choicesOf, compareFacts, compareToEstate, craftTokens, deltaEOk, deltaEOkHex, densityCss, densityTokens, emitCss, emptyEstate, estatePairs, exitDuration, extractHtml, findClaims, findColours, fingerprint, fingerprintDistance, fluidClamp, fluidSpace, fluidType, fontClass, formatClaims, formatComparison, formatHex, formatReport, groundName, harvest, hexToOklch, houseRule, hueName, hueSwatch, inP3Gamut, inSrgbGamut, initDirection, isAiViolet, isCream, makeSnapshot, motionTokens, namedSources, nearest, normaliseFamily, nullPrompt, oklchToHex, oklchToRgb, paletteFromPixels, parseColour, parseHex, propose, protectedFacts, ramp, rampFromAnchors, retrofitPlan, runTell, scanSource, sectionRhythm, semantic, shapeName, sharedParts, shouldAnimate, siblingLine, tailwindV3Preset, tailwindV4Theme, tellById, toGamut, typeFeatureTokens, typicality, validateDirection, visibleText, wcagContrast, wordListsJson };
|
|
2007
|
+
export { AI_PHRASES, AI_VIOLET, AI_WORDS, type AccentFork, type AccentForkOptions, type Action, type Area, type ArtDirection, BASELINE_VERSION, BUZZWORDS, type Baseline, type BaselineEntry, CATALOGUE, CATALOGUE_VERSION, CHOICE_KEYS, type CandidateKind, type CharacterInputs, type CharacterReport, type CheckOptions, type CheckReport, type CheckRestraintInput, type ChoiceKey, type Claim, type ClaimsReport, type ContrastCheck, type ContrastReport, type CopyTell, type CraftConfig, type CraftTokensInput, type CraftTokensResult, DEFAULT_IGNORE, DENSITIES, DENSITY_NAMES, DIRECTION_VERSION, DISTANCE_WEIGHTS, DURATION_MS, DURATION_S, type DensityCssOptions, type DensityName, type DirectionChoice, type DirectionProblem, type DirectionReport, type DirectionSource, type DurationName, EASE, EASE_TUPLE, ESTATE_VERSION, EXIT_RATIO, type EaseName, type EmitCssOptions, type EstateMatch, type EstatePair, type EstateRegister, type EstateSite, FINGERPRINT_VERSION, type Fact, type FactComparison, type FactKind, type Finding, type Fingerprint, type FingerprintDistance, type FluidSpaceOptions, type FluidTypeOptions, type FluidTypeResult, type FontClass, type Gamut, type Generation, HOUSE, HOUSE_BUDGET, HOUSE_TYPE, type HarvestCandidate, type HarvestOptions, type Hit, type HouseRule, type HouseTier, JSON_SCHEMA_VERSION, LIGHTNESS_CURVE, MIN_DECIDED, MIN_RUNS, type MotionDecision, type MotionKind, NEGATIVE_REASSURANCE, NULL_VERSION, type NullModel, type NullRun, type Oklch, type OklchToHexOptions, PILL_LIMIT, PLAINER_WORDS, PROPOSED, type PaletteColour, type Proposal, type ProposeInput, RAMP_STEPS, REFLEX_FONTS_1, REFLEX_FONTS_2, RENDERED_PATHS, REVEAL_LIMIT, REVIEW_PHRASES, type Ramp, type RampFromAnchorsOptions, type RampOptions, type RampStep, type RestraintBudget, type RestraintReport, type RestraintSeverity, type RestraintViolation, type Rgb, SCALE, SHADCN_LIMIT, SHADCN_PRIMITIVES, SIBLING_AT, SNAPSHOT_VERSION, SOURCE_KINDS, SPACE_STEPS, SPRING, STOCK_PHRASES, type SarifOptions, type SarifTell, type SectionKind, type SectionRhythmOptions, type SemanticInput, type SemanticResult, type Severity, type SeverityLevel, type SeverityOverride, type ShouldAnimateInput, type SignalResult, type Snapshot, type SnapshotColour, type SnapshotControl, type SnapshotEffects, type SnapshotFont, type SnapshotGradient, type SnapshotHeading, type SnapshotMotion, type SnapshotPatch, type SnapshotSection, type SourceFile, type SourceKind, type SourceTell, type SpaceStep, type Surface, TELL_HEAVY, TYPE_STEPS, TYPICAL_AT, type TailwindPreset, type Tell, type TellException, type TypeStep, type Typicality, VAGUE_WORDS, type ValidateContext, type Verdict, accentFork, addSite, apcaContrast, applyBaseline, applyHouseGate, applySeverity, auditSnapshot, catalogueTable, characterReport, checkCopy, checkPair, checkRestraint, choicesOf, compareFacts, compareToEstate, craftTokens, createBaseline, deltaEOk, deltaEOkHex, densityCss, densityTokens, emitCss, emptyEstate, estatePairs, exitDuration, extractHtml, findClaims, findColours, findingKey, fingerprint, fingerprintDistance, fluidClamp, fluidSpace, fluidType, fontClass, formatClaims, formatComparison, formatHex, formatReport, globToRegExp, groundName, harvest, hexToOklch, houseRule, hueName, hueSwatch, ignoreMatcher, inP3Gamut, inSrgbGamut, initDirection, isAiViolet, isCream, makeSnapshot, motionTokens, namedSources, nearest, normaliseFamily, nullPrompt, oklchToHex, oklchToRgb, paletteFromPixels, parseBaseline, parseColour, parseCraftConfig, parseHex, propose, protectedFacts, ramp, rampFromAnchors, retrofitPlan, runTell, scanSource, sectionRhythm, semantic, severityExceptions, shapeName, sharedParts, shouldAnimate, siblingLine, tailwindV3Preset, tailwindV4Theme, tellById, toGamut, toJson, toSarif, typeFeatureTokens, typicality, validateDirection, visibleText, wcagContrast, wordListsJson };
|
package/dist/index.js
CHANGED
|
@@ -4599,6 +4599,194 @@ function formatClaims(report2, label = "craft copy claims") {
|
|
|
4599
4599
|
return lines.join("\n");
|
|
4600
4600
|
}
|
|
4601
4601
|
|
|
4602
|
+
// src/character/baseline.ts
|
|
4603
|
+
var BASELINE_VERSION = 1;
|
|
4604
|
+
function fnv1a(text) {
|
|
4605
|
+
let hash = 2166136261;
|
|
4606
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
4607
|
+
hash ^= text.charCodeAt(i);
|
|
4608
|
+
hash = Math.imul(hash, 16777619);
|
|
4609
|
+
}
|
|
4610
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
4611
|
+
}
|
|
4612
|
+
var excerptHash = (f4) => fnv1a(f4.excerpt.replace(/\s+/g, " ").trim());
|
|
4613
|
+
var keyOf = (tell, path, excerpt) => `${tell}\0${path}\0${excerpt}`;
|
|
4614
|
+
function findingKey(f4) {
|
|
4615
|
+
return keyOf(f4.tell, f4.path, excerptHash(f4));
|
|
4616
|
+
}
|
|
4617
|
+
function createBaseline(report2) {
|
|
4618
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4619
|
+
for (const f4 of report2.findings) {
|
|
4620
|
+
const excerpt = excerptHash(f4);
|
|
4621
|
+
const key = keyOf(f4.tell, f4.path, excerpt);
|
|
4622
|
+
const entry = counts.get(key);
|
|
4623
|
+
if (entry) entry.count += 1;
|
|
4624
|
+
else counts.set(key, { tell: f4.tell, path: f4.path, excerpt, count: 1 });
|
|
4625
|
+
}
|
|
4626
|
+
const entries = [...counts.values()].sort((a, b) => a.path.localeCompare(b.path) || a.tell.localeCompare(b.tell) || a.excerpt.localeCompare(b.excerpt));
|
|
4627
|
+
return { version: BASELINE_VERSION, catalogueVersion: report2.catalogueVersion, entries };
|
|
4628
|
+
}
|
|
4629
|
+
function parseBaseline(data, source = "baseline") {
|
|
4630
|
+
const b = data;
|
|
4631
|
+
if (!b || typeof b !== "object" || b.version !== BASELINE_VERSION || !Array.isArray(b.entries)) {
|
|
4632
|
+
throw new Error(`${source}: not a craft baseline (version ${BASELINE_VERSION})`);
|
|
4633
|
+
}
|
|
4634
|
+
for (const e of b.entries) {
|
|
4635
|
+
if (typeof e?.tell !== "string" || typeof e.path !== "string" || typeof e.excerpt !== "string" || !Number.isInteger(e.count) || e.count < 1) {
|
|
4636
|
+
throw new Error(`${source}: malformed entry ${JSON.stringify(e)}`);
|
|
4637
|
+
}
|
|
4638
|
+
}
|
|
4639
|
+
return b;
|
|
4640
|
+
}
|
|
4641
|
+
function recount(report2, findings) {
|
|
4642
|
+
const byGeneration = { 1: 0, 2: 0, 3: 0 };
|
|
4643
|
+
const byTell = {};
|
|
4644
|
+
for (const f4 of findings) {
|
|
4645
|
+
byGeneration[f4.generation] += 1;
|
|
4646
|
+
byTell[f4.tell] = (byTell[f4.tell] ?? 0) + 1;
|
|
4647
|
+
}
|
|
4648
|
+
return {
|
|
4649
|
+
...report2,
|
|
4650
|
+
findings,
|
|
4651
|
+
summary: { ...report2.summary, findings: findings.length, byGeneration, byTell, blocking: findings.filter((f4) => f4.severity === "block").length }
|
|
4652
|
+
};
|
|
4653
|
+
}
|
|
4654
|
+
function applyBaseline(report2, baseline) {
|
|
4655
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
4656
|
+
for (const e of baseline.entries) {
|
|
4657
|
+
const key = keyOf(e.tell, e.path, e.excerpt);
|
|
4658
|
+
remaining.set(key, (remaining.get(key) ?? 0) + e.count);
|
|
4659
|
+
}
|
|
4660
|
+
const kept = [];
|
|
4661
|
+
let baselined = 0;
|
|
4662
|
+
for (const f4 of report2.findings) {
|
|
4663
|
+
const key = findingKey(f4);
|
|
4664
|
+
const left = remaining.get(key) ?? 0;
|
|
4665
|
+
if (left > 0) {
|
|
4666
|
+
remaining.set(key, left - 1);
|
|
4667
|
+
baselined += 1;
|
|
4668
|
+
} else kept.push(f4);
|
|
4669
|
+
}
|
|
4670
|
+
return { ...recount(report2, kept), baselined };
|
|
4671
|
+
}
|
|
4672
|
+
|
|
4673
|
+
// src/character/config.ts
|
|
4674
|
+
var DEFAULT_IGNORE = [".claude", ".agents", ".cursor"];
|
|
4675
|
+
var KEYS = /* @__PURE__ */ new Set(["$schema", "ignore", "copyPaths", "severity"]);
|
|
4676
|
+
var LEVELS = /* @__PURE__ */ new Set(["warn", "block", "off"]);
|
|
4677
|
+
var MIN_BECAUSE = 12;
|
|
4678
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4679
|
+
var isStringArray = (v) => Array.isArray(v) && v.every((s) => typeof s === "string");
|
|
4680
|
+
function parseCraftConfig(data, knownTells, source = "craft.config.json") {
|
|
4681
|
+
if (!isRecord(data)) throw new Error(`${source}: expected an object`);
|
|
4682
|
+
for (const key of Object.keys(data)) if (!KEYS.has(key)) throw new Error(`${source}: unknown key "${key}"`);
|
|
4683
|
+
const ignore = data.ignore ?? [];
|
|
4684
|
+
if (!isStringArray(ignore)) throw new Error(`${source}: "ignore" must be an array of glob strings`);
|
|
4685
|
+
if (data.copyPaths !== void 0 && !isStringArray(data.copyPaths)) throw new Error(`${source}: "copyPaths" must be an array of paths`);
|
|
4686
|
+
const severity = {};
|
|
4687
|
+
const raw = data.severity ?? {};
|
|
4688
|
+
if (!isRecord(raw)) throw new Error(`${source}: "severity" must map a tell id to { level, because }`);
|
|
4689
|
+
for (const [tell, value] of Object.entries(raw)) {
|
|
4690
|
+
if (!knownTells.has(tell)) throw new Error(`${source}: severity names no tell called "${tell}"`);
|
|
4691
|
+
if (!isRecord(value) || !LEVELS.has(value.level)) {
|
|
4692
|
+
throw new Error(`${source}: severity.${tell} needs a level of "warn", "block" or "off"`);
|
|
4693
|
+
}
|
|
4694
|
+
if (typeof value.because !== "string" || value.because.trim().length < MIN_BECAUSE) {
|
|
4695
|
+
throw new Error(`${source}: severity.${tell} needs a because of at least a sentence`);
|
|
4696
|
+
}
|
|
4697
|
+
severity[tell] = { level: value.level, because: value.because };
|
|
4698
|
+
}
|
|
4699
|
+
return { ignore, severity, ...data.copyPaths ? { copyPaths: data.copyPaths } : {} };
|
|
4700
|
+
}
|
|
4701
|
+
function globToRegExp(glob) {
|
|
4702
|
+
const pattern = glob.replace(/^\.\//, "").replace(/\/$/, "").replace(/\/\*\*$/, "");
|
|
4703
|
+
let body = "";
|
|
4704
|
+
for (let i = 0; i < pattern.length; i += 1) {
|
|
4705
|
+
const c = pattern[i];
|
|
4706
|
+
if (c === "*" && pattern[i + 1] === "*") {
|
|
4707
|
+
const slash = pattern[i + 2] === "/";
|
|
4708
|
+
body += slash ? "(?:.*/)?" : ".*";
|
|
4709
|
+
i += slash ? 2 : 1;
|
|
4710
|
+
} else if (c === "*") body += "[^/]*";
|
|
4711
|
+
else if (c === "?") body += "[^/]";
|
|
4712
|
+
else body += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
4713
|
+
}
|
|
4714
|
+
const anywhere = !pattern.includes("/");
|
|
4715
|
+
return new RegExp(`^${anywhere ? "(?:.*/)?" : ""}${body}(?:/.*)?$`);
|
|
4716
|
+
}
|
|
4717
|
+
function ignoreMatcher(patterns) {
|
|
4718
|
+
const res = patterns.map(globToRegExp);
|
|
4719
|
+
return (path) => {
|
|
4720
|
+
const p = path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4721
|
+
return res.some((re) => re.test(p));
|
|
4722
|
+
};
|
|
4723
|
+
}
|
|
4724
|
+
function severityExceptions(config) {
|
|
4725
|
+
return Object.entries(config.severity).filter(([, o]) => o.level === "off").map(([tell, o]) => ({ tell, because: `craft.config.json: ${o.because}` }));
|
|
4726
|
+
}
|
|
4727
|
+
function applySeverity(report2, config) {
|
|
4728
|
+
const findings = report2.findings.map((f4) => {
|
|
4729
|
+
const o = config.severity[f4.tell];
|
|
4730
|
+
return o && o.level !== "off" ? { ...f4, severity: o.level } : f4;
|
|
4731
|
+
});
|
|
4732
|
+
return { ...report2, findings, summary: { ...report2.summary, blocking: findings.filter((f4) => f4.severity === "block").length } };
|
|
4733
|
+
}
|
|
4734
|
+
|
|
4735
|
+
// src/character/sarif.ts
|
|
4736
|
+
var isUrl = (path) => /^[a-z][a-z0-9+.-]*:\/\//i.test(path);
|
|
4737
|
+
function toSarif(report2, options) {
|
|
4738
|
+
const byId = new Map(options.tells.map((t) => [t.id, t]));
|
|
4739
|
+
const ids = [...new Set(report2.findings.map((f4) => f4.tell))].sort();
|
|
4740
|
+
const index = new Map(ids.map((id, i) => [id, i]));
|
|
4741
|
+
const rules = ids.map((id) => {
|
|
4742
|
+
const t = byId.get(id);
|
|
4743
|
+
return {
|
|
4744
|
+
id,
|
|
4745
|
+
name: t?.name ?? id,
|
|
4746
|
+
shortDescription: { text: t?.name ?? id },
|
|
4747
|
+
...t ? { fullDescription: { text: t.why }, help: { text: t.fix } } : {}
|
|
4748
|
+
};
|
|
4749
|
+
});
|
|
4750
|
+
const results = report2.findings.map((f4) => ({
|
|
4751
|
+
ruleId: f4.tell,
|
|
4752
|
+
ruleIndex: index.get(f4.tell),
|
|
4753
|
+
level: f4.severity === "block" ? "error" : "warning",
|
|
4754
|
+
message: { text: `${f4.message}. ${f4.fix}` },
|
|
4755
|
+
locations: [
|
|
4756
|
+
{
|
|
4757
|
+
physicalLocation: {
|
|
4758
|
+
artifactLocation: { uri: isUrl(f4.path) ? f4.path : f4.path.replace(/\\/g, "/") },
|
|
4759
|
+
...f4.line > 0 ? { region: { startLine: f4.line, snippet: { text: f4.excerpt } } } : {}
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
],
|
|
4763
|
+
partialFingerprints: { "craftFinding/v1": fnv1a(findingKey(f4)) }
|
|
4764
|
+
}));
|
|
4765
|
+
return {
|
|
4766
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
4767
|
+
version: "2.1.0",
|
|
4768
|
+
runs: [
|
|
4769
|
+
{
|
|
4770
|
+
tool: {
|
|
4771
|
+
driver: {
|
|
4772
|
+
name: "craft",
|
|
4773
|
+
version: report2.catalogueVersion,
|
|
4774
|
+
informationUri: options.informationUri ?? "https://www.npmjs.com/package/@domandigital/craft",
|
|
4775
|
+
rules
|
|
4776
|
+
}
|
|
4777
|
+
},
|
|
4778
|
+
results
|
|
4779
|
+
}
|
|
4780
|
+
]
|
|
4781
|
+
};
|
|
4782
|
+
}
|
|
4783
|
+
|
|
4784
|
+
// src/character/json.ts
|
|
4785
|
+
var JSON_SCHEMA_VERSION = 1;
|
|
4786
|
+
function toJson(data) {
|
|
4787
|
+
return JSON.stringify({ schemaVersion: JSON_SCHEMA_VERSION, ...data }, null, 2);
|
|
4788
|
+
}
|
|
4789
|
+
|
|
4602
4790
|
// src/fingerprint/index.ts
|
|
4603
4791
|
var FINGERPRINT_VERSION = 1;
|
|
4604
4792
|
var CHROMATIC = 0.03;
|
|
@@ -5493,10 +5681,12 @@ export {
|
|
|
5493
5681
|
AI_PHRASES,
|
|
5494
5682
|
AI_VIOLET,
|
|
5495
5683
|
AI_WORDS,
|
|
5684
|
+
BASELINE_VERSION,
|
|
5496
5685
|
BUZZWORDS,
|
|
5497
5686
|
CATALOGUE,
|
|
5498
5687
|
CATALOGUE_VERSION,
|
|
5499
5688
|
CHOICE_KEYS,
|
|
5689
|
+
DEFAULT_IGNORE,
|
|
5500
5690
|
DENSITIES,
|
|
5501
5691
|
DENSITY_NAMES,
|
|
5502
5692
|
DIRECTION_VERSION,
|
|
@@ -5511,6 +5701,7 @@ export {
|
|
|
5511
5701
|
HOUSE,
|
|
5512
5702
|
HOUSE_BUDGET,
|
|
5513
5703
|
HOUSE_TYPE,
|
|
5704
|
+
JSON_SCHEMA_VERSION,
|
|
5514
5705
|
LIGHTNESS_CURVE,
|
|
5515
5706
|
MIN_DECIDED,
|
|
5516
5707
|
MIN_RUNS,
|
|
@@ -5541,7 +5732,9 @@ export {
|
|
|
5541
5732
|
accentFork,
|
|
5542
5733
|
addSite,
|
|
5543
5734
|
apcaContrast,
|
|
5735
|
+
applyBaseline,
|
|
5544
5736
|
applyHouseGate,
|
|
5737
|
+
applySeverity,
|
|
5545
5738
|
auditSnapshot,
|
|
5546
5739
|
catalogueTable,
|
|
5547
5740
|
characterReport,
|
|
@@ -5552,6 +5745,7 @@ export {
|
|
|
5552
5745
|
compareFacts,
|
|
5553
5746
|
compareToEstate,
|
|
5554
5747
|
craftTokens,
|
|
5748
|
+
createBaseline,
|
|
5555
5749
|
deltaEOk,
|
|
5556
5750
|
deltaEOkHex,
|
|
5557
5751
|
densityCss,
|
|
@@ -5563,6 +5757,7 @@ export {
|
|
|
5563
5757
|
extractHtml,
|
|
5564
5758
|
findClaims,
|
|
5565
5759
|
findColours,
|
|
5760
|
+
findingKey,
|
|
5566
5761
|
fingerprint,
|
|
5567
5762
|
fingerprintDistance,
|
|
5568
5763
|
fluidClamp,
|
|
@@ -5573,12 +5768,14 @@ export {
|
|
|
5573
5768
|
formatComparison,
|
|
5574
5769
|
formatHex,
|
|
5575
5770
|
formatReport,
|
|
5771
|
+
globToRegExp,
|
|
5576
5772
|
groundName,
|
|
5577
5773
|
harvest,
|
|
5578
5774
|
hexToOklch,
|
|
5579
5775
|
houseRule,
|
|
5580
5776
|
hueName,
|
|
5581
5777
|
hueSwatch,
|
|
5778
|
+
ignoreMatcher,
|
|
5582
5779
|
inP3Gamut,
|
|
5583
5780
|
inSrgbGamut,
|
|
5584
5781
|
initDirection,
|
|
@@ -5593,7 +5790,9 @@ export {
|
|
|
5593
5790
|
oklchToHex,
|
|
5594
5791
|
oklchToRgb,
|
|
5595
5792
|
paletteFromPixels,
|
|
5793
|
+
parseBaseline,
|
|
5596
5794
|
parseColour,
|
|
5795
|
+
parseCraftConfig,
|
|
5597
5796
|
parseHex,
|
|
5598
5797
|
propose,
|
|
5599
5798
|
protectedFacts,
|
|
@@ -5604,6 +5803,7 @@ export {
|
|
|
5604
5803
|
scanSource,
|
|
5605
5804
|
sectionRhythm,
|
|
5606
5805
|
semantic,
|
|
5806
|
+
severityExceptions,
|
|
5607
5807
|
shapeName,
|
|
5608
5808
|
sharedParts,
|
|
5609
5809
|
shouldAnimate,
|
|
@@ -5612,6 +5812,8 @@ export {
|
|
|
5612
5812
|
tailwindV4Theme,
|
|
5613
5813
|
tellById,
|
|
5614
5814
|
toGamut,
|
|
5815
|
+
toJson,
|
|
5816
|
+
toSarif,
|
|
5615
5817
|
typeFeatureTokens,
|
|
5616
5818
|
typicality,
|
|
5617
5819
|
validateDirection,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@domandigital/craft",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The Doman Digital house craft standard as numbers: OKLCh colour ramps that snap to existing anchors, derived semantic tokens with measured WCAG and APCA contrast, one shared motion vocabulary for CSS and JS, and a base stylesheet. Zero dependencies.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -8,21 +8,28 @@
|
|
|
8
8
|
"main": "dist/index.cjs",
|
|
9
9
|
"module": "dist/index.js",
|
|
10
10
|
"types": "dist/index.d.ts",
|
|
11
|
+
"sideEffects": [
|
|
12
|
+
"./css/*.css"
|
|
13
|
+
],
|
|
11
14
|
"bin": {
|
|
12
15
|
"craft": "dist/cli.js"
|
|
13
16
|
},
|
|
14
17
|
"exports": {
|
|
15
18
|
".": {
|
|
16
|
-
"
|
|
17
|
-
|
|
19
|
+
"import": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
18
23
|
"require": {
|
|
19
24
|
"types": "./dist/index.d.cts",
|
|
20
25
|
"default": "./dist/index.cjs"
|
|
21
26
|
}
|
|
22
27
|
},
|
|
23
28
|
"./audit": {
|
|
24
|
-
"
|
|
25
|
-
|
|
29
|
+
"import": {
|
|
30
|
+
"types": "./dist/audit.d.ts",
|
|
31
|
+
"default": "./dist/audit.js"
|
|
32
|
+
},
|
|
26
33
|
"require": {
|
|
27
34
|
"types": "./dist/audit.d.cts",
|
|
28
35
|
"default": "./dist/audit.cjs"
|
|
@@ -52,7 +59,7 @@
|
|
|
52
59
|
"vitest": "^2.1.9"
|
|
53
60
|
},
|
|
54
61
|
"engines": {
|
|
55
|
-
"node": ">=
|
|
62
|
+
"node": ">=22.12"
|
|
56
63
|
},
|
|
57
64
|
"repository": {
|
|
58
65
|
"type": "git",
|
package/dist/chunk-U2JTFQKO.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
snapshotUrl
|
|
3
|
-
} from "./chunk-TSKRBIOC.js";
|
|
4
|
-
import {
|
|
5
|
-
describeTypicality,
|
|
6
|
-
loadNull,
|
|
7
|
-
typicality
|
|
8
|
-
} from "./chunk-APO4BT2F.js";
|
|
9
|
-
import {
|
|
10
|
-
SOURCE_FILE,
|
|
11
|
-
finish,
|
|
12
|
-
loadExceptions,
|
|
13
|
-
parseFlags,
|
|
14
|
-
readPaths
|
|
15
|
-
} from "./chunk-JKIDWHG3.js";
|
|
16
|
-
import {
|
|
17
|
-
fingerprint
|
|
18
|
-
} from "./chunk-QMYERCBB.js";
|
|
19
|
-
import {
|
|
20
|
-
SNAPSHOT_VERSION,
|
|
21
|
-
auditSnapshot,
|
|
22
|
-
scanSource
|
|
23
|
-
} from "./chunk-3NGWXZZE.js";
|
|
24
|
-
|
|
25
|
-
// src/audit/cli.ts
|
|
26
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
27
|
-
import { resolve } from "path";
|
|
28
|
-
var fmt = (o) => o ? `oklch(${o.l.toFixed(3)} ${o.c.toFixed(3)} ${o.h.toFixed(1)})` : "none";
|
|
29
|
-
function describeFingerprint(fp) {
|
|
30
|
-
return [
|
|
31
|
-
"Fingerprint",
|
|
32
|
-
` accent ${fmt(fp.accent)}`,
|
|
33
|
-
` ground ${fmt(fp.ground)}`,
|
|
34
|
-
` display ${fp.display.family} (${fp.display.class})`,
|
|
35
|
-
` body ${fp.body.family} (${fp.body.class})`,
|
|
36
|
-
` roundness ${fp.roundness === null ? "no buttons" : fp.roundness.toFixed(2)}`,
|
|
37
|
-
` motion ${Math.round(fp.motion * 100)}% of sections below the fold reveal on scroll`,
|
|
38
|
-
` effects ${fp.effects.join(", ") || "none"}`,
|
|
39
|
-
` layout ${fp.layout.join(" > ") || "none"}`
|
|
40
|
-
].join("\n");
|
|
41
|
-
}
|
|
42
|
-
function merge(a, b) {
|
|
43
|
-
const byTell = { ...a.summary.byTell };
|
|
44
|
-
for (const [k, v] of Object.entries(b.summary.byTell)) byTell[k] = (byTell[k] ?? 0) + v;
|
|
45
|
-
return {
|
|
46
|
-
catalogueVersion: a.catalogueVersion,
|
|
47
|
-
findings: [...a.findings, ...b.findings],
|
|
48
|
-
rejectedExceptions: a.rejectedExceptions,
|
|
49
|
-
excepted: [...a.excepted, ...b.excepted],
|
|
50
|
-
suppressed: [...a.suppressed, ...b.suppressed],
|
|
51
|
-
summary: {
|
|
52
|
-
files: a.summary.files + b.summary.files,
|
|
53
|
-
findings: a.summary.findings + b.summary.findings,
|
|
54
|
-
byGeneration: { 1: a.summary.byGeneration[1] + b.summary.byGeneration[1], 2: a.summary.byGeneration[2] + b.summary.byGeneration[2], 3: a.summary.byGeneration[3] + b.summary.byGeneration[3] },
|
|
55
|
-
byTell,
|
|
56
|
-
blocking: a.summary.blocking + b.summary.blocking
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function readSnapshot(path) {
|
|
61
|
-
const data = JSON.parse(readFileSync(path, "utf8"));
|
|
62
|
-
if (data.version !== SNAPSHOT_VERSION) throw new Error(`${path} is snapshot version ${String(data.version)}; this craft reads ${SNAPSHOT_VERSION}`);
|
|
63
|
-
return data;
|
|
64
|
-
}
|
|
65
|
-
async function runAudit(command, args, io) {
|
|
66
|
-
try {
|
|
67
|
-
const flags = parseFlags(args);
|
|
68
|
-
if (typeof flags === "string") throw new Error(flags);
|
|
69
|
-
const target = flags.positional[0];
|
|
70
|
-
if (!target) throw new Error(`usage: craft ${command} <url${command === "audit" ? " | snapshot.json" : ""}>`);
|
|
71
|
-
const viewport = flags.width || flags.height ? { width: Number(flags.width ?? 1440), height: Number(flags.height ?? 900) } : void 0;
|
|
72
|
-
const local = resolve(io.cwd, target);
|
|
73
|
-
const snapshot = command === "audit" && /\.json$/i.test(target) && existsSync(local) ? readSnapshot(local) : await snapshotUrl(target, { viewport });
|
|
74
|
-
if (flags.out) writeFileSync(resolve(io.cwd, flags.out), `${JSON.stringify(snapshot, null, 2)}
|
|
75
|
-
`);
|
|
76
|
-
if (command === "snapshot") {
|
|
77
|
-
if (!flags.out) io.out(JSON.stringify(snapshot, null, 2));
|
|
78
|
-
else io.out(`craft snapshot: ${snapshot.url} saved to ${flags.out}`);
|
|
79
|
-
return 0;
|
|
80
|
-
}
|
|
81
|
-
const exceptions = loadExceptions(flags, flags.repo ? resolve(io.cwd, flags.repo) : io.cwd);
|
|
82
|
-
let report = auditSnapshot(snapshot, { exceptions });
|
|
83
|
-
if (flags.repo) {
|
|
84
|
-
const repo = resolve(io.cwd, flags.repo);
|
|
85
|
-
report = merge(scanSource(readPaths(["."], repo, SOURCE_FILE), { exceptions }), report);
|
|
86
|
-
}
|
|
87
|
-
const fp = fingerprint(snapshot);
|
|
88
|
-
const nulls = flags.null ? flags.null.split(",").map((p) => loadNull(p.trim(), io.cwd)) : [];
|
|
89
|
-
const typical = nulls.length ? typicality(fp, nulls.flatMap((m, i) => m.runs.map((r) => nulls.length > 1 ? { ...r, id: `${i + 1}/${r.id}` } : r))) : null;
|
|
90
|
-
if (flags.json) {
|
|
91
|
-
io.out(JSON.stringify({ report, fingerprint: fp, typicality: typical, snapshot: flags.out ?? null }, null, 2));
|
|
92
|
-
return report.summary.blocking > 0 || flags.strict && report.summary.findings > 0 ? 1 : 0;
|
|
93
|
-
}
|
|
94
|
-
const code = finish(report, { ...flags, json: false }, `craft audit ${snapshot.url}`, io);
|
|
95
|
-
io.out("");
|
|
96
|
-
io.out(describeFingerprint(fp));
|
|
97
|
-
if (typical) {
|
|
98
|
-
io.out("");
|
|
99
|
-
io.out(describeTypicality(typical, nulls.reduce((s, m) => s + m.runs.length, 0), nulls.length));
|
|
100
|
-
}
|
|
101
|
-
return code;
|
|
102
|
-
} catch (error) {
|
|
103
|
-
io.err(`craft: ${error.message}`);
|
|
104
|
-
return 2;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export {
|
|
109
|
-
describeFingerprint,
|
|
110
|
-
merge,
|
|
111
|
-
runAudit
|
|
112
|
-
};
|