@inflexa-ai/harness 0.28.0 → 0.29.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.
Files changed (87) hide show
  1. package/dist/app/data-profile-orientation.d.ts +52 -27
  2. package/dist/app/data-profile-orientation.js +331 -92
  3. package/dist/app/data-profile-view.d.ts +70 -0
  4. package/dist/app/data-profile-view.js +123 -0
  5. package/dist/app/synthesize-run.js +1 -1
  6. package/dist/contracts/data-profile.d.ts +276 -25
  7. package/dist/contracts/index.d.ts +3 -1
  8. package/dist/contracts/index.js +1 -0
  9. package/dist/contracts/profile-vocabulary.d.ts +103 -0
  10. package/dist/contracts/profile-vocabulary.js +449 -0
  11. package/dist/contracts/target-dossier.d.ts +21 -21
  12. package/dist/execution/artifact-registration.js +9 -0
  13. package/dist/execution/artifact-registry.d.ts +14 -0
  14. package/dist/execution/input-signature.d.ts +16 -3
  15. package/dist/execution/input-signature.js +0 -0
  16. package/dist/execution/post-step-pipeline.d.ts +6 -4
  17. package/dist/execution/post-step-pipeline.js +18 -9
  18. package/dist/execution/staged-input.d.ts +4 -4
  19. package/dist/index.d.ts +5 -1
  20. package/dist/index.js +7 -0
  21. package/dist/input-scan/clustering.d.ts +63 -0
  22. package/dist/input-scan/clustering.js +208 -0
  23. package/dist/input-scan/companions.d.ts +30 -0
  24. package/dist/input-scan/companions.js +138 -0
  25. package/dist/input-scan/decoder/container_readout.py +171 -0
  26. package/dist/input-scan/detect-sets.d.ts +24 -0
  27. package/dist/input-scan/detect-sets.js +279 -0
  28. package/dist/input-scan/enrich.d.ts +62 -19
  29. package/dist/input-scan/enrich.js +132 -187
  30. package/dist/input-scan/formats.d.ts +13 -5
  31. package/dist/input-scan/formats.js +13 -5
  32. package/dist/input-scan/index.d.ts +10 -4
  33. package/dist/input-scan/index.js +7 -4
  34. package/dist/input-scan/markers.d.ts +21 -0
  35. package/dist/input-scan/markers.js +89 -0
  36. package/dist/input-scan/menu.d.ts +127 -0
  37. package/dist/input-scan/menu.js +0 -0
  38. package/dist/input-scan/quarantine.d.ts +17 -0
  39. package/dist/input-scan/quarantine.js +69 -0
  40. package/dist/input-scan/readout-budget.d.ts +32 -0
  41. package/dist/input-scan/readout-budget.js +61 -0
  42. package/dist/input-scan/readout.d.ts +33 -0
  43. package/dist/input-scan/readout.js +336 -0
  44. package/dist/input-scan/scan.d.ts +5 -4
  45. package/dist/input-scan/scan.js +5 -5
  46. package/dist/input-scan/set-types.d.ts +209 -0
  47. package/dist/input-scan/set-types.js +15 -0
  48. package/dist/input-scan/templates.d.ts +93 -0
  49. package/dist/input-scan/templates.js +369 -0
  50. package/dist/input-scan/tokens.d.ts +94 -0
  51. package/dist/input-scan/tokens.js +267 -0
  52. package/dist/input-scan/tuning.d.ts +55 -0
  53. package/dist/input-scan/tuning.js +55 -0
  54. package/dist/input-scan/types.d.ts +2 -2
  55. package/dist/prompts/conversation.d.ts +1 -1
  56. package/dist/prompts/conversation.js +8 -6
  57. package/dist/prompts/report-session.d.ts +1 -1
  58. package/dist/prompts/report-session.js +3 -2
  59. package/dist/prompts/sandbox/data-profiler.d.ts +10 -1
  60. package/dist/prompts/sandbox/data-profiler.js +197 -53
  61. package/dist/prompts/sandbox-standards.d.ts +1 -1
  62. package/dist/prompts/sandbox-standards.js +10 -7
  63. package/dist/schemas/data-profile-schemas.d.ts +943 -80
  64. package/dist/schemas/data-profile-schemas.js +300 -115
  65. package/dist/state/data-profile.d.ts +1 -1
  66. package/dist/state/index.d.ts +1 -1
  67. package/dist/state/vector-store.d.ts +13 -0
  68. package/dist/state/vector-store.js +8 -0
  69. package/dist/tasks/data-profile-absorb.d.ts +78 -0
  70. package/dist/tasks/data-profile-absorb.js +285 -0
  71. package/dist/tasks/data-profile-index.d.ts +34 -25
  72. package/dist/tasks/data-profile-index.js +160 -59
  73. package/dist/tasks/data-profile-resolve.d.ts +72 -0
  74. package/dist/tasks/data-profile-resolve.js +639 -0
  75. package/dist/tasks/data-profile.d.ts +16 -5
  76. package/dist/tasks/data-profile.js +270 -131
  77. package/dist/tools/research/inspect-data-profile.d.ts +48 -22
  78. package/dist/tools/research/inspect-data-profile.js +73 -54
  79. package/dist/tools/workspace/scan-inputs.d.ts +4 -4
  80. package/dist/tools/workspace/scan-inputs.js +13 -14
  81. package/dist/tools/workspace/workspace-search.d.ts +11 -2
  82. package/dist/tools/workspace/workspace-search.js +44 -16
  83. package/dist/workflows/sandbox-step.js +36 -6
  84. package/dist/workflows/target-assessment/phase4-assemble.d.ts +21 -21
  85. package/package.json +5 -3
  86. package/dist/input-scan/coverage.d.ts +0 -22
  87. package/dist/input-scan/coverage.js +0 -85
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Stage 3 — name-template mining within one directory context.
3
+ *
4
+ * Literal versus slot is decided by per-position distinct counts: a position whose
5
+ * token is the same in every member is literal text, and one that varies is a slot.
6
+ * Groups that differ in exactly one position, or only in extension, merge — variance
7
+ * inside a set is a property of the set, never a partition key.
8
+ *
9
+ * Proliferation is bounded three ways, because a template per file is the same failure
10
+ * as no templates at all: a prefix trie claims residual singletons that share literal
11
+ * text, a family collapse claims singletons that share only a token-kind sequence
12
+ * (one template for the family costs less than one template each), and a catch-all
13
+ * claims the rest where the repetition across directories is itself the structure.
14
+ */
15
+ import { DATE_SYMBOL, DIGIT_SYMBOL, ID_SYMBOL, classifyValues, commonAffixes, isDateToken, maskAffix, skeletonOf, tokenizeName } from "./tokens.js";
16
+ import { MAX_SLOT_SAMPLE_VALUES, MIN_AFFIX_LENGTH, MIN_CATCH_ALL_MEMBERS, MIN_FAMILY_SET_MEMBERS, MIN_PREFIX_SET_MEMBERS } from "./tuning.js";
17
+ function toItem(file) {
18
+ const { tokens, suffix } = tokenizeName(file.name);
19
+ return { file, tokens, suffix, skeleton: skeletonOf(tokens) };
20
+ }
21
+ function newGroup(kind, seed) {
22
+ return { kind, tokenKinds: [], prefixTokens: [], suffixes: new Map(), items: [], ...seed };
23
+ }
24
+ function firstPass(items) {
25
+ const groups = new Map();
26
+ for (const item of items) {
27
+ const key = `${item.skeleton} ${item.suffix}`;
28
+ let group = groups.get(key);
29
+ if (!group) {
30
+ group = newGroup("tokens", { tokenKinds: item.tokens.map((token) => token.kind), suffix: item.suffix });
31
+ groups.set(key, group);
32
+ }
33
+ group.items.push(item);
34
+ group.suffixes.set(item.suffix, (group.suffixes.get(item.suffix) ?? 0) + 1);
35
+ }
36
+ return [...groups.values()];
37
+ }
38
+ function distinctCounts(group) {
39
+ if (!group.distinct) {
40
+ group.distinct = group.tokenKinds.map((_, position) => {
41
+ const seen = new Set();
42
+ for (const item of group.items)
43
+ seen.add(item.tokens[position]?.value ?? "");
44
+ return seen.size;
45
+ });
46
+ }
47
+ return group.distinct;
48
+ }
49
+ function isVariable(group, position) {
50
+ if (group.tokenKinds[position] === "id")
51
+ return true;
52
+ return distinctCounts(group)[position] > 1;
53
+ }
54
+ function mergeGroups(groups) {
55
+ const head = groups[0];
56
+ const suffixes = new Map();
57
+ const items = [];
58
+ for (const group of groups) {
59
+ items.push(...group.items);
60
+ for (const [suffix, count] of group.suffixes)
61
+ suffixes.set(suffix, (suffixes.get(suffix) ?? 0) + count);
62
+ }
63
+ return { kind: head.kind, tokenKinds: head.tokenKinds, prefixTokens: head.prefixTokens, suffix: head.suffix, suffixes, items };
64
+ }
65
+ /**
66
+ * Merge groups differing in exactly one alphabetic or numeric position to a fixpoint,
67
+ * then fold groups whose stems agree once their slots are wildcarded.
68
+ *
69
+ * Without the first pass a categorical position reads as unrelated groups, and the
70
+ * agent is handed the very split it exists to decide against. Without the second,
71
+ * members that differ only in compression become two sets that a reader has to notice
72
+ * are one.
73
+ */
74
+ function mergeVariants(groups) {
75
+ let current = [...groups];
76
+ for (;;) {
77
+ const families = new Map();
78
+ for (const group of current) {
79
+ const key = `${group.tokenKinds.join(",")} ${group.suffix}`;
80
+ const bucket = families.get(key);
81
+ if (bucket)
82
+ bucket.push(group);
83
+ else
84
+ families.set(key, [group]);
85
+ }
86
+ const next = [];
87
+ let merged = false;
88
+ for (const family of families.values()) {
89
+ if (family.length === 1) {
90
+ next.push(family[0]);
91
+ continue;
92
+ }
93
+ const positions = family[0].tokenKinds.length;
94
+ let mergedHere = false;
95
+ for (let p = 0; p < positions && !mergedHere; p++) {
96
+ const kind = family[0].tokenKinds[p];
97
+ if (kind !== "alpha" && kind !== "digit")
98
+ continue;
99
+ const buckets = new Map();
100
+ for (const group of family) {
101
+ // Every member of a group shares its literal tokens, so the first row
102
+ // identifies the whole group at this position.
103
+ const row = group.items[0].tokens;
104
+ const key = row.map((token, i) => (i === p || isVariable(group, i) ? "" : token.value)).join(" ");
105
+ const bucket = buckets.get(key);
106
+ if (bucket)
107
+ bucket.push(group);
108
+ else
109
+ buckets.set(key, [group]);
110
+ }
111
+ if ([...buckets.values()].every((bucket) => bucket.length === 1))
112
+ continue;
113
+ for (const bucket of buckets.values())
114
+ next.push(bucket.length === 1 ? bucket[0] : mergeGroups(bucket));
115
+ mergedHere = true;
116
+ merged = true;
117
+ }
118
+ if (!mergedHere)
119
+ next.push(...family);
120
+ }
121
+ current = next;
122
+ if (!merged)
123
+ break;
124
+ }
125
+ const byStem = new Map();
126
+ for (const group of current) {
127
+ const row = group.items[0].tokens;
128
+ const key = `${group.tokenKinds.join(",")} ${row.map((token, i) => (isVariable(group, i) ? "" : token.value)).join(" ")}`;
129
+ const bucket = byStem.get(key);
130
+ if (bucket)
131
+ bucket.push(group);
132
+ else
133
+ byStem.set(key, [group]);
134
+ }
135
+ return [...byStem.values()].map((bucket) => (bucket.length === 1 ? bucket[0] : mergeGroups(bucket)));
136
+ }
137
+ function symbolOf(token) {
138
+ if (token.kind === "id")
139
+ return ID_SYMBOL;
140
+ if (token.kind === "digit")
141
+ return DIGIT_SYMBOL;
142
+ if (token.kind === "date")
143
+ return DATE_SYMBOL;
144
+ return token.value;
145
+ }
146
+ /**
147
+ * Claim residual singletons by shared literal prefix, deepest node first.
148
+ *
149
+ * Children are claimed before their parent, so a specific prefix wins over the general
150
+ * one it extends, and a prefix earns a set only once it covers enough files to be
151
+ * cheaper than the singletons it replaces.
152
+ */
153
+ function minePrefixes(residue) {
154
+ const bySuffix = new Map();
155
+ for (const item of residue) {
156
+ const bucket = bySuffix.get(item.suffix);
157
+ if (bucket)
158
+ bucket.push(item);
159
+ else
160
+ bySuffix.set(item.suffix, [item]);
161
+ }
162
+ const sets = [];
163
+ const rest = [];
164
+ for (const [suffix, items] of bySuffix) {
165
+ if (items.length < MIN_PREFIX_SET_MEMBERS) {
166
+ rest.push(...items);
167
+ continue;
168
+ }
169
+ const root = { children: new Map(), items: [] };
170
+ for (const item of items) {
171
+ let node = root;
172
+ node.items.push(item);
173
+ for (const token of item.tokens) {
174
+ const key = symbolOf(token);
175
+ let child = node.children.get(key);
176
+ if (!child) {
177
+ child = { children: new Map(), items: [], token };
178
+ node.children.set(key, child);
179
+ }
180
+ child.items.push(item);
181
+ node = child;
182
+ }
183
+ }
184
+ const taken = new Set();
185
+ const claim = (node, prefixTokens) => {
186
+ for (const child of node.children.values())
187
+ claim(child, [...prefixTokens, child.token]);
188
+ const free = node.items.filter((item) => !taken.has(item));
189
+ const literals = prefixTokens.filter((token) => token.kind === "alpha").length;
190
+ if (free.length < MIN_PREFIX_SET_MEMBERS || literals < 1)
191
+ return;
192
+ for (const item of free)
193
+ taken.add(item);
194
+ const suffixes = new Map([[suffix, free.length]]);
195
+ sets.push(newGroup("prefix", { prefixTokens, suffix, suffixes, items: free }));
196
+ };
197
+ claim(root, []);
198
+ rest.push(...items.filter((item) => !taken.has(item)));
199
+ }
200
+ return { sets, rest };
201
+ }
202
+ /**
203
+ * Collapse singletons that share a token-kind sequence into one set.
204
+ *
205
+ * A flat directory of differently-named tables has one shape even though no two names
206
+ * differ in a single position. One template for the family is a shorter description
207
+ * than one template each, which is the only reason to prefer it — so it applies only
208
+ * once the family is large enough to pay for the generality it gives up.
209
+ */
210
+ function collapseFamilies(residue) {
211
+ const families = new Map();
212
+ for (const item of residue) {
213
+ const key = `${item.tokens.map((token) => token.kind).join(",")} ${item.suffix}`;
214
+ const bucket = families.get(key);
215
+ if (bucket)
216
+ bucket.push(item);
217
+ else
218
+ families.set(key, [item]);
219
+ }
220
+ const sets = [];
221
+ const rest = [];
222
+ for (const items of families.values()) {
223
+ if (items.length < MIN_FAMILY_SET_MEMBERS) {
224
+ rest.push(...items);
225
+ continue;
226
+ }
227
+ const head = items[0];
228
+ sets.push(newGroup("family", {
229
+ tokenKinds: head.tokens.map((token) => token.kind),
230
+ suffix: head.suffix,
231
+ suffixes: new Map([[head.suffix, items.length]]),
232
+ items: [...items],
233
+ }));
234
+ }
235
+ return { sets, rest };
236
+ }
237
+ /**
238
+ * Fold what is left of a cross-directory context into one set.
239
+ *
240
+ * Where the same directory shape repeats, that repetition is the structure, and the
241
+ * residual names inside it are one set whatever they are called. Extension
242
+ * disagreement is carried as the format census, never used to split.
243
+ */
244
+ export function catchAll(residue, hasDirectorySlot) {
245
+ if (!hasDirectorySlot || residue.length < MIN_CATCH_ALL_MEMBERS)
246
+ return { sets: [], rest: [...residue] };
247
+ const suffixes = new Map();
248
+ for (const item of residue)
249
+ suffixes.set(item.suffix, (suffixes.get(item.suffix) ?? 0) + 1);
250
+ return {
251
+ sets: [newGroup("catch-all", { suffix: [...suffixes.keys()].sort((a, b) => a.localeCompare(b, "en")).join(","), suffixes, items: [...residue] })],
252
+ rest: [],
253
+ };
254
+ }
255
+ /** Mine one directory context: every file whose directory path instantiates one template. */
256
+ export function mineContext(files) {
257
+ const groups = mergeVariants(firstPass(files.map(toItem)));
258
+ const sets = [];
259
+ const singletons = [];
260
+ for (const group of groups) {
261
+ if (group.items.length >= 2)
262
+ sets.push(group);
263
+ else
264
+ singletons.push(group.items[0]);
265
+ }
266
+ const prefixes = minePrefixes(singletons);
267
+ sets.push(...prefixes.sets);
268
+ const families = collapseFamilies(prefixes.rest);
269
+ sets.push(...families.sets);
270
+ return { sets, residue: families.rest };
271
+ }
272
+ /** Describe one varying position: its class, its cardinality, and a bounded value sample. */
273
+ export function describeSlot(rawValues, location, index) {
274
+ const distinct = [...new Set(rawValues)];
275
+ let { prefix, suffix } = commonAffixes(distinct);
276
+ if (prefix.length < MIN_AFFIX_LENGTH)
277
+ prefix = "";
278
+ if (suffix.length < MIN_AFFIX_LENGTH)
279
+ suffix = "";
280
+ // A number and a date are each one value: slicing a shared year off them would
281
+ // report a date as a literal prefix plus a pattern.
282
+ if (distinct.every((value) => /^[0-9]*$/.test(value)) || distinct.every((value) => isDateToken(value))) {
283
+ prefix = "";
284
+ suffix = "";
285
+ }
286
+ const strip = (value) => value.slice(prefix.length, suffix ? value.length - suffix.length : undefined);
287
+ const cores = distinct.map(strip);
288
+ const observed = classifyValues(cores);
289
+ const sorted = [...cores].sort((a, b) => a.localeCompare(b, "en"));
290
+ return {
291
+ location,
292
+ index,
293
+ tokenClass: observed.tokenClass,
294
+ ...(observed.width !== undefined ? { width: observed.width } : {}),
295
+ ...(observed.minLength !== undefined ? { minLength: observed.minLength, maxLength: observed.maxLength } : {}),
296
+ ...(observed.skeleton !== undefined ? { skeleton: observed.skeleton } : {}),
297
+ ...(prefix ? { prefix } : {}),
298
+ ...(suffix ? { suffix } : {}),
299
+ distinctValues: distinct.length,
300
+ sampleValues: sorted.slice(0, MAX_SLOT_SAMPLE_VALUES),
301
+ values: cores,
302
+ memberValues: rawValues.map(strip),
303
+ };
304
+ }
305
+ /** The template token a slot renders as. */
306
+ export function slotToken(slot) {
307
+ switch (slot.tokenClass) {
308
+ case "digits-fixed":
309
+ return `<digits:${slot.width}>`;
310
+ case "pattern":
311
+ return `<pattern:${slot.skeleton}>`;
312
+ default:
313
+ return `<${slot.tokenClass}>`;
314
+ }
315
+ }
316
+ function affixText(slot, affix) {
317
+ return slot.tokenClass === "opaque-id" ? maskAffix(affix) : affix;
318
+ }
319
+ function suffixLiteral(group) {
320
+ if (group.suffixes.size <= 1)
321
+ return group.suffix ? `.${group.suffix}` : "";
322
+ const suffixes = [...group.suffixes.keys()].sort((a, b) => a.localeCompare(b, "en"));
323
+ return `.{${suffixes.join(",")}}`;
324
+ }
325
+ /**
326
+ * The name half of a set's template: its literal text, its slots, and the order they
327
+ * appear in. Directory slots are prepended by the assembly, which knows the context.
328
+ */
329
+ export function describeGroup(group) {
330
+ const slots = [];
331
+ const segments = [];
332
+ const pushSlot = (slot) => {
333
+ if (slot.prefix)
334
+ segments.push({ kind: "literal", text: affixText(slot, slot.prefix) });
335
+ segments.push({ kind: "slot", slot: slots.length });
336
+ if (slot.suffix)
337
+ segments.push({ kind: "literal", text: affixText(slot, slot.suffix) });
338
+ slots.push(slot);
339
+ };
340
+ if (group.kind === "catch-all") {
341
+ pushSlot(describeSlot(group.items.map((item) => item.file.name), "name", 0));
342
+ return { slots, segments };
343
+ }
344
+ if (group.kind === "prefix") {
345
+ segments.push({ kind: "literal", text: group.prefixTokens.map((token) => token.value).join("") });
346
+ const rests = group.items.map((item) => item.tokens
347
+ .slice(group.prefixTokens.length)
348
+ .map((token) => token.value)
349
+ .join(""));
350
+ pushSlot(describeSlot(rests, "name", group.prefixTokens.length));
351
+ const literal = suffixLiteral(group);
352
+ if (literal)
353
+ segments.push({ kind: "literal", text: literal });
354
+ return { slots, segments };
355
+ }
356
+ for (let i = 0; i < group.tokenKinds.length; i++) {
357
+ const values = group.items.map((item) => item.tokens[i]?.value ?? "");
358
+ const distinct = new Set(values);
359
+ if (distinct.size === 1 && group.tokenKinds[i] !== "id") {
360
+ segments.push({ kind: "literal", text: [...distinct][0] });
361
+ continue;
362
+ }
363
+ pushSlot(describeSlot(values, "name", i));
364
+ }
365
+ const literal = suffixLiteral(group);
366
+ if (literal)
367
+ segments.push({ kind: "literal", text: literal });
368
+ return { slots, segments };
369
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Tokenisation and the token-class lattice the detected-set pipeline mines over.
3
+ *
4
+ * A name splits on the boundaries that carry structure — the dotted suffix chain, and
5
+ * runs of letters, digits, and separators inside each dot-part — except where a run is
6
+ * one indivisible token. A machine-issued opaque identifier and a delimited date are
7
+ * each one token, because splitting them yields positions whose values never repeat,
8
+ * and a position whose values never repeat fragments the template space into one
9
+ * template per file.
10
+ *
11
+ * The lattice orders classes by specificity: a value set is described by the most
12
+ * specific class every one of its values satisfies.
13
+ */
14
+ export type TokenKind = "alpha" | "digit" | "delim" | "id" | "date";
15
+ export interface NameToken {
16
+ readonly kind: TokenKind;
17
+ readonly value: string;
18
+ }
19
+ export declare const ID_SYMBOL = "ID";
20
+ export declare const DIGIT_SYMBOL = "#";
21
+ export declare const DATE_SYMBOL = "@";
22
+ export declare function basenameOf(path: string): string;
23
+ export declare function dirnameOf(path: string): string;
24
+ /**
25
+ * Split a basename into its stem and the terminal chain of recognised extensions.
26
+ *
27
+ * The stem may itself contain dots: only a trailing run of known extension tokens is
28
+ * the extension, so the categorical tokens of a dotted suffix chain stay minable.
29
+ */
30
+ export declare function splitStem(fileName: string): {
31
+ stem: string;
32
+ suffix: string;
33
+ };
34
+ /**
35
+ * A machine-issued identifier: a UUID, a fixed-length hex digest, or a long
36
+ * class-mixed base64url token that is structurally random.
37
+ *
38
+ * Randomness shows either as case flipping inside letter runs or as digit
39
+ * fragmentation, and either signal alone suffices — a letter-heavy random token has
40
+ * few digit runs, and a digit-heavy one has few letters to flip. Separator density
41
+ * vetoes the second signal only: snake_case is how a person writes a name, and a
42
+ * person's name is not an identifier however many digits it carries.
43
+ */
44
+ export declare function isOpaqueId(value: string): boolean;
45
+ /** A calendar date written compactly or with a single separator: `20260824`, `2026-08-24`. */
46
+ export declare function isDateToken(value: string): boolean;
47
+ /**
48
+ * Tokenise one path segment — a directory name, or one dot-part of a stem.
49
+ *
50
+ * A maximal base64url-charset run is tested whole before it is split, which is what
51
+ * lets a long identifier collapse to one position where masking digit runs cannot. A
52
+ * date embedded in a longer run is lifted out whole for the same reason: split into
53
+ * year, month, and day it becomes three positions that vary together, and the template
54
+ * space grows by the product rather than by one.
55
+ */
56
+ export declare function tokenizeSegment(segment: string): NameToken[];
57
+ /** Tokenise a full basename. Dot-parts of the stem are joined by explicit delimiter tokens. */
58
+ export declare function tokenizeName(fileName: string): {
59
+ tokens: NameToken[];
60
+ suffix: string;
61
+ };
62
+ /** The masked form of a token run: variable material becomes its class symbol. */
63
+ export declare function skeletonOf(tokens: readonly NameToken[]): string;
64
+ export declare function maskSegment(segment: string): string;
65
+ export type SlotTokenClass = "constant" | "date" | "digits-fixed" | "digits" | "hex" | "opaque-id" | "word" | "pattern" | "any";
66
+ export interface ValueClass {
67
+ readonly tokenClass: SlotTokenClass;
68
+ /** Digit width, when every value is a fixed-width number. */
69
+ readonly width?: number;
70
+ readonly minLength?: number;
71
+ readonly maxLength?: number;
72
+ /** The shared masked form, when the class is `pattern`. */
73
+ readonly skeleton?: string;
74
+ }
75
+ /** Place a set of observed values on the lattice, most specific class first. */
76
+ export declare function classifyValues(values: readonly string[]): ValueClass;
77
+ /**
78
+ * The longest prefix and suffix shared by every value.
79
+ *
80
+ * A greedy identifier run swallows the literal text fused to it; recovering the shared
81
+ * affixes puts that text back in the template where a reader can see it.
82
+ */
83
+ export declare function commonAffixes(values: readonly string[]): {
84
+ prefix: string;
85
+ suffix: string;
86
+ };
87
+ /**
88
+ * Mask class-mixed runs inside a literal affix of an identifier position.
89
+ *
90
+ * The affix was recovered by comparing identifier values, so a run that looks like
91
+ * identifier material almost certainly is: it reached the template only because every
92
+ * member happened to share it.
93
+ */
94
+ export declare function maskAffix(affix: string): string;