gitsieve 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 John Yoon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,362 @@
1
+ # gitsieve
2
+
3
+ Audit a private repository's **entire git history** before you open-source it,
4
+ and get a go/no-go.
5
+
6
+ It scans every commit on every ref — file contents, path names, commit
7
+ messages, author and committer identities, and ref names — against a list of
8
+ words you supply: your organisation's name, your products, your clients. Then
9
+ it exits non-zero if it finds any of them.
10
+
11
+ The feature that matters most is the one that sounds like overhead: **it proves
12
+ it can find something before it tells you it found nothing.**
13
+
14
+ ```
15
+ $ gitsieve --repo . --terms ~/private/terms.json
16
+
17
+ SCANNER VERIFICATION
18
+ ok contents "createReadStream" 1 match(es), derived
19
+ ok paths "decisions" 1 match(es), derived
20
+ ok messages "verification" 1 match(es), derived
21
+ ok identities "someone@example.com" 1 match(es), derived
22
+ ok refs "heads" 1 match(es), derived
23
+
24
+ FINDINGS (3 across 2 term/channel pairs)
25
+ [term] client-name -- paths -- 2 match(es)
26
+ vendor/<redacted>/adapter.ts
27
+ [term] client-name -- identities -- 1 match(es)
28
+ commit 8f31a04
29
+
30
+ RESULT: NO-GO -- 3 finding(s). Do not publish this repository.
31
+ $ echo $?
32
+ 1
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Why this exists
38
+
39
+ This tool is not a hypothetical. Earlier this year, publishing a private
40
+ repository as open source went wrong in three distinct ways, and every rule in
41
+ here comes from one of them.
42
+
43
+ **1. The confidentiality check ran against a `--depth 1` clone.**
44
+ A shallow clone holds one commit of one branch. `git grep` over it is a
45
+ perfectly correct command answering a question nobody asked, and it reported
46
+ clean.
47
+
48
+ **2. A full-history scan later found what was actually there.**
49
+ 37 files under a directory named after an employer's product, roughly 17,900
50
+ content matches, employer package names, and commits authored from an employer
51
+ email address. Every current file was clean. The history was not, and history
52
+ is what `git push` sends.
53
+
54
+ **3. Worse: a second scan of the *current* files also returned zero, falsely.**
55
+ In that shell `grep` was not `/usr/bin/grep`. It was a shell function shimming
56
+ [ugrep](https://github.com/Genivia/ugrep) with `--ignore-files`, so it honoured
57
+ `.gitignore`. The repository's `.gitignore` was a deny-everything whitelist —
58
+ `*` followed by a handful of `!allowed` lines — so the scanner silently skipped
59
+ almost every file and exited 0. `git grep` over the same directory returned
60
+ three matches.
61
+
62
+ The employer's product name was public for about fifteen minutes.
63
+
64
+ None of those three runs produced an error. Two of them produced an empty
65
+ result set, which is exactly what a person about to publish wants to see.
66
+
67
+ ---
68
+
69
+ ## What it checks
70
+
71
+ ### Five channels
72
+
73
+ The worst finding in the real incident was a **directory name**, and the second
74
+ worst was an **email address in an author field**. A scanner that only reads
75
+ file contents sees neither.
76
+
77
+ | channel | what it is | why it is in the list |
78
+ | ------------ | --------------------------------------- | -------------------------------------------------------------- |
79
+ | `contents` | every text blob in every commit | the obvious one, and the only one most tools do |
80
+ | `paths` | every path that ever existed | `vendor/<client-name>/…` leaks the client with no file content |
81
+ | `messages` | every commit message, and tag messages | "integrate with <product> v2" ships with the repo |
82
+ | `identities` | every author and committer name/email | `you@employer.example` in `%ae` on every commit you ever made |
83
+ | `refs` | every branch and tag name | `feature/<client>-migration` is on the GitHub branch dropdown |
84
+
85
+ ### Scanner verification (the important one)
86
+
87
+ Before printing a result, the tool derives a control string per channel from
88
+ the repository itself — a real word from a real file, a real path segment, a
89
+ real author address — and pushes each through the *same* matcher a real term
90
+ uses. Any control that matches zero times means that channel is not being read,
91
+ and the whole run is reported as **not trustworthy**, exit code 2, with the
92
+ finding count explicitly disclaimed.
93
+
94
+ You can add your own controls to the terms file: strings you know are in the
95
+ repository. They are checked identically.
96
+
97
+ This is not a flag. A verification you can turn off is a verification that will
98
+ be off in the run that matters. Rationale and evidence:
99
+ [docs/decisions/0002](docs/decisions/0002-control-strings-are-mandatory.md).
100
+
101
+ ### Preflight
102
+
103
+ - **Shallow clone** → blocker. `git rev-parse --is-shallow-repository`. There is
104
+ no scan that can rescue a corpus of one commit. `--allow-shallow` downgrades
105
+ it to a warning if you know what you are doing.
106
+ - **Single-branch clone** → warning, when `remote.origin.fetch` does not cover
107
+ `refs/heads/*`. Branches outside that refspec were never fetched.
108
+ - **Deny-all `.gitignore`** → warning. A `*` + `!allowed` whitelist is a normal
109
+ thing to write and an auditing hazard: every tool that honours ignore files
110
+ will skip most of the repository and report it clean. `gitsieve` itself
111
+ reads objects from git, not files from disk, so it is unaffected — but the
112
+ next person to run ripgrep in that directory will be.
113
+
114
+ ### Non-ASCII terms
115
+
116
+ Terms are literal strings, never regexes, and word boundaries are applied only
117
+ when the term itself starts and ends with an ASCII word character.
118
+
119
+ `\b` in a regex is defined against `[A-Za-z0-9_]`. It never fires next to a
120
+ Hangul syllable, a kana, or a Han character. A term list built on
121
+ `\b<term>\b` therefore matches **nothing at all** for exactly the terms most
122
+ likely to be the organisation's real internal vocabulary. In the incident this
123
+ tool comes from, the terms included Korean spellings of the same names.
124
+
125
+ ```ts
126
+ // tests/matcher.test.ts
127
+ findMatches(compileTerm({ id: "ko", value: "노르헤이븐" }).regex, "노르헤이븐 연동 메모");
128
+ // => 1 match
129
+
130
+ findMatches(compileTerm({ id: "ko", value: "노르헤이븐", wholeWord: "always" }).regex,
131
+ "노르헤이븐 연동 메모");
132
+ // => 0 matches — the bug this design avoids
133
+ ```
134
+
135
+ ### Secret patterns
136
+
137
+ A short list of prefix-anchored credential patterns (AWS key ids, GitHub
138
+ tokens, PEM private key blocks, Slack, Stripe, Google API keys, npm tokens,
139
+ JWTs) runs as a **secondary** check. It is not the headline. See below.
140
+
141
+ ---
142
+
143
+ ## "gitleaks and trufflehog exist. Why this?"
144
+
145
+ They are better than this tool at what they do, and you should run one of them
146
+ too. They are not substitutes for each other.
147
+
148
+ **gitleaks and trufflehog find secrets.** Keys, tokens, credentials — things
149
+ with a recognisable shape, often verified live against the issuing service.
150
+ Hundreds of rules, years of tuning. `gitsieve` ships nine patterns and no
151
+ verification, and will not catch what they catch.
152
+
153
+ **They do not find organisation-owned vocabulary,** because it has no shape. A
154
+ product name is a word. A client name is a word. No entropy heuristic and no
155
+ regex library knows that one particular ordinary-looking noun is the one you
156
+ are contractually forbidden from publishing. Only you know that, which is why
157
+ this tool's core input is a list *you* write.
158
+
159
+ Concretely, against the real incident:
160
+
161
+ | what actually leaked | a secret scanner finds it? |
162
+ | ------------------------------------------------ | -------------------------- |
163
+ | a directory named after the employer's product | no — it is a path, not a secret, and most secret scanners scan blob contents |
164
+ | an employer email address in the commit author field | no — `%ae` is not part of the scanned corpus |
165
+ | the product name in 17,900 lines of past commits | no — it is an ordinary word with no entropy signature |
166
+ | Korean spellings of the same names | no — non-ASCII names are not in any rule set |
167
+ | an API key | **yes** — but there wasn't one |
168
+
169
+ The leak was not an API key. It was a client's name in a directory path and an
170
+ employer's address in an author field.
171
+
172
+ **And neither of them tells you when they read nothing.** That is the specific
173
+ failure that put a product name in public: a scanner that skipped almost every
174
+ file, exited 0, and printed nothing. Every tool in this category is one shell
175
+ alias away from that. This one refuses to report a zero it has not just
176
+ verified.
177
+
178
+ Run gitleaks for secrets. Run this for the words only you know are dangerous,
179
+ and for the reassurance that the scan happened at all.
180
+
181
+ ---
182
+
183
+ ## False positives, and the suppression list
184
+
185
+ **When a term is also a common word.** It happens: an organisation called Delta
186
+ or Nova or Prism will match the geometry code, the CSS, and the changelog.
187
+ There is no clever fix, so the tool does three unclever things instead:
188
+
189
+ 1. **Word boundaries by default for ASCII terms.** `Zephyr` matches
190
+ `vendor/Zephyr-adapter/x.ts` and does not match `Zephyrline`.
191
+ `wholeWord: "never"` widens it, `"always"` narrows it.
192
+ 2. **`channels` per term.** A term that is only dangerous in a path
193
+ (`{ "value": "Delta", "channels": ["paths", "refs"] }`) stops generating
194
+ noise from prose.
195
+ 3. **`suppress` entries, each with a mandatory `reason`.** A suppression
196
+ without a stated reason is rejected by the config parser. An unexplained
197
+ hole in an audit is worse than a noisy audit.
198
+
199
+ ```json
200
+ {
201
+ "suppress": [
202
+ {
203
+ "termId": "delta",
204
+ "channel": "contents",
205
+ "pathPrefix": "src/geometry/",
206
+ "reason": "delta as in difference; unrelated to the organisation name"
207
+ }
208
+ ]
209
+ }
210
+ ```
211
+
212
+ **How the suppression list avoids becoming a leak vector.** Two rules:
213
+
214
+ - **Suppressions reference a term by `id`, never by value.** Passing a `value`
215
+ or `term` field is a hard config error, with that reason in the message. A
216
+ suppression file that repeated the sensitive string would be the leak it was
217
+ written to prevent.
218
+ - **The terms file itself must live outside the repository being scanned.**
219
+ The tool refuses a `--terms` path inside the repo and exits 3. That file is a
220
+ list of exactly the strings you are trying not to publish; committing it into
221
+ the repository you are about to publish is the whole problem in miniature.
222
+ `--allow-terms-in-repo` exists for terms that are genuinely not sensitive.
223
+
224
+ So a repository can carry `gitsieve.config.json` with its suppressions and
225
+ its `id`s, and the file that maps those ids to real words stays in your home
226
+ directory, your password manager, or your CI secrets.
227
+
228
+ **Reports redact by default.** Findings print the term id and location, not the
229
+ matched text. `--show-matches` reveals context for terms; secret matches are
230
+ never printed, with or without the flag. A report pasted into a ticket or a CI
231
+ log must not become the leak.
232
+
233
+ **Known false negatives**, stated plainly:
234
+
235
+ - An ASCII term next to an underscore. `Zephyrline` does not match
236
+ `ZEPHYRLINE_QUEUE_URL`, because `_` is a word character and the boundary does
237
+ not fire. Use `wholeWord: "never"` if that matters to you.
238
+ - Terms split across lines, obfuscated, or base64-encoded in history.
239
+ - Content inside binary blobs, which are skipped after a NUL-byte check, and
240
+ blobs over `--max-blob-bytes` (2 MiB), which are reported as a warning rather
241
+ than skipped silently.
242
+ - A term that is not on your list. This tool cannot tell you what you forgot.
243
+
244
+ ---
245
+
246
+ ## Install and use
247
+
248
+ ```bash
249
+ git clone https://github.com/sparkYJO1/gitsieve
250
+ cd gitsieve
251
+ npm install && npm run build
252
+ node dist/cli.js --repo /path/to/repo --terms ~/private/terms.json
253
+ ```
254
+
255
+ Node 20+. No runtime dependencies — it shells out to `git` and nothing else.
256
+
257
+ ### Terms file
258
+
259
+ ```json
260
+ {
261
+ "terms": [
262
+ { "id": "org", "value": "Norhaven" },
263
+ { "id": "org-ko", "value": "노르헤이븐" },
264
+ { "id": "product", "value": "Zephyrline", "channels": ["contents", "paths", "refs"] },
265
+ { "id": "employer-mail", "value": "zephyrline-internal.example" },
266
+ "AnotherName"
267
+ ],
268
+ "controls": [
269
+ { "value": "a string you know is in this repository", "channel": "contents" }
270
+ ],
271
+ "suppress": [
272
+ { "termId": "org", "channel": "contents", "pathPrefix": "docs/", "reason": "…" }
273
+ ],
274
+ "secrets": true
275
+ }
276
+ ```
277
+
278
+ A bare string is shorthand for `{ "id": s, "value": s }`. See
279
+ [`gitsieve.config.example.json`](gitsieve.config.example.json).
280
+
281
+ ### Options
282
+
283
+ ```
284
+ --repo <path> repository to audit (default: .)
285
+ --terms <file.json> terms, suppressions and controls
286
+ --json machine-readable report on stdout
287
+ --show-matches print matched text instead of a redaction placeholder
288
+ --allow-shallow scan a shallow clone anyway, as a warning not a blocker
289
+ --allow-terms-in-repo permit a terms file stored inside the audited repo
290
+ --no-reflog scan refs only, ignoring the reflog
291
+ --no-secrets skip the secondary credential patterns
292
+ --max-blob-bytes <n> do not read blobs larger than this (default 2097152)
293
+ ```
294
+
295
+ ### Exit codes
296
+
297
+ | code | meaning |
298
+ | ---- | ------------------------------------------------------------------ |
299
+ | 0 | go — controls verified and no findings |
300
+ | 1 | no-go — findings |
301
+ | 2 | no-go — the scan could not be trusted; the number it printed means nothing |
302
+ | 3 | usage or configuration error |
303
+
304
+ `0` and `2` are the pair that matters. Most tools collapse them.
305
+
306
+ ---
307
+
308
+ ## What this does not do
309
+
310
+ - **It does not rewrite history.** It tells you where a finding is. Removing it
311
+ means `git filter-repo`, or publishing a fresh repository with no history.
312
+ - **It is not a secret scanner.** Nine patterns, no live verification. Run
313
+ gitleaks or trufflehog alongside it.
314
+ - **It does not read submodules, LFS objects, or `.git/objects` that no ref or
315
+ reflog entry reaches.** Unreachable objects are not pushed, but they are in a
316
+ `.git` directory you hand over by copying.
317
+ - **It does not know your terms.** Everything depends on the list you write.
318
+ It cannot tell you the word you forgot, and a clean report against a short
319
+ list means very little.
320
+ - **It does not scan GitHub.** Issues, pull request titles, release notes,
321
+ wiki, Actions logs and repository description are all outside a git
322
+ repository and all publishable. Check them by hand.
323
+ - **It does not detect a leak that is a paraphrase.** A sentence that
324
+ identifies an organisation without naming it -- "our largest customer's
325
+ internal platform" -- is not a string match.
326
+ - **It is not legal advice about what you may publish.**
327
+
328
+ ---
329
+
330
+ ## Development
331
+
332
+ ```bash
333
+ npm run typecheck
334
+ npm test # 46 tests; each builds real git repositories in a temp dir
335
+ npm run selfcheck # build, then audit this repository with itself
336
+ ```
337
+
338
+ The tests do not mock git. Every fixture is a real repository with planted
339
+ findings across history, branches, path names, commit messages and author
340
+ fields, and the suite asserts each one is found. It also includes a test that
341
+ hands the scanner a deliberately broken git — one whose object reader silently
342
+ returns nothing, reproducing the shimmed-`grep` failure — and proves the
343
+ control check fails loudly instead of reporting the repository clean.
344
+
345
+ Every term in the test fixtures is invented. Nothing in this repository names a
346
+ real employer, client or product, which is the failure it exists to catch.
347
+
348
+ ---
349
+
350
+ ## Provenance
351
+
352
+ The incident is real and the decisions are mine. The code was written with
353
+ [Claude](https://claude.com/claude-code) against those decisions, and I
354
+ reviewed every line of it. The two architecture decision records in
355
+ [`docs/decisions/`](docs/decisions/) carry the reasoning and the terminal
356
+ transcripts behind the two choices that were not obvious — including an
357
+ approach that looked right, produced a plausible result, and turned out to miss
358
+ the exact category of finding that caused the incident.
359
+
360
+ ## Licence
361
+
362
+ MIT. See [LICENSE](LICENSE).
package/dist/cli.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ export interface CliArgs {
3
+ repo: string;
4
+ terms?: string;
5
+ json: boolean;
6
+ showMatches: boolean;
7
+ allowShallow: boolean;
8
+ allowTermsInRepo: boolean;
9
+ reflog: boolean;
10
+ secrets: boolean;
11
+ maxBlobBytes?: number;
12
+ help: boolean;
13
+ }
14
+ export declare function parseArgs(argv: string[]): CliArgs;
15
+ export declare function main(argv: string[], out?: NodeJS.WriteStream & {
16
+ fd: 1;
17
+ }, err?: NodeJS.WriteStream & {
18
+ fd: 2;
19
+ }): number;
package/dist/cli.js ADDED
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+ import { realpathSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { assertTermsFileOutsideRepo, ConfigError, EMPTY_CONFIG, loadConfig, } from "./config.js";
6
+ import { GitError, RealGitRepo } from "./git.js";
7
+ import { exitCodeFor, formatJson, formatReport, EXIT_USAGE } from "./report.js";
8
+ import { scan } from "./scan.js";
9
+ const USAGE = `gitsieve -- audit a repository's entire history before open-sourcing it
10
+
11
+ gitsieve [options]
12
+
13
+ Options
14
+ --repo <path> repository to audit (default: .)
15
+ --terms <file.json> terms, suppressions and controls (see README)
16
+ --json machine-readable report on stdout
17
+ --show-matches print matched text instead of a redaction placeholder
18
+ (never applies to secret matches)
19
+ --allow-shallow scan a shallow clone anyway, as a warning not a blocker
20
+ --allow-terms-in-repo permit a terms file stored inside the audited repo
21
+ --no-reflog scan refs only, ignoring the reflog
22
+ --no-secrets skip the secondary credential patterns
23
+ --max-blob-bytes <n> do not read blobs larger than this (default 2097152)
24
+ -h, --help this text
25
+
26
+ Exit codes
27
+ 0 go controls verified, no findings
28
+ 1 no-go findings
29
+ 2 no-go the scan could not be trusted; the number it printed means nothing
30
+ 3 usage error
31
+ `;
32
+ export function parseArgs(argv) {
33
+ const args = {
34
+ repo: ".",
35
+ json: false,
36
+ showMatches: false,
37
+ allowShallow: false,
38
+ allowTermsInRepo: false,
39
+ reflog: true,
40
+ secrets: true,
41
+ help: false,
42
+ };
43
+ const need = (flag, value) => {
44
+ if (value === undefined)
45
+ throw new ConfigError(`${flag} needs a value`);
46
+ return value;
47
+ };
48
+ for (let i = 0; i < argv.length; i++) {
49
+ const flag = argv[i];
50
+ switch (flag) {
51
+ case "--repo":
52
+ args.repo = need(flag, argv[++i]);
53
+ break;
54
+ case "--terms":
55
+ args.terms = need(flag, argv[++i]);
56
+ break;
57
+ case "--json":
58
+ args.json = true;
59
+ break;
60
+ case "--show-matches":
61
+ args.showMatches = true;
62
+ break;
63
+ case "--allow-shallow":
64
+ args.allowShallow = true;
65
+ break;
66
+ case "--allow-terms-in-repo":
67
+ args.allowTermsInRepo = true;
68
+ break;
69
+ case "--no-reflog":
70
+ args.reflog = false;
71
+ break;
72
+ case "--no-secrets":
73
+ args.secrets = false;
74
+ break;
75
+ case "--max-blob-bytes":
76
+ args.maxBlobBytes = Number(need(flag, argv[++i]));
77
+ if (!Number.isFinite(args.maxBlobBytes) || args.maxBlobBytes <= 0) {
78
+ throw new ConfigError("--max-blob-bytes must be a positive number");
79
+ }
80
+ break;
81
+ case "-h":
82
+ case "--help":
83
+ args.help = true;
84
+ break;
85
+ default:
86
+ throw new ConfigError(`unknown option ${flag}`);
87
+ }
88
+ }
89
+ return args;
90
+ }
91
+ export function main(argv, out = process.stdout, err = process.stderr) {
92
+ let args;
93
+ try {
94
+ args = parseArgs(argv);
95
+ }
96
+ catch (error) {
97
+ err.write(`${error.message}\n\n${USAGE}`);
98
+ return EXIT_USAGE;
99
+ }
100
+ if (args.help) {
101
+ out.write(USAGE);
102
+ return 0;
103
+ }
104
+ const repoRoot = resolve(args.repo);
105
+ try {
106
+ let config = EMPTY_CONFIG;
107
+ if (args.terms) {
108
+ if (!args.allowTermsInRepo)
109
+ assertTermsFileOutsideRepo(args.terms, repoRoot);
110
+ config = loadConfig(args.terms);
111
+ }
112
+ if (!args.secrets)
113
+ config = { ...config, secrets: false };
114
+ const repo = new RealGitRepo(repoRoot, { includeReflog: args.reflog });
115
+ const report = scan({
116
+ repo,
117
+ config,
118
+ allowShallow: args.allowShallow,
119
+ showMatches: args.showMatches,
120
+ ...(args.maxBlobBytes ? { maxBlobBytes: args.maxBlobBytes } : {}),
121
+ });
122
+ out.write(args.json ? formatJson(report) : formatReport(report));
123
+ return exitCodeFor(report);
124
+ }
125
+ catch (error) {
126
+ if (error instanceof ConfigError || error instanceof GitError) {
127
+ err.write(`${error.message}\n`);
128
+ return EXIT_USAGE;
129
+ }
130
+ throw error;
131
+ }
132
+ }
133
+ function invokedDirectly() {
134
+ const entry = process.argv[1];
135
+ if (!entry)
136
+ return false;
137
+ try {
138
+ return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ }
144
+ if (invokedDirectly()) {
145
+ process.exitCode = main(process.argv.slice(2));
146
+ }
147
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Config } from "./types.js";
2
+ export declare class ConfigError extends Error {
3
+ }
4
+ /**
5
+ * The terms file lists the exact strings you are trying to keep out of public
6
+ * view. Committing it into the repository being published would publish them.
7
+ * Keep it outside the repo, or pass --allow-terms-in-repo and accept that.
8
+ */
9
+ export declare function assertTermsFileOutsideRepo(configPath: string, repoRoot: string): void;
10
+ export declare function parseConfig(json: unknown): Config;
11
+ export declare function loadConfig(path: string): Config;
12
+ export declare const EMPTY_CONFIG: Config;