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
package/README.md CHANGED
@@ -1,401 +1,241 @@
1
- # carveout
2
-
3
- Hand one part of a closed TypeScript repository to an outside contractor,
4
- and take their work back, without showing them the rest.
5
-
6
- You name a file or a folder. carveout copies it, and what it imports up to
7
- a depth you choose, into a new folder at the same paths. Every module it
8
- imports from the rest of the repository becomes a `.d.ts` stub with a
9
- recording mock behind it, so the folder installs, builds and tests on its
10
- own: the contractor gets the slice's source and the declarations of what it
11
- uses, never the bodies behind them. When the folder comes back,
12
- `carveout patch` turns the edits into a patch for the repository, runs none
13
- of the contractor's code, and keeps out what they should not slip in: CI
14
- and hook files, manifests and lockfiles, deleted or weakened tests, new
15
- files in other packages.
1
+ <h1 align="center">carveout</h1>
2
+
3
+ <p align="center">
4
+ <strong>Open-source a feature, not your codebase.</strong><br>
5
+ Let a contractor, an outside contributor or an AI agent work on your closed repository.<br>
6
+ They get one folder that builds and tests on its own. Their work comes back as a patch that runs none of their code.
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/carveout"><img src="https://img.shields.io/npm/v/carveout?style=flat-square&label=npm" alt="npm version"></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="MIT license"></a>
12
+ <a href="skills/carveout/SKILL.md"><img src="https://img.shields.io/badge/Claude_Code-skill-D97757?style=flat-square" alt="Claude Code skill"></a>
13
+ <a href="https://skills.sh/lunox-work/carveout"><img src="https://img.shields.io/badge/skills.sh-npx_skills_add_lunox--work%2Fcarveout-111?style=flat-square" alt="skills.sh"></a>
14
+ <a href="#limits"><img src="https://img.shields.io/badge/TypeScript-repositories-3178C6?style=flat-square" alt="TypeScript repositories"></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <img src="assets/hero.png" width="100%" alt="Open-source a feature, not your codebase. What they see: 0.7% of a 245k-line repository, the median slice (11% with tests and pure code). What runs on your machine: 0 lines of their code; CI, hooks, lockfiles, weakened tests, code that spots a test run, new network or disk access and files outside the slice are held. What comes back: 52 of 52 real commits replayed through a slice come back as patches that apply.">
19
+ </p>
20
+
21
+ <table>
22
+ <tr>
23
+ <td align="center" width="25%"><h3>0.7%</h3>of the repository<br>is what a slice shows<br><sub>median of 8 slices of 245k lines; 11% with tests and pure code</sub></td>
24
+ <td align="center" width="25%"><h3>0 lines</h3>of their code run<br>on your machine<br><sub>the patch is read, never run; checks run in a container with no network</sub></td>
25
+ <td align="center" width="25%"><h3>52 of 52</h3>replayed commits<br>come back and apply<br><sub>49 byte-identical; 89% with <code>--3way</code> after the repo moved</sub></td>
26
+ <td align="center" width="25%"><h3>6 of 6</h3>agents in a sealed folder<br>shipped their task<br><sub>each patch passed the owner's hidden tests</sub></td>
27
+ </tr>
28
+ </table>
29
+
30
+ <p align="center">
31
+ <a href="#before--after">Before/After</a> •
32
+ <a href="#install">Install</a> •
33
+ <a href="#how-it-works">How it works</a> •
34
+ <a href="#what-comes-back-and-what-is-held">What comes back</a> •
35
+ <a href="#proof">Proof</a> •
36
+ <a href="#commands">Commands</a> •
37
+ <a href="#limits">Limits</a> •
38
+ <a href="docs/REFERENCE.md">Reference</a>
39
+ </p>
40
+
41
+ Named for the corporate carve-out: one unit split off from a company so it
42
+ can stand on its own. carveout does that for one feature of a repository,
43
+ and takes the work back when it is done.
44
+
45
+ ---
46
+
47
+ ## Before / After
48
+
49
+ <table>
50
+ <tr>
51
+ <td width="50%">
52
+
53
+ ### 🔓 Without carveout: they get the repo
16
54
 
17
- ```sh
18
- # maintainer, at the repository's root
19
- npx -y carveout plan apps/api/src/billing --depth 1 --with-tests # what the folder would hold; writes nothing
20
- npx -y carveout cut apps/api/src/billing --depth 1 --with-tests --public --out ../billing-slice
21
- npx -y carveout check ../billing-slice # npm ci, build, test, as the contractor will
55
+ ```
56
+ $ git clone git@github.com:acme/closed-repo
57
+ 709 files · 245,456 lines of TypeScript
58
+ every package, infra/, .github/workflows/
59
+ their branch runs CI with your secrets
60
+ ```
22
61
 
23
- # contractor: npm ci, edit, npm test, send the folder back
62
+ </td>
63
+ <td width="50%">
64
+
65
+ ### 🔒 With carveout: they get one folder
24
66
 
25
- # maintainer, on the folder that came back
26
- npx -y carveout status ../billing-slice # does the repository still match the cut?
27
- npx -y carveout patch ../billing-slice --output billing.patch # runs none of its code
28
- git apply --3way billing.patch
29
- # or both, with a check: carveout merge ../billing-slice --into . --verify "npm test"
67
+ ```
68
+ $ carveout handoff \
69
+ packages/jira/src/selection-jql.ts
70
+ Verdict: ready: it installs, builds, and its
71
+ tests pass before any edit
72
+ Tests: 15 of 15 test(s) pass before any edit
73
+ Shows 15 files, 2,980 lines
74
+ Names: 3 rename(s) hide the repository's
30
75
  ```
31
76
 
32
- Use it to:
77
+ </td>
78
+ </tr>
79
+ <tr>
80
+ <td width="50%">
33
81
 
34
- - let a contractor, an outside contributor or an agent you do not trust
35
- with the rest work on one part of a closed repository, and take the work
36
- back as a patch that is checked before anything runs it
37
- ([below](#handing-a-slice-to-a-contractor));
38
- - work on one package of a monorepo without installing or building the
39
- others: a slice installs in about a third of the time and an eighth of
40
- the disk;
41
- - see what a module really depends on, and what depends on it;
42
- - make a minimal reproduction of one module.
82
+ ### ✅ The work comes back
43
83
 
44
- The same import graph also checks a change made inside the repository,
45
- yours or a coding agent's: `carveout impact` names what it reaches and the
46
- tests to run, and `carveout gate` holds weakened tests and CI or config
47
- edits, also as a Claude Code Stop hook
48
- ([below](#also-checking-a-change-in-the-repository)).
84
+ ```
85
+ $ carveout patch ../returned --output change.patch
86
+ carveout: 2 changed, 0 new and 0 deleted files
87
+ $ git apply change.patch && npm test -w packages/jira
88
+ ℹ tests 165
89
+ ℹ pass 165
90
+ ℹ fail 0
91
+ ```
49
92
 
50
- ## Handing a slice to a contractor
93
+ </td>
94
+ <td width="50%">
51
95
 
52
- For a contractor or contributor who must not see the rest of a closed
53
- repository. The maintainer cuts and later merges; the contractor only works
54
- in the folder.
96
+ ### 🚫 A shortcut does not
55
97
 
56
- ```sh
57
- # maintainer, at the repository's root
58
- carveout plan packages/api/src/quote.ts --depth 1 --with-tests --whole-package --copy-pure
59
- carveout cut packages/api/src/quote.ts --depth 1 --with-tests --whole-package --copy-pure \
60
- --public --out ../quote-slice
61
- carveout check ../quote-slice # read what it holds before sending it
62
- # contractor: npm ci, edit, npm test, send the folder back
63
- # maintainer, on the folder that came back
64
- carveout patch ../returned --output quote.patch # runs none of its code
65
- less quote.patch # read all of it before anything runs it
66
- git switch -c contrib/quote && git apply --3way quote.patch
67
- carveout impact --base HEAD # the importers and tests the contractor never saw
68
- # then a PR: CI decides
69
98
  ```
99
+ $ carveout patch ../returned
100
+ carveout: kept out:
101
+ packages/jira/src/selection-jql.ts,
102
+ could pass a test without the code
103
+ passing it (process.execArgv matched
104
+ against a test runner)
105
+ exit 6
106
+ ```
107
+
108
+ </td>
109
+ </tr>
110
+ </table>
111
+
112
+ **Same task. 15 files out of 709. The shortcut held before anything ran
113
+ it.**
114
+
115
+ A real hand-off on [sandbox-factory](https://github.com/lunox-work/sandbox-factory),
116
+ 2026-11-11. The 15 files are the entry point, its test, and 13 pure modules
117
+ copied so that test runs on real code. Everything else the slice touches is
118
+ a `.d.ts` stub with a recording mock: declarations, never bodies. Those 13
119
+ modules were pricing code. `plan` lists every copied file before anything is
120
+ written, and `--private` keeps a folder out: here that shows 245 lines, and 7
121
+ tests then fail on the mocks before any edit. You choose.
70
122
 
71
- What carveout holds to:
72
-
73
- - **The folder.** It holds the slice's source as it is, and stubs of what
74
- the slice imports from the rest: the declarations of the names it uses
75
- (a one-line barrel included), never their bodies or comments. With
76
- `--public` (or `"public": true` in the configuration), literal values are
77
- widened and the folder names neither the maintainer's checkout nor the
78
- files outside the slice that import it. `--private <path>` keeps a module
79
- out even when the walk reaches it, `--copy-pure` included. `--copy-pure`
80
- copies more of the repository (the pure code the slice runs) so the
81
- contributor's tests run against it rather than against mocks: leave it
82
- off, or mark that code private, where it must stay closed. `plan` and
83
- `cut` list the source copied from outside the entry points by folder,
84
- with its lines, tests not counted: each folder there is a `--private`
85
- decision to make before the folder is sent.
86
- - **The folder that comes back.** Nothing in it is trusted. `patch` and
87
- `apply` refuse a `carveout.json` that names a path outside the folder or
88
- a `.git` path, or lists as the slice's a file the repository does not
89
- hold at those bytes, and work against the checkout given with `--repo`
90
- (or the current directory), which must hold the cut's commit. `patch`
91
- runs none of the folder's code.
92
- - **The hand-off policy.** The patch carries edits to the slice's files and
93
- new files in the slice's packages. It leaves out, names, and exits 6 on:
94
- new files in other packages (a cut module's folder among them);
95
- protected paths (CI workflows, hooks, `.vscode`, `.devcontainer`,
96
- `package.json`, lockfiles, `.npmrc`, `.env*`, Dockerfiles, Makefiles,
97
- `*.sh`, `CODEOWNERS`, `carveout.config.json`); deleted tests; and edited
98
- tests that remove or rewrite an assertion, skip or focus (`.only`) a
99
- test, or leave an assertion unreachable or in a `catch` that swallows it
100
- (an expected value changed may be the change itself, or a test bent to
101
- pass: the maintainer reads it); and a script, new or edited, that gains
102
- a reach it did not have: Node's disk, network or process modules, a
103
- service SDK, `fetch`, `eval`, the environment, or a module or global
104
- named by a computed value (a returned test that reads the closed source
105
- and sends it out is one). `--allow <path|glob>` lets a reviewed one
106
- through. `merge` applies nothing while any is refused. The policy reads
107
- syntax, not intent: a new runner configuration (`vitest.config.ts`), or
108
- code that does harm with only the reach its file already had, passes it.
109
- - **Running what came back.** The contributor's tests, and every new file
110
- the patch carries, are code that runs beside the whole repository, the
111
- part the slice kept closed included. A branch pushed to the repository
112
- runs CI with the repository's secrets and network, which a fork's pull
113
- request does not: code there can read the source and send it out. Read
114
- the whole patch before anything runs it, or run its first CI with no
115
- secrets and no outbound network. `check --isolated` gives the folder a
116
- scratch HOME and npm cache and no install scripts, and `merge --verify`
117
- a scratch HOME and only the variables named with `--verify-env`:
118
- neither is a sandbox, and `merge --verify` runs in the full checkout.
119
-
120
- What it does not promise:
121
-
122
- - **What a pass in the folder covers is stated.** `plan`, `cut` and
123
- BOUNDARY.md name every test that runs the slice's code and stays in the
124
- repository, and why (not asked for, another package's, Jest, the file
125
- limit). When none is in the folder, a pass means the code builds and
126
- keeps its exports, nothing more.
127
- - **Passing in the slice is a pre-check, not the merge gate.** The slice's
128
- tests run against mocks of everything it cuts. A test that needs a cut
129
- module's real behaviour fails at the slice's baseline and is not counted,
130
- and a test that imports `carveout/` to make a mock answer stays in the
131
- folder: the change comes back without it, and `patch` says how many. In
132
- the experiments, verdicts in the slice and after the merge agreed in 74%
133
- of cases, nearly all disagreements in the safe direction (the slice
134
- failed, the repository passed). `--copy-pure` turns most such baselines
135
- green, and the surface check catches a changed export the importers in
136
- other packages rely on; neither runs those importers. Those figures come
137
- from earlier CLIs: the model-run benchmarks have not been run again since
138
- `--copy-pure`, the surface check and the barrel-chain rule. The
139
- repository's CI decides, once the patch has been read.
140
- - **The slice's own source is the contributor's to read.** Choose the
141
- entry points and `--private` paths with that in mind; `plan` lists every
142
- file before anything is written.
143
-
144
- ## What a cut holds
123
+ ## Install
145
124
 
125
+ ```sh
126
+ npx skills add lunox-work/carveout -g
146
127
  ```
147
- billing-slice/
148
- apps/api/src/billing/... the slice, copied at its repository paths
149
- packages/db/src/index.d.ts a stub of what the slice uses from a cut module
150
- packages/db/src/index.js its recording mock
151
- carveout/packages/stripe/ a mock package for a service SDK
152
- carveout/mock.js the mock runtime: recordedCalls(), mockImplementation()
153
- carveout/tests/ an interface test: every export outside code imports
154
- carveout/surface/ those exports' declarations at the cut, and the type
155
- check npm test holds them to
156
- BOUNDARY.md an index: the tests that run the slice's code (in the
157
- folder, and left behind with why), the modules it
158
- imports from outside, and the exports outside code
159
- takes from it
160
- carveout.json the cut: hashes, cuts, packages, rewrites, blockers
161
- package.json, tsconfig.json, package-lock.json, carveout.env
128
+
129
+ In Claude Code, the repository is also a plugin marketplace:
130
+ `/plugin marketplace add lunox-work/carveout`, then
131
+ `/plugin install carveout@carveout`. It holds two skills:
132
+ [`carveout`](skills/carveout/SKILL.md), the hand-off, and
133
+ [`carveout-gate`](skills/carveout-gate/SKILL.md), `impact` and `gate` for a
134
+ change made in the repository. The plugin also bundles the `lunox` MCP server
135
+ (`.mcp.json`), Lunox's hosted platform: Claude Code asks you to sign in on
136
+ first use (`/mcp`), and signed out the skills run locally as before.
137
+
138
+ Then ask your coding agent (Claude Code, Codex, Cursor, or any agent that
139
+ loads skills): _"hand `src/billing` to a contractor"_. It plans the slice,
140
+ shows you what goes out, and cuts it once you agree. Without a skill, use the
141
+ CLI:
142
+
143
+ ```sh
144
+ npx -y carveout handoff src/billing/quote.ts --task task.md --out ../quote-slice
162
145
  ```
163
146
 
164
- - **Stubs** are emitted by the TypeScript compiler from the cut modules'
165
- own declarations, and narrowed to the names the slice uses.
166
- - **Workspace packages** are stubbed from their source rather than
167
- installed, unless the cut follows them (`--follow-workspaces`): then an
168
- import of one is an import of its source file, copied or cut like any
169
- module. Either way, a file in another package that imports the slice by
170
- the package's name is one of its importers.
171
- - **Installed packages** are those the repository pins from the public
172
- registry. Each comes with its `@types` and its required peers, at the
173
- repository's versions.
174
- - **Mock packages** stand in for service SDKs (payments, cloud, databases,
175
- queues, email, HTTP clients, LLM SDKs) and for private-registry packages.
176
- - **Import rewrites.** Path aliases, workspace imports and bundler-style
177
- extensionless imports become relative paths Node resolves. Each is
178
- recorded, and `patch` undoes it.
179
- - **Assets.** Stylesheets and images a module imports are copied, and load
180
- in Node as their path. `import.meta.glob` and `import.meta.env` work
181
- under Node.
182
- - **The repository's checks.** The compiler options that only reject
183
- code (`strict`, `noUnusedLocals` and the like) come from the
184
- repository's tsconfig. `noImplicitAny` too, unless the slice does not
185
- compile under it with its stubs and untyped packages, which the cut says. Node's types come at the repository's version.
186
- When the package's own test sets coverage thresholds, `npm test` holds
187
- the source files you add to them, and fails, as the package's script
188
- does, on a file you add that no test loads (`assert-all-covered`).
189
- - **The public surface.** Every export code outside the slice imports
190
- must stay exported (the interface test) and keep its type: `npm test`
191
- type-checks `carveout/surface/check.ts`, which holds each such value to
192
- its type at the cut and each such type to its shape both ways, and
193
- names the export whose type changed. Enums and classes with private
194
- members are not checked; a check that fails before any edit is not
195
- counted.
196
- - **Credentials.** A credentials file is never copied. A credential found
197
- in anything the cut would write, a `--copy` folder or the resolved
198
- lockfile included, stops the cut: nothing is written.
199
-
200
- The plan and the cut read the repository and never run its code. The cut
201
- fetches package types from the public registry with install scripts off,
202
- and resolves a lockfile; when the repository has an npm lockfile, the
203
- slice starts from its locked versions. `--offline` skips both.
204
-
205
- ## What the walk follows
206
-
207
- - **Names, not whole files.** Importing `a` from a barrel brings the barrel
208
- and the module that declares `a`, at the barrel's depth. The barrel's
209
- other re-exports are cut.
210
- - **Code, not types.** A module the slice takes only types from
211
- (`import type`, or an interface or type alias by name) is cut to its
212
- stub and never copied.
213
- - **Files read by path.** `new URL("./worker.ts", import.meta.url)` and
214
- `join(__dirname, "get.sql")` copy the file. A folder read by path, or such
215
- a file left outside the slice, is a `path_read` blocker.
216
- - **Modules named to a test runner** (`vi.mock`, `jest.requireActual`,
217
- `require.resolve`) are cut, not followed. Their specifiers are rewritten
218
- like imports.
219
- - **The file budget.** When it runs out, `plan` lists the files it left out.
220
- - **The whole package**, with `--whole-package`: every file of the entry
221
- points' package the imports reach comes along at any depth, so the cut
222
- falls at package and service boundaries, and a sibling module is copied
223
- rather than stubbed. A barrel's re-exports come only when they supply a
224
- name the slice asks of it. Imports into other packages still stop at the
225
- depth.
226
- - **Barrel chains.** A test that takes a slice name through barrels
227
- (`src/index.ts` → `pricing/index.ts` → `pricing/weight.ts`) is a test of
228
- the slice: `--with-tests` brings it and the whole chain, each barrel cut
229
- to what it forwards from the slice.
230
- - **A test's helpers.** A test brought along by `--with-tests` brings what
231
- it imports at runtime from its own package, or from a test folder
232
- anywhere (`test/`, `__mocks__/`, `fixtures/`): a fake copied is a test
233
- that passes, a fake stubbed is one that fails before any change.
234
- - **Private paths** (`--private <path>`, or `private` in the
235
- configuration) are never copied: wherever the walk reaches one it is
236
- cut and stubbed, with literal values widened.
237
- - **Pure code**, with `--copy-pure`: a module the slice runs from outside
238
- it is copied rather than mocked, past the depth and across packages,
239
- when nothing its runtime imports reach outside the slice imports a
240
- service SDK, Node's disk, network or process modules, reads the
241
- environment or a path, or is private. A mocked validator or error
242
- class answers with more mocks, and the tests that use it fail before
243
- any change; copied, they run as in the repository. Through a barrel,
244
- only the modules that supply the names come. The copied code is the
245
- contributor's to read: `plan` lists it.
246
-
247
- Only the files the walk needs are parsed. Files that may import the slice
248
- are found by name first, and the compiler confirms each one.
147
+ Needs Node 22.12+ and a TypeScript repository (npm, pnpm or Yarn; `node:test`
148
+ or Vitest). Code that came back runs only in Docker; without it, carveout
149
+ runs none of it unless you pass `--no-sandbox`.
249
150
 
250
- ## Commands
151
+ ## Try it in 60 seconds
152
+
153
+ ```sh
154
+ # maintainer, at the repository's root
155
+ npx -y carveout plan apps/api/src/billing --depth 1 --with-tests # what the folder would hold; writes nothing
156
+ npx -y carveout handoff apps/api/src/billing --task task.md --out ../billing-slice
251
157
 
252
- | Command | Does |
253
- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
254
- | `carveout plan <entry>...` | Lists the files, cuts, importers, packages, environment and blockers; the lines the cut shows, by why each file is there; every test that runs the code, in the folder or left behind and why. Writes nothing. |
255
- | `carveout cut <entry>... --out <dir>` | Writes the folder. Exits 2 if it was written with blockers; writes nothing if it would carry a credential. |
256
- | `carveout check <dir>` | Runs `npm ci`, `npm run build` and `npm test` in a scratch copy; records the tests that fail on the mocks. `--isolated` for a folder that came back. |
257
- | `carveout status <dir>` | Says whether a cut now would write the same folder, and names what changed. Exits 4 if stale. |
258
- | `carveout patch <dir>` | Prints the edits as a patch for the repository (`--output <file>`), with full blob ids for `git apply --3way`. Runs none of the folder's code; keeps out what the hand-off policy refuses (exit 6). |
259
- | `carveout merge <dir> --into <checkout>` | Patches, applies with `--3way` in the checkout you name, and runs `--verify <cmd>` there with a scratch HOME and only the `--verify-env` variables. Applies nothing on a refusal (exit 6). |
260
- | `carveout apply <dir> <patch>` | Brings a patch for the repository into the folder, imports rewritten; `--stubs` adds the stubs it needs. A module the patch adds past the cut's reach joins the folder as an entry, with its tests. |
261
-
262
- Options for `plan` and `cut`:
263
-
264
- - `--repo <root>` (default `.`)
265
- - `--depth N` (default 3)
266
- - `--max-files N` (default 40)
267
- - `--with-tests`, which also brings the tests outside the slice that import
268
- it (`*.test.*`, `*.spec.*`, `__tests__/`) and run on `node:test` or
269
- Vitest (imported, or its globals in a package that depends on Vitest and
270
- not Jest), directly or through barrels outside the slice that re-export
271
- from it; such a barrel comes along, its other re-exports cut, and so do
272
- the test's own helpers. `npm test` in the folder runs them. When the
273
- file budget cannot hold them all, the tests nearest the entry points
274
- come first: those that take names the entry points declare, whatever
275
- barrel they import through, ahead of those that take names declared
276
- further out. Without a
277
- lockfile Vitest is mocked like any package; cut with `--install vitest`.
278
- - `--whole-package`, `--follow-workspaces`, `--copy-pure` (which follows
279
- the workspaces too) and `--private <path>` (see above; `--private` takes
280
- a path, a folder or a glob, more than once).
281
- - `--json`
282
-
283
- A `carveout.config.json` at the repository's root sets the same for every
284
- cut, so the owner decides once where a slice may follow and what it keeps
285
- out: `{"wholePackage": true, "followWorkspaces": true, "private":
286
- ["packages/core/src/pricing/**"], "copy": ["packages/db/drizzle"],
287
- "widenLiterals": true, "public": true, "copyPure": true}`. A flag overrides a boolean; the
288
- lists are joined. With any `private` path, or `public`, `widenLiterals` is
289
- on unless the file says otherwise.
290
-
291
- Options for `cut` only:
292
-
293
- - `--mock <pkg>` and `--install <pkg>`, which take precedence over the
294
- defaults. A package whose class a carried test patches by prototype
295
- (`S3Client.prototype.send = ...`) is installed without being asked: a
296
- mock has no prototype to patch, and such a test never reaches the
297
- service. So is one whose class the code tests with `instanceof`
298
- (`error instanceof Anthropic.APIError`): a mock narrows nothing, and the
299
- folder would not build;
300
- - `--copy <dir>`, which copies a folder the code reads by a name the walk
301
- cannot follow (a migrations folder) beside the slice. Its files are the
302
- repository's: an edit to one is in the patch;
303
- - `--live <pkg>`, which keeps a service package real: installed and typed
304
- as the repository has it, with the environment its importers read left
305
- out of `carveout.env` for the shell to supply, so the code's own
306
- defaults apply otherwise. For tests that need the service itself, such
307
- as a database driver against a local server;
308
- - `--widen-literals`, which writes `string` in stubs in place of literal
309
- values. A constant the stub reads as a type (`type Kind = (typeof
310
- KINDS)[number]`) keeps its name-like strings (`"github"`, `"draft"`):
311
- they are that type's members, and widened, the slice would stop checking
312
- the code that relies on them. Its numbers, URLs and sentences widen;
313
- - `--public`, for a folder handed to someone outside: literals widened,
314
- and neither the checkout's path or folder name nor the files outside the
315
- slice that import it written (each importer is named by its package
316
- folder; compiler messages name files from the repository's root). The
317
- commands that need the repository then take `--repo`, defaulting to the
318
- current directory;
319
- - `--offline`.
320
-
321
- Options for `patch`, `status`, `apply` and `merge`: `--repo <checkout>`,
322
- the repository to work against (it must hold the cut's commit; `merge`
323
- uses `--into`). For `patch` and `merge`: `--allow <path|glob>`, which lets
324
- a path the hand-off policy refuses through. For `merge`: `--verify-env
325
- <name>`, an environment variable the verify command is given.
326
-
327
- Exit codes: 0 done, 1 refused or failed, 2 written with blockers (`cut`)
328
- or a step failed (`check`, `merge`, `impact --run`; `gate --hook` on a
329
- hold), 3 nothing to patch, 4 stale (`status`), 5 the edits reach past the
330
- slice, 6 the hand-off policy kept files out (`patch`, `merge`) or held a
331
- change (`gate`).
158
+ # contractor: npm ci, edit, npm test, send the folder back
159
+
160
+ # maintainer, on the folder that came back
161
+ npx -y carveout patch ../billing-slice --output billing.patch # runs none of its code
162
+ less billing.patch # read all of it
163
+ git switch -c contrib/billing && git apply --3way billing.patch # then a PR: CI decides
164
+ ```
165
+
166
+ ## How it works
167
+
168
+ <p align="center">
169
+ <img src="assets/flow.png" width="100%" alt="One folder out, one checked patch back. Out: carveout handoff on a 709-file repository sends 15 files; the rest are .d.ts stubs and mocks, names in pseudonyms, 15 of 15 tests pass before any edit. Their work: a contractor, contributor or agent runs npm ci, edits, and npm test passes 17 of 17. Back: carveout patch runs none of it; 2 files come back and the repository's tests pass 165 of 165; code that matched process.execArgv against a test runner is held.">
170
+ </p>
171
+
172
+ - **Out.** You name a file or a folder. carveout copies it, and what it
173
+ imports up to a depth you choose, at the same paths. Every module it
174
+ imports from the rest becomes a `.d.ts` stub with a recording mock, so the
175
+ folder installs, builds and tests on its own.
176
+ - **Nameless.** A hand-off is written in pseudonyms: the repository's
177
+ package names, scope, remote, authors and your Git identity are renamed
178
+ (`LunoxCart` becomes `AcmeCart`). Only your receipt maps them back, and it
179
+ never leaves your machine.
180
+ - **Ready before it ships.** `check` runs the folder as the contractor will.
181
+ It says the folder is ready (its tests pass before any edit) or names the
182
+ tests that fail on the mocks and what would run them on real code.
183
+ - **Back.** `carveout patch` turns the folder's edits into a patch in your
184
+ names. It trusts nothing in the folder: the receipt decides what a patch
185
+ may carry. It runs none of their code.
186
+
187
+ ## What comes back, and what is held
188
+
189
+ | Comes back | Held, named, exit 6 until you `--allow` it |
190
+ | ------------------------------------------------ | -------------------------------------------------------------------------- |
191
+ | Edits to the slice's files | CI workflows, hooks, Dockerfiles, Makefiles, `.env*`, manifests, lockfiles |
192
+ | New files in the slice's packages | Deleted tests, and tests that lose, move or rewrite an assertion |
193
+ | New tests, where the package's runner finds them | `.skip`, `.only`, and code that spots a test run (`NODE_ENV`, argv, stack) |
194
+ | A package you add, named with its pin | New reach: disk, network, process, environment, `eval`, a missing package |
195
+ | Your names, put back | New `@ts-ignore` or `eslint-disable`, and files in other packages |
196
+
197
+ The policy reads syntax, not intent: code that special-cases a test's own
198
+ inputs passes it. Read the whole patch before anything runs it, and run its
199
+ first CI with no secrets. The full policy is in the
200
+ [reference](docs/REFERENCE.md#handing-a-slice-to-a-contractor).
201
+
202
+ ## Proof
203
+
204
+ - **What a slice shows.** Over 8 slices of a 245k-line repository, a
205
+ default cut copies a median of 0.7% of the source (0.03 to 2.4%). With the
206
+ hand-off's tests and pure code, 11% (0.3 to 24%). Stub declarations come
207
+ on top and are not counted
208
+ ([hero-exposure.mjs](experiments/hero-exposure.mjs)).
209
+ - **The round trip.** 52 real commits replayed through a slice all come back
210
+ as patches that apply, 49 byte-identical. On a repository that moved on,
211
+ 27% apply plain and 89% with `git apply --3way`.
212
+ - **Agents in a folder.** Six agents, each given only a public hand-off
213
+ folder, delivered their tasks. Each patch applied in the repository's
214
+ names and passed the owner's hidden acceptance tests.
215
+ - **Folders that stand alone.** Ten of eleven slices from three
216
+ repositories install, build and test on their own with the hand-off's
217
+ flags. The eleventh fails on a narrowed inferred type ([Limits](#limits)).
218
+ - **What it does not win.** A slice does not make an agent cheaper or
219
+ better: it read 17 to 24% more input and passed hidden tests 5 to 10
220
+ points less often than the same agent in the whole repository. A pass in
221
+ the slice matched the verdict after the merge in 74% of replays, nearly
222
+ every miss the safe way (the slice failed, the repository passed). The
223
+ slice is a pre-check; your CI is the gate.
224
+
225
+ Pre-registered, with the raw data: [experiments/RESULTS.md](experiments/RESULTS.md).
332
226
 
333
227
  ## Also: checking a change in the repository
334
228
 
335
- The import graph a slice is cut from also answers two questions about a
336
- change made inside the repository, yours or a coding agent's, with no slice
337
- involved: what does it reach, and what should a reviewer read before it
338
- lands? `gate` applies the hand-off policy above to that change.
229
+ The same import graph checks a change made inside the repository, yours or
230
+ a coding agent's:
339
231
 
340
232
  ```sh
341
- npx -y carveout impact # what the uncommitted change reaches
342
- npx -y carveout impact --base main --run # since main, and run just those tests
343
- npx -y carveout gate --base main # weakened tests, CI or config edits, suppressions
233
+ npx -y carveout impact # what the change reaches, and the tests to run
234
+ npx -y carveout gate --base main # weakened tests, CI or config edits, suppressions
344
235
  ```
345
236
 
346
- | Command | Does |
347
- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
348
- | `carveout impact [<file>...]` | What a change in the repository reaches: importers, changed exports and who takes them, the tests to run and their commands. `--base`, `--depth`, `--run` (exit 2 on a failing test command). |
349
- | `carveout gate` | Holds a change's weakened or deleted tests, protected paths, check configuration and new suppressions (exit 6). `--base`, `--scope`, `--allow`, `--hook`. |
350
-
351
- `impact` and `gate` read the change from Git: the base (default `HEAD`)
352
- against the working tree, untracked files included. Neither runs the
353
- repository's code, but for `impact --run`.
354
-
355
- **`carveout impact`** reports:
356
-
357
- - **Importers**: every file that imports a changed module, directly or
358
- through others, found by name and confirmed by the compiler (aliases,
359
- workspace package names, barrels, `vi.mock`). It follows names, as the
360
- slice walk does: a barrel carries a change only through the names it
361
- forwards from the changed module, and a module only through its exports
362
- whose declarations reach a changed declaration. With a base, only the
363
- exports the diff reaches start the search; named files start with all of
364
- theirs.
365
- - **Exports**: each export of a changed module whose type changed or that
366
- is gone, as the TypeScript checker prints it at the base and now, one
367
- level of members deep, and the files that take it.
368
- - **Tests**: the tests among the importers, each with its runner
369
- (`node:test`, Vitest or Jest; otherwise the package's own `npm test`),
370
- and one command per package that runs just them. The commands run only
371
- installed binaries (`npx --no`).
372
- - **`--run`** runs those commands one at a time, in the repository, with
373
- your environment, and exits 2 when one fails. `--depth N` bounds the hops;
374
- `--json` gives it all as data.
375
-
376
- On sandbox-factory (1,288 files) it takes under 1.5 s. A change to one
377
- function of `packages/core/src/stages.ts` selects 21 of the repository's
378
- 241 tests, among them the API test that pins that function's message, two
379
- hops away; naming the whole file selects 156.
380
-
381
- **`carveout gate`** holds for review, and exits 6 on any:
382
-
383
- - protected paths (CI workflows, hooks, manifests, lockfiles, `.env*`,
384
- Dockerfiles; the hand-off policy's list above);
385
- - configuration a check reads, when edited: `tsconfig*.json`, ESLint,
386
- Biome, Vitest, Jest, coverage settings;
387
- - a deleted test, and a test whose assertions were removed or rewritten,
388
- skipped, focused with `.only`, made unreachable (an early `return`,
389
- `if (false)`), or wrapped in a `catch` that swallows their failure;
390
- - new suppressions in a script: `@ts-ignore`, `@ts-expect-error`,
391
- `eslint-disable`, coverage ignores;
392
- - with `--scope <path|glob>`, any file outside it.
393
-
394
- A hold is a question for a reviewer, not a verdict: a change asked to
395
- alter a behaviour rewrites the assertion that held it. `--allow
396
- <path|glob>` lets a reviewed file through. As a Claude Code Stop hook,
397
- `--hook` sends the holds back to the agent (exit 2) once per stop, asking
398
- it to undo what the task did not ask for and to name the rest in its reply:
237
+ `gate` also works as a Claude Code Stop hook. It sends its holds back to the
238
+ agent once per stop:
399
239
 
400
240
  ```json
401
241
  {
@@ -411,94 +251,46 @@ it to undo what the task did not ask for and to name the rest in its reply:
411
251
  }
412
252
  ```
413
253
 
414
- On a patch that came back from a slice, `git apply` it on a branch and run
415
- `carveout impact --base HEAD` (no `--run`) before anything runs it: it
416
- names the importers and tests the contractor never saw.
417
-
418
- ## Tested on
419
-
420
- Thirteen real slices from three TypeScript repositories, sandbox-factory
421
- among them. They range from 1 to 173 files, and cover Node services, React
422
- components and a whole Vite app's `src` at depth 1, with npm workspaces and
423
- path aliases. Every one installed, built and passed its interface test with
424
- `carveout check`.
425
-
426
- One needed a second cut. An auth module's installed package loads an
427
- optional peer at runtime, `drizzle-orm`. The first cut listed it among the
428
- optional peers and `check` named it; cutting again with
429
- `--install drizzle-orm` passed.
430
-
431
- Walking by name rather than by file made three of them smaller, with the
432
- rest unchanged: 4 files to 2, 3 to 1, and 19 to 11. The modules left out
433
- were ones they imported only types from, now stubs. All thirteen still pass
434
- `check`.
435
-
436
- The lazy graph walks as the whole graph does: identical results in 722
437
- walks over five repositories, cal.com and n8n among them. On n8n (24k
438
- files) the median walk takes 2.3 s against 13.7 s for the whole graph. A
439
- slice whose files have common names can read most of the repository, and
440
- then takes as long.
441
-
442
- On n8n (pnpm, catalogs, Vitest globals), a one-module slice installs,
443
- builds and passes `check`, where it once stopped at the install on a
444
- `catalog:` pin; with `--with-tests` it carries five Vitest tests, and its
445
- build then fails on limits named below (an `override` of a mocked base
446
- class, `never[]` under the relaxed `noImplicitAny`).
447
-
448
- ## Experiments
449
-
450
- `experiments/` holds the pre-registered experiments and their data
451
- ([RESULTS.md](experiments/RESULTS.md)).
452
-
453
- - **Agents in a slice (Phases 5 to 8).** An agent given a slice instead of
454
- the repository did not get cheaper or better: it read 17 to 24% more
455
- input, passed hidden tests 5 to 10 points less often, and a weaker model
456
- did as well in the repository as in a slice. A full-repository agent read
457
- a median of two files outside the slice. The slice's measured wins are
458
- the ones without a model: setup at 0.35× the time and 0.12× the disk, and
459
- a bounded view. That is why the slice is for someone who must not see
460
- the rest, not a way to make an agent cheaper, and why `impact` and
461
- `gate` work in the repository itself.
462
- - **`status` (E1).** Over 345 later commits it never said `current` when a
463
- fresh cut differed.
464
- - **Three-way patches (E2).** Applied to a repository that had moved, a
465
- patch applied plain in 27% of cases and with `git apply --3way` in 89%.
466
- Every three-way result typechecked.
467
- - **Co-change hints in `plan` (E3).** Precision was too low, so they were
468
- dropped.
469
- - **Graph time (E4).** The graph of a 24k-file monorepo takes 12 s, down
470
- from 7.5 minutes before a fix that leaves the graph identical. A cache
471
- across runs was not built: it could not reach a 3× speedup.
254
+ ## Commands
255
+
256
+ | Command | Does |
257
+ | ----------------------------- | ------------------------------------------------------------------------------ |
258
+ | `carveout handoff <entry>...` | Cut for someone outside, check it, and say whether it is ready to send |
259
+ | `carveout plan <entry>...` | List what a folder would hold, cut and show. Writes nothing |
260
+ | `carveout cut <entry>...` | Write the folder |
261
+ | `carveout check <dir>` | `npm ci`, build and test it as the contractor will; returned folders in Docker |
262
+ | `carveout patch <dir>` | Turn the folder's edits into a patch, holding what the policy refuses |
263
+ | `carveout merge <dir>` | Patch, apply, and run `--verify` in a container |
264
+ | `carveout status <dir>` | Say whether the repository still matches the cut |
265
+ | `carveout refresh <dir>` | Bring the repository's new commits into the folder, merged with the work |
266
+ | `carveout widen <dir>` | Cut again with more entry points, keeping the work |
267
+ | `carveout impact` | What a change reaches: importers, changed exports, tests to run |
268
+ | `carveout gate` | Hold weakened tests, CI and config edits, and new suppressions |
269
+ | `carveout login` | Optional: sign in to the Lunox platform; a token in `~/.lunox/credentials` |
270
+ | `carveout whoami` | Print the account, workspace and token expiry signed in with |
271
+ | `carveout logout` | Delete the saved platform token |
272
+
273
+ Every option: [reference](docs/REFERENCE.md#commands). Only `login`, `whoami`
274
+ and `logout` talk to the Lunox platform; every other command is local and needs
275
+ no account.
472
276
 
473
277
  ## Limits
474
278
 
475
- - **Languages.** Stubs are extracted from TypeScript only. A JavaScript
476
- source in a slice is reported as `language_unsupported`.
477
- - **Inferred types.** A stub can be narrower than the source when a type is
478
- inferred from a package carveout could not read. The cut reports this; it
479
- does not hide it.
480
- - **Lockfiles.** Peers and locked versions are read from an npm
481
- `package-lock.json`. pnpm and Yarn repositories fall back to the versions
482
- in their package.json files; a `catalog:` pin takes the catalog's
483
- version (`pnpm-workspace.yaml`, or `catalog(s)` in the root
484
- package.json). A pnpm lock entry with no tarball URL shows a package is
485
- public only when its integrity equals the public registry's for that
486
- version (checked online; offline it is mocked). Such folders resolve
487
- peers as pnpm and Yarn do (`legacy-peer-deps`).
488
- - **Test runners.** `node:test` and Vitest. A Jest test is named as left
489
- behind, never run.
490
- - **Folders named like build output.** Files under `build/`, `dist/`,
491
- `vendor/` and `third_party/` are not parsed: a source folder named
492
- `build` is copied when reached, but its own imports are not followed.
493
- - **What `impact` can miss.** It follows imports, not runtime: a module
494
- loaded by a computed name, a file read by path, or configuration a test
495
- reads is not traced, and a test it does not list can still fail. Inside a
496
- module it traces names by the identifiers each top-level statement uses,
497
- which errs toward more. A type is compared one level of members deep: a
498
- change inside a named type shows on that type's own module. Its
499
- `node:test` command for TypeScript uses Node's type stripping, which does
500
- not resolve path aliases; such a package's own test script is the one to
279
+ - **TypeScript only.** A JavaScript source in a slice is reported, not
280
+ stubbed.
281
+ - **`node:test` and Vitest.** A Jest test is named as left behind, never
501
282
  run.
283
+ - **Mocks only record calls.** A change that needs a cut module's real
284
+ behaviour needs `--copy-pure`, `--live <package>` or a wider cut
285
+ (`carveout widen`).
286
+ - **More green, more shown.** Copying pure code turns red baselines green
287
+ and shows more of the repository. `plan` lists every file first.
288
+ - **Syntax, not intent.** The policy is a reader's checklist, not a proof.
289
+ Read the patch; the repository's CI decides.
290
+ - **Docker** (or Podman) runs code that came back. Without it, nothing runs
291
+ but with `--no-sandbox`, which runs it as you.
292
+
293
+ All of them: [reference](docs/REFERENCE.md#limits).
502
294
 
503
295
  <!-- Keep this section and AGENTS.md the same. -->
504
296
 
@@ -523,8 +315,9 @@ the person:
523
315
 
524
316
  For a slice, run `npx -y carveout plan <entry> --depth 1` first. Show the
525
317
  person what the slice would hold and what it cuts, and agree the entry
526
- points and the depth with them. Then `cut` and `check`, and report the
527
- check's result as it is.
318
+ points and the depth with them. Then `cut` and `check` (or `handoff`, both
319
+ in one, with `--task` for the work and its acceptance), and report the
320
+ verdict as it is: ready, tests that fail on the mocks, or no tests.
528
321
 
529
322
  For a change in the repository, run `npx -y carveout impact` (add
530
323
  `--base <branch>` for committed work) and report the exports, importers and
@@ -558,14 +351,31 @@ What to tell them, accurately:
558
351
  - `--copy-pure` copies the pure code the slice runs instead of mocking
559
352
  it, so the folder's tests pass before any change; it shows the
560
353
  contributor more of the repository, which `plan` lists.
354
+ - `--public` also writes the folder in pseudonyms: the names the
355
+ repository goes by (packages, remote, authors, the maintainer's Git
356
+ identity, `--name`) are renamed under a table only the maintainer's
357
+ receipt holds, and `patch` renames the work back. A name left behind
358
+ blocks the cut; report it, and add it with `--name` or `pseudonyms` in
359
+ the configuration. Never send the receipt.
561
360
  - For someone outside the repository, cut with `--public`, take the folder
562
361
  back with `carveout patch` (it runs none of the folder's code and keeps
563
362
  out CI, hook, manifest and lockfile files, new files in other packages,
564
- deleted tests and code that newly reaches the disk, network or
363
+ deleted or bent tests, code that could pass a test without the code
364
+ under test, and code that newly reaches the disk, network or
565
365
  environment), read the whole patch before anything runs it (its
566
366
  tests and new files are code beside the closed repository, and a branch
567
367
  pushed there gets CI's secrets and network), then let the repository's
568
368
  CI judge the change: a pass in the slice is a pass against mocks.
369
+ - `carveout check` and `merge --verify` run a folder that came back only
370
+ after the hand-off policy has read it, and only in a container (Docker)
371
+ that mounts nothing of the machine but a copy, with no network but for an
372
+ install with scripts off. When the container cannot run, they run
373
+ nothing; `--no-sandbox` runs the folder's code as the person, with their
374
+ files and network: say so before passing it.
375
+ - For a job long enough that the repository moves, `carveout refresh`
376
+ brings its commits into the folder, merged into the work (an overlap is
377
+ named, never overwritten), and a patch after it carries the work alone;
378
+ `carveout widen` cuts the folder again, wider, with the work in it.
569
379
  - `carveout check` passing means the folder installs, builds, loads every
570
380
  export outside code uses and keeps their types. It tests the slice's
571
381
  behaviour only when the cut was made with `--with-tests`, and then only
@@ -577,3 +387,7 @@ What to tell them, accurately:
577
387
  can still fail. The repository's CI decides.
578
388
  - `gate` names what a reviewer should read; it does not judge. A rewritten
579
389
  assertion may be the change that was asked for.
390
+
391
+ ## License
392
+
393
+ MIT. See [LICENSE](LICENSE) and [NOTICE.md](NOTICE.md).