carveout 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.
Files changed (78) hide show
  1. package/AGENTS.md +21 -3
  2. package/NOTICE.md +2 -1
  3. package/README.md +274 -460
  4. package/dist/src/account.d.ts +37 -0
  5. package/dist/src/account.js +275 -0
  6. package/dist/src/api.d.ts +148 -0
  7. package/dist/src/api.js +120 -0
  8. package/dist/src/carve.d.ts +788 -0
  9. package/dist/src/carve.js +1721 -125
  10. package/dist/src/cli.d.ts +10 -0
  11. package/dist/src/cli.js +674 -47
  12. package/dist/src/command.d.ts +10 -0
  13. package/dist/src/core.d.ts +11 -0
  14. package/dist/src/cut.d.ts +78 -0
  15. package/dist/src/cut.js +25 -7
  16. package/dist/src/extract/compiler-config.d.ts +49 -0
  17. package/dist/src/extract/fixture.d.ts +103 -0
  18. package/dist/src/extract/hash.d.ts +6 -0
  19. package/dist/src/extract/package-registry.d.ts +47 -0
  20. package/dist/src/extract/package-types.d.ts +52 -0
  21. package/dist/src/extract/surface.d.ts +44 -0
  22. package/dist/src/extract/typescript.d.ts +180 -0
  23. package/dist/src/gate.d.ts +55 -0
  24. package/dist/src/gate.js +1 -0
  25. package/dist/src/graph.d.ts +142 -0
  26. package/dist/src/graph.js +4 -3
  27. package/dist/src/impact.d.ts +113 -0
  28. package/dist/src/imports.d.ts +67 -0
  29. package/dist/src/mergeback.d.ts +182 -0
  30. package/dist/src/mergeback.js +580 -35
  31. package/dist/src/platform.d.ts +69 -0
  32. package/dist/src/platform.js +184 -0
  33. package/dist/src/policy.d.ts +181 -0
  34. package/dist/src/policy.js +716 -230
  35. package/dist/src/project/build.d.ts +23 -0
  36. package/dist/src/project/catalog.d.ts +25 -0
  37. package/dist/src/project/project.d.ts +287 -0
  38. package/dist/src/project/project.js +479 -52
  39. package/dist/src/project/provenance.d.ts +73 -0
  40. package/dist/src/project/provenance.js +12 -0
  41. package/dist/src/project/runner.d.ts +19 -0
  42. package/dist/src/project/secrets.d.ts +64 -0
  43. package/dist/src/project/vitest-config.d.ts +70 -0
  44. package/dist/src/project/vitest-config.js +291 -0
  45. package/dist/src/pseudonyms/aliases.d.ts +107 -0
  46. package/dist/src/pseudonyms/aliases.js +394 -0
  47. package/dist/src/pseudonyms/draft.d.ts +64 -0
  48. package/dist/src/pseudonyms/draft.js +284 -0
  49. package/dist/src/pseudonyms/folder.d.ts +172 -0
  50. package/dist/src/pseudonyms/folder.js +527 -0
  51. package/dist/src/pseudonyms/index.d.ts +11 -0
  52. package/dist/src/pseudonyms/index.js +11 -0
  53. package/dist/src/pseudonyms/vocabulary.d.ts +113 -0
  54. package/dist/src/pseudonyms/vocabulary.js +522 -0
  55. package/dist/src/readiness.d.ts +71 -0
  56. package/dist/src/readiness.js +131 -0
  57. package/dist/src/receipt.d.ts +154 -0
  58. package/dist/src/receipt.js +296 -0
  59. package/dist/src/sandbox.d.ts +53 -0
  60. package/dist/src/sandbox.js +154 -0
  61. package/dist/src/slice/analysis.d.ts +75 -0
  62. package/dist/src/slice/externals.d.ts +51 -0
  63. package/dist/src/slice/graph.d.ts +69 -0
  64. package/dist/src/slice/manifest.d.ts +262 -0
  65. package/dist/src/slice/manifest.js +4 -3
  66. package/dist/src/slice/reach.d.ts +204 -0
  67. package/dist/src/slice/reach.js +11 -5
  68. package/dist/src/slice/report.d.ts +49 -0
  69. package/dist/src/slice/report.js +5 -2
  70. package/dist/src/specifiers.d.ts +48 -0
  71. package/dist/src/status.d.ts +46 -0
  72. package/dist/src/status.js +3 -2
  73. package/dist/src/test-integrity.d.ts +54 -0
  74. package/dist/src/test-integrity.js +812 -0
  75. package/package.json +24 -5
  76. package/skills/carveout/SKILL.md +517 -0
  77. package/skills/carveout-gate/SKILL.md +98 -0
  78. package/SKILL.md +0 -423
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Pseudonyms drafted by rule: the name table a sliced version needs to keep
3
+ * the workspace's vocabulary out of its public files, written without a
4
+ * model wherever the vocabulary is where rules can find it.
5
+ *
6
+ * An import of one of the workspace's own packages (`@acme/db`) resolves to
7
+ * a module the slice copies or stubs, so it becomes the relative path the
8
+ * generator would give it, scoped to the files that import it: a renamed
9
+ * package would no longer resolve. Every other occurrence of a vocabulary
10
+ * term becomes a placeholder of the same case, one placeholder per term
11
+ * that no input already uses: a path segment by a path rule, an identifier
12
+ * that contains it renamed whole (`acmeOrders` becomes `globexOrders`), and
13
+ * anything else as text, held in place by the characters around it. One
14
+ * identifier rule covers each spelling up to case; a second spelling that
15
+ * differs from it only by case (`LUNOX` beside `Lunox`) is text too, and
16
+ * several casings may each get a text rule of their own, since a text rule
17
+ * matches and inverts its exact characters.
18
+ *
19
+ * The draft is a candidate, not a guarantee: the caller checks it as the
20
+ * build applies it, and one that does not hold is where the pseudonyms agent
21
+ * starts. Only the vocabulary is covered; a name that says whose code it is
22
+ * without being in it (a customer, a product's feature) is the agent's to
23
+ * find.
24
+ */
25
+ import { renameFile } from "./aliases.js";
26
+ import { relativeSpecifier } from "../project/project.js";
27
+ import { termMatches } from "./vocabulary.js";
28
+ /**
29
+ * Placeholders, in the order tried: a term takes the first that no input
30
+ * contains, in any case.
31
+ */
32
+ export const PSEUDONYM_PLACEHOLDERS = [
33
+ // Made-up words first: a word the code or a contributor uses for what it
34
+ // is (the ACME protocol, `acme-client`, an "Acme Corp" in test data) must
35
+ // never be read back as a hidden name.
36
+ "zorvex",
37
+ "velmira",
38
+ "quarnell",
39
+ "brixdor",
40
+ "tovlane",
41
+ "munquist",
42
+ "kelbrook",
43
+ "drovane",
44
+ "acme",
45
+ "globex",
46
+ "initech",
47
+ "hooli",
48
+ "vandelay",
49
+ "contoso",
50
+ "fabrikam",
51
+ "northwind",
52
+ "tailspin",
53
+ "wingtip",
54
+ "cyberdyne",
55
+ "tyrell",
56
+ ];
57
+ /**
58
+ * Every placeholder in the order tried: the fictional companies first, then
59
+ * made-up words any source is unlikely to hold, so a corpus that holds the
60
+ * companies' names (a pseudonym tool of its own) still gets one per term.
61
+ */
62
+ function* placeholderCandidates() {
63
+ yield* PSEUDONYM_PLACEHOLDERS;
64
+ const onsets = ["zor", "vel", "quar", "brix", "tov", "mun", "kel", "dro", "fen", "sav"];
65
+ const codas = ["vex", "lane", "dor", "mira", "tune", "quist", "brook", "nell"];
66
+ for (const onset of onsets)
67
+ for (const coda of codas)
68
+ yield `${onset}${coda}`;
69
+ }
70
+ /** The generator's words as a list, for `find`. */
71
+ const placeholderList = [...placeholderCandidates()];
72
+ const IDENTIFIER_CHAR = /[A-Za-z0-9_$]/;
73
+ const IDENTIFIER_START = /^[A-Za-z_$]/;
74
+ const IDENTIFIER_RUN = /^[A-Za-z0-9_$]+$/;
75
+ const isIdentifierChar = (char) => char !== undefined && IDENTIFIER_CHAR.test(char);
76
+ /** `word` in the case `surface` is written in: `ACME`, `Acme` or `acme`. */
77
+ function styled(word, surface) {
78
+ if (!/\p{Ll}/u.test(surface))
79
+ return word.toUpperCase();
80
+ if (/^\P{L}*\p{Lu}/u.test(surface))
81
+ return `${word.charAt(0).toUpperCase()}${word.slice(1)}`;
82
+ return word;
83
+ }
84
+ /**
85
+ * The words a multi-word term's placeholder adds after its own: a term of
86
+ * two words (`sandbox-factory`) becomes two (`contoso-labs`), so every
87
+ * spelling of it (`sandboxFactory`, `SANDBOX_FACTORY`, `sandbox factory`)
88
+ * has a spelling of its own, and maps back.
89
+ */
90
+ const PLACEHOLDER_TAILS = ["labs", "group", "works", "systems", "partners", "ventures"];
91
+ /** A term's placeholder as words: its base, and a tail word for each word more the term has. */
92
+ export function placeholderWords(term, base) {
93
+ const count = term.split("-").filter(Boolean).length;
94
+ return [base, ...PLACEHOLDER_TAILS.slice(0, Math.max(0, count - 1))];
95
+ }
96
+ /**
97
+ * `words` spelled as `surface` spells its own: each word in the case of the
98
+ * surface's word in its place, joined as they are (`contosoLabs` for
99
+ * `sandboxFactory`, `CONTOSO_LABS` for `SANDBOX_FACTORY`, `contoso-labs`
100
+ * for `sandbox-factory`). A surface whose words run together with no mark
101
+ * between them (`sandboxfactory`) gets the words run together too.
102
+ */
103
+ export function restyle(words, surface) {
104
+ if (words.length <= 1)
105
+ return styled(words[0] ?? "", surface);
106
+ const segments = [];
107
+ const joins = [];
108
+ surface.split(/([\s_\-./]+)/).forEach((part, index) => {
109
+ if (index % 2 === 1) {
110
+ joins.push(part);
111
+ return;
112
+ }
113
+ const pieces = part
114
+ .replace(/(\p{Ll}|\p{N})(\p{Lu})/gu, "$1\u0000$2")
115
+ .replace(/(\p{Lu})(\p{Lu}\p{Ll})/gu, "$1\u0000$2")
116
+ .split("\u0000")
117
+ .filter(Boolean);
118
+ pieces.forEach((piece, at) => {
119
+ if (at > 0)
120
+ joins.push("");
121
+ segments.push(piece);
122
+ });
123
+ });
124
+ if (segments.length === words.length)
125
+ return words.map((word, index) => `${index === 0 ? "" : (joins[index - 1] ?? "")}${styled(word, segments[index] ?? surface)}`).join("");
126
+ const upper = !/\p{Ll}/u.test(surface);
127
+ return words.map((word, index) => (index === 0 ? styled(word, surface) : upper ? word.toUpperCase() : word)).join("");
128
+ }
129
+ /** A pseudo-file's leaves (`base#pointer`) have no path to rename. */
130
+ const isRealPath = (path) => !path.includes("#");
131
+ export function draftPseudonyms(input) {
132
+ // Longer terms first, so `lunox-work` is renamed whole before `lunox`.
133
+ const terms = [...input.vocabulary].sort((a, b) => b.length - a.length || (a < b ? -1 : a > b ? 1 : 0));
134
+ if (terms.length === 0)
135
+ return [];
136
+ const matchesIn = (text) => {
137
+ const found = [];
138
+ for (const term of terms)
139
+ for (const match of termMatches(text, term))
140
+ if (!found.some((other) => match.start < other.end && other.start < match.end))
141
+ found.push({ ...match, term });
142
+ return found.sort((a, b) => a.start - b.start);
143
+ };
144
+ // One placeholder per term, found in no input.
145
+ const corpus = input.files
146
+ .map((file) => `${file.path}\n${file.text}`)
147
+ .join("\n")
148
+ .toLowerCase();
149
+ const placeholders = new Map();
150
+ const placeholderOf = (term) => {
151
+ const chosen = placeholders.get(term);
152
+ if (chosen !== undefined)
153
+ return chosen;
154
+ const taken = new Set(placeholders.values());
155
+ const earlier = input.preferred?.get(term);
156
+ if (earlier !== undefined && !taken.has(earlier) && !corpus.includes(earlier) && matchesIn(earlier).length === 0) {
157
+ placeholders.set(term, earlier);
158
+ return earlier;
159
+ }
160
+ // A word an earlier cut gave another term is not given to this one.
161
+ const reserved = new Set(input.preferred?.values() ?? []);
162
+ const word = placeholderList.find((candidate) => !taken.has(candidate) &&
163
+ !reserved.has(candidate) &&
164
+ !corpus.includes(candidate) &&
165
+ matchesIn(candidate).length === 0);
166
+ if (word !== undefined)
167
+ placeholders.set(term, word);
168
+ return word ?? null;
169
+ };
170
+ /** `text` with each term in it replaced, or null when one has no placeholder. */
171
+ const replaced = (text) => {
172
+ let out = "";
173
+ let at = 0;
174
+ for (const match of matchesIn(text)) {
175
+ const word = placeholderOf(match.term);
176
+ if (word === null)
177
+ return null;
178
+ out += text.slice(at, match.start);
179
+ out += restyle(placeholderWords(match.term, word), text.slice(match.start, match.end));
180
+ at = match.end;
181
+ }
182
+ return out + text.slice(at);
183
+ };
184
+ // Paths: each segment that carries a term, renamed from the root down.
185
+ const pathRules = [];
186
+ for (const file of input.files) {
187
+ if (!isRealPath(file.path))
188
+ continue;
189
+ const segments = renameFile({ path: file.path, text: "" }, pathRules).path.split("/");
190
+ for (const [index, segment] of segments.entries()) {
191
+ const after = replaced(segment);
192
+ if (after === null || after === segment)
193
+ continue;
194
+ const before = segments.slice(0, index + 1).join("/");
195
+ segments[index] = after;
196
+ if (!pathRules.some((rule) => rule.before === before))
197
+ pathRules.push({
198
+ before,
199
+ after: segments.slice(0, index + 1).join("/"),
200
+ kind: "path",
201
+ paths: [],
202
+ });
203
+ }
204
+ }
205
+ const aliasedPath = (path) => renameFile({ path, text: "" }, pathRules).path;
206
+ // Imports of the workspace's packages, as the relative path to the module:
207
+ // one rule per literal and path, scoped to the files that import it so.
208
+ const importers = new Map();
209
+ const byPath = new Map(input.files.map((file) => [file.path, file]));
210
+ for (const edge of input.imports) {
211
+ if (edge.specifier.startsWith(".") || edge.specifier.startsWith("/"))
212
+ continue;
213
+ if (matchesIn(edge.specifier).length === 0)
214
+ continue;
215
+ const text = byPath.get(edge.from)?.text;
216
+ if (text === undefined)
217
+ continue;
218
+ const to = relativeSpecifier(aliasedPath(edge.from), aliasedPath(edge.to));
219
+ for (const quote of ['"', "'"]) {
220
+ const before = `${quote}${edge.specifier}${quote}`;
221
+ if (!text.includes(before))
222
+ continue;
223
+ const after = `${quote}${to}${quote}`;
224
+ const key = `${before}\n${after}`;
225
+ const entry = importers.get(key) ?? { before, after, paths: new Set() };
226
+ entry.paths.add(edge.from);
227
+ importers.set(key, entry);
228
+ }
229
+ }
230
+ const importRules = [...importers.values()].map((entry) => ({
231
+ before: entry.before,
232
+ after: entry.after,
233
+ kind: "text",
234
+ paths: [...entry.paths].sort(),
235
+ }));
236
+ // What is left: each occurrence, as the identifier it is in or as text.
237
+ const settled = [...importRules, ...pathRules];
238
+ const identifiers = new Map();
239
+ const texts = new Map();
240
+ const foldedIdentifiers = new Set();
241
+ for (const file of input.files) {
242
+ const { text } = renameFile(file, settled);
243
+ for (const match of matchesIn(text)) {
244
+ const surface = text.slice(match.start, match.end);
245
+ let start = match.start;
246
+ let end = match.end;
247
+ if (IDENTIFIER_RUN.test(surface)) {
248
+ while (isIdentifierChar(text[start - 1]))
249
+ start -= 1;
250
+ while (isIdentifierChar(text[end]))
251
+ end += 1;
252
+ const token = text.slice(start, end);
253
+ const after = replaced(token);
254
+ if (after === null || after === token)
255
+ continue;
256
+ if (identifiers.has(token))
257
+ continue;
258
+ if (IDENTIFIER_START.test(token) &&
259
+ !foldedIdentifiers.has(token.toLowerCase())) {
260
+ foldedIdentifiers.add(token.toLowerCase());
261
+ identifiers.set(token, {
262
+ before: token,
263
+ after,
264
+ kind: "identifier",
265
+ paths: [],
266
+ });
267
+ continue;
268
+ }
269
+ }
270
+ // Text, held in place by the characters around it, so it renames
271
+ // neither another spelling nor the inside of an identifier.
272
+ const from = Math.max(0, start - 1);
273
+ const to = Math.min(text.length, end + 1);
274
+ const before = `${isIdentifierChar(text[from]) ? "" : text.slice(from, start)}${text.slice(start, end)}${isIdentifierChar(text[end]) ? "" : text.slice(end, to)}`;
275
+ const after = replaced(before);
276
+ if (after === null || after === before || texts.has(before))
277
+ continue;
278
+ texts.set(before, { before, after, kind: "text", paths: [] });
279
+ }
280
+ }
281
+ for (const [term, word] of placeholders)
282
+ input.placeholders?.set(term, placeholderWords(term, word).join("-"));
283
+ return [...settled, ...identifiers.values(), ...texts.values()];
284
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * A public folder in pseudonyms, and the way back.
3
+ *
4
+ * A public cut writes the slice for someone who must not learn whose code
5
+ * it is. The names that say so (the organization, its handle and the
6
+ * account that owns the repository, the repository's and the workspace's
7
+ * package names, the maintainer's own name and address) are found by
8
+ * `workspaceNames`, and every file the folder holds is written under a
9
+ * table of renames that hides them: drafted by rule over the folder's own
10
+ * text (`draftPseudonyms`), after any renames the owner wrote, and proved
11
+ * to map back byte for byte (`applyAliases`). A name still in the folder
12
+ * after the table is a `vocabulary_leaked` blocker.
13
+ *
14
+ * The table never leaves the maintainer: it is kept in the cut's receipt.
15
+ * A folder that comes back is renamed back, path and text, into a scratch
16
+ * copy at the repository's own paths (`unaliasFolder`), and the patch is
17
+ * made from that copy as from any folder: the maintainer reads, and the
18
+ * hand-off policy judges, the change in the repository's own names.
19
+ *
20
+ * A package the folder installs from the public registry keeps its name,
21
+ * which npm needs: its occurrences are masked from the table both ways,
22
+ * and named to the maintainer when they carry a term (`kept`).
23
+ */
24
+ import { type AliasRule, type AliasedFile } from "./aliases.js";
25
+ import { type VocabularyLeak, type VocabularyName } from "./vocabulary.js";
26
+ /** What the receipt keeps of a public folder's pseudonyms. */
27
+ export interface FolderPseudonyms {
28
+ /** The table, in the order it was applied: the owner's rules first. */
29
+ readonly rules: readonly AliasRule[];
30
+ /** Package names the folder installs and keeps, which carry a term. */
31
+ readonly kept: readonly string[];
32
+ /** The terms the folder was scanned for. */
33
+ readonly vocabulary: readonly string[];
34
+ /**
35
+ * The placeholder each term was given. A placeholder occurs nowhere in
36
+ * the repository's own text, so one the folder's holder wrote (a new
37
+ * `AcmeDiscount`, a new `acme-totals.ts`) maps back to its term.
38
+ */
39
+ readonly words?: readonly {
40
+ readonly term: string;
41
+ readonly placeholder: string;
42
+ }[];
43
+ }
44
+ /**
45
+ * The names a repository goes by, for a public folder to hide: the owner's
46
+ * (`names` in the configuration, `--name`), the root's and the slice's
47
+ * packages' names, scopes, authors and repository URLs, the account and
48
+ * repository its `origin` remote names, the checkout's folder name, and the
49
+ * maintainer's own Git name and address. `workspaceVocabulary` drops what
50
+ * is too short or too generic to say whose code it is.
51
+ */
52
+ export declare function workspaceNames(repo: string, packageDirs: readonly string[], owner?: readonly string[]): VocabularyName[];
53
+ /**
54
+ * The names written after a name the repository goes by, the way its
55
+ * owner's other repositories are (`lunox-work/sandbox-factory` in a default
56
+ * list, a URL, a comment): each is the owner's too, and goes in the
57
+ * vocabulary. Only a name of two words or more (`sandbox-factory`): one
58
+ * word after a slash is as often a folder or a file (`lunox/README`), and
59
+ * a common word renamed everywhere hides nothing.
60
+ */
61
+ export declare function ownedNames(texts: readonly {
62
+ readonly text: string;
63
+ }[], vocabulary: readonly string[]): VocabularyName[];
64
+ /**
65
+ * `text` with each kept name (longest first) behind a mask the table cannot
66
+ * match, where it is written as a package: in quotes, alone or before a
67
+ * subpath (`"@lunox/ui"`, `'@lunox/ui/button'`). Elsewhere it is a word
68
+ * like any other, renamed and scanned.
69
+ */
70
+ export declare function maskKept(text: string, kept: readonly string[]): string;
71
+ /** `maskKept` undone. */
72
+ export declare function unmaskKept(text: string, kept: readonly string[]): string;
73
+ /** The kept names among the packages: those that carry one of the terms. */
74
+ export declare function keptNames(packages: readonly string[], vocabulary: readonly string[]): string[];
75
+ /** A folder's files in pseudonyms, or why the table could not be made. */
76
+ export type Pseudonymized = {
77
+ readonly ok: true;
78
+ readonly table: FolderPseudonyms;
79
+ /** The text files as the folder holds them, by repository path. */
80
+ readonly files: ReadonlyMap<string, AliasedFile>;
81
+ /** Each repository path's path in the folder, binaries included. */
82
+ readonly pathOf: (path: string) => string;
83
+ /** What the table left: a term still in a path or a text. */
84
+ readonly leaks: readonly VocabularyLeak[];
85
+ } | {
86
+ readonly ok: false;
87
+ readonly problems: readonly string[];
88
+ };
89
+ /**
90
+ * The folder's files under a table that hides `vocabulary`: the owner's
91
+ * rules, then rules drafted over what they leave. `files` are the text
92
+ * files at their repository paths, `binaries` the paths of the rest (only
93
+ * their paths are renamed). Fails, writing nothing, when the table does
94
+ * not map back byte for byte.
95
+ */
96
+ export declare function pseudonymize(input: {
97
+ readonly files: readonly AliasedFile[];
98
+ readonly binaries: readonly string[];
99
+ readonly vocabulary: readonly string[];
100
+ readonly owner: readonly AliasRule[];
101
+ readonly kept: readonly string[];
102
+ /** An earlier cut's pseudonyms, whose placeholders are kept where they fit. */
103
+ readonly earlier?: FolderPseudonyms;
104
+ /**
105
+ * The files a patch will read back (the slice's own): each must come
106
+ * back through `fromFolder` byte for byte, as `patch` reads it, or a
107
+ * patch of a folder no one edited would carry a change.
108
+ */
109
+ readonly readBack?: (path: string) => boolean;
110
+ }): Pseudonymized;
111
+ /**
112
+ * Each term found in the files by a looser reading than the table's: inside
113
+ * a longer word (`getlunox`, `lunoxes`), whatever joins its words, in any
114
+ * case, its accents stripped, and in binary files' bytes. The table renames
115
+ * a name on its word's edges; a name it could not reach still says whose
116
+ * code it is, and blocks the folder.
117
+ */
118
+ export declare function looseLeaks(files: readonly {
119
+ readonly path: string;
120
+ readonly text: string;
121
+ }[], vocabulary: readonly string[], kept?: readonly string[]): VocabularyLeak[];
122
+ /**
123
+ * Each term the table's rules left in a path or a text, given its
124
+ * placeholder, spelled as the term was (`AcmeDiscount` → `ZorvexDiscount`,
125
+ * `acme-cases-v2.ts` → `zorvex-cases-v2.ts`): what the cut never saw, as
126
+ * `apply` writes it. The rules match what the cut found, file by file; a
127
+ * file the repository added since, or a line it wrote, may spell a term
128
+ * where no rule reaches. `fromFolder` puts each back by the same words. A
129
+ * kept package's name stays as it is, and a term the cut gave no
130
+ * placeholder (it found none) is left for the leak scan to name.
131
+ */
132
+ export declare function wordsToFolder(table: FolderPseudonyms, text: string): string;
133
+ /** The package names a text spells as module specifiers (`from "x"`, `import("x")`), relative ones aside. */
134
+ export declare function specifierPackages(text: string): string[];
135
+ /**
136
+ * A path or a text in the folder's names that maps back byte for byte: the
137
+ * table's rules with a placeholder for each term they left
138
+ * (`wordsToFolder`) when `fromFolder` takes that back to what it was, else
139
+ * the rules alone when those do; null when neither does (a mixed-case term
140
+ * `restyle` cannot keep, a package name the restore shields, a placeholder
141
+ * word the repository's own text holds). What `apply` writes, and only
142
+ * that: a text that maps back wrong would have `patch` rewrite the
143
+ * repository's.
144
+ */
145
+ export declare function exactToFolder(table: FolderPseudonyms, path: string, text: string, ownPackage?: (name: string) => boolean, repositoryPackage?: (name: string) => boolean): {
146
+ readonly path: string;
147
+ readonly text: string;
148
+ } | null;
149
+ /** A path or text in the repository's names, put in the folder's. */
150
+ export declare function toFolder(table: FolderPseudonyms, path: string, text: string): AliasedFile;
151
+ /**
152
+ * A path or text in the folder's names, put back in the repository's: the
153
+ * table undone, then any placeholder still there (a name the folder's
154
+ * holder made) put back as its term. A package the contributor imports by
155
+ * its own name (`acme-client`) is shielded from both, unless it is one of
156
+ * the folder's own mock packages (`ownPackage`), whose names are the
157
+ * table's.
158
+ */
159
+ export declare function fromFolder(table: FolderPseudonyms, path: string, text: string, ownPackage?: (name: string) => boolean,
160
+ /**
161
+ * A package the repository knows (a workspace package, a dependency it
162
+ * declares): a specifier the table's rules take back to one is the
163
+ * repository's own name the cut renamed (in an import, in a test's
164
+ * data), and goes back with the rest.
165
+ */
166
+ repositoryPackage?: (name: string) => boolean): AliasedFile;
167
+ /**
168
+ * A record's string leaves in the folder's names, its hashes and ids left
169
+ * as they are (a term made of hex letters could match inside one): the
170
+ * folder's own `carveout.json`, which its own commands read.
171
+ */
172
+ export declare function recordToFolder<T>(table: FolderPseudonyms, value: T): T;