carveout 0.1.1 → 0.2.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/AGENTS.md +21 -3
- package/NOTICE.md +2 -1
- package/README.md +248 -473
- package/dist/src/account.d.ts +37 -0
- package/dist/src/account.js +275 -0
- package/dist/src/api.d.ts +162 -0
- package/dist/src/api.js +134 -0
- package/dist/src/carve.d.ts +788 -0
- package/dist/src/carve.js +1721 -125
- package/dist/src/cli.d.ts +10 -0
- package/dist/src/cli.js +674 -47
- package/dist/src/command.d.ts +10 -0
- package/dist/src/core.d.ts +11 -0
- package/dist/src/cut.d.ts +78 -0
- package/dist/src/cut.js +25 -7
- package/dist/src/extract/compiler-config.d.ts +49 -0
- package/dist/src/extract/fixture.d.ts +103 -0
- package/dist/src/extract/hash.d.ts +6 -0
- package/dist/src/extract/package-registry.d.ts +47 -0
- package/dist/src/extract/package-types.d.ts +52 -0
- package/dist/src/extract/surface.d.ts +44 -0
- package/dist/src/extract/typescript.d.ts +180 -0
- package/dist/src/gate.d.ts +55 -0
- package/dist/src/gate.js +1 -0
- package/dist/src/graph.d.ts +142 -0
- package/dist/src/graph.js +4 -3
- package/dist/src/impact.d.ts +113 -0
- package/dist/src/imports.d.ts +67 -0
- package/dist/src/mergeback.d.ts +182 -0
- package/dist/src/mergeback.js +580 -35
- package/dist/src/platform.d.ts +69 -0
- package/dist/src/platform.js +184 -0
- package/dist/src/policy.d.ts +181 -0
- package/dist/src/policy.js +716 -230
- package/dist/src/project/build.d.ts +23 -0
- package/dist/src/project/catalog.d.ts +25 -0
- package/dist/src/project/project.d.ts +287 -0
- package/dist/src/project/project.js +479 -52
- package/dist/src/project/provenance.d.ts +73 -0
- package/dist/src/project/provenance.js +12 -0
- package/dist/src/project/runner.d.ts +19 -0
- package/dist/src/project/secrets.d.ts +64 -0
- package/dist/src/project/vitest-config.d.ts +70 -0
- package/dist/src/project/vitest-config.js +291 -0
- package/dist/src/pseudonyms/aliases.d.ts +107 -0
- package/dist/src/pseudonyms/aliases.js +394 -0
- package/dist/src/pseudonyms/draft.d.ts +64 -0
- package/dist/src/pseudonyms/draft.js +284 -0
- package/dist/src/pseudonyms/folder.d.ts +172 -0
- package/dist/src/pseudonyms/folder.js +527 -0
- package/dist/src/pseudonyms/index.d.ts +11 -0
- package/dist/src/pseudonyms/index.js +11 -0
- package/dist/src/pseudonyms/vocabulary.d.ts +113 -0
- package/dist/src/pseudonyms/vocabulary.js +522 -0
- package/dist/src/readiness.d.ts +71 -0
- package/dist/src/readiness.js +131 -0
- package/dist/src/receipt.d.ts +154 -0
- package/dist/src/receipt.js +296 -0
- package/dist/src/sandbox.d.ts +53 -0
- package/dist/src/sandbox.js +154 -0
- package/dist/src/slice/analysis.d.ts +75 -0
- package/dist/src/slice/externals.d.ts +51 -0
- package/dist/src/slice/graph.d.ts +69 -0
- package/dist/src/slice/manifest.d.ts +262 -0
- package/dist/src/slice/manifest.js +4 -3
- package/dist/src/slice/reach.d.ts +204 -0
- package/dist/src/slice/reach.js +11 -5
- package/dist/src/slice/report.d.ts +49 -0
- package/dist/src/slice/report.js +5 -2
- package/dist/src/specifiers.d.ts +48 -0
- package/dist/src/status.d.ts +46 -0
- package/dist/src/status.js +3 -2
- package/dist/src/test-integrity.d.ts +54 -0
- package/dist/src/test-integrity.js +812 -0
- package/package.json +24 -5
- package/skills/carveout/SKILL.md +518 -0
- package/skills/carveout-gate/SKILL.md +98 -0
- package/SKILL.md +0 -423
package/README.md
CHANGED
|
@@ -1,435 +1,241 @@
|
|
|
1
1
|
<h1 align="center">carveout</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<strong>
|
|
5
|
-
|
|
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.
|
|
6
7
|
</p>
|
|
7
8
|
|
|
8
9
|
<p align="center">
|
|
9
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>
|
|
10
11
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="MIT license"></a>
|
|
11
|
-
<a href="SKILL.md"><img src="https://img.shields.io/badge/Claude_Code-skill-D97757?style=flat-square" alt="Claude Code skill"></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>
|
|
12
14
|
<a href="#limits"><img src="https://img.shields.io/badge/TypeScript-repositories-3178C6?style=flat-square" alt="TypeScript repositories"></a>
|
|
13
15
|
</p>
|
|
14
16
|
|
|
15
17
|
<p align="center">
|
|
16
|
-
<img src="assets/hero.png" width="100%" alt="
|
|
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.">
|
|
17
19
|
</p>
|
|
18
20
|
|
|
19
21
|
<table>
|
|
20
22
|
<tr>
|
|
21
|
-
<td align="center" width="25%"><h3>
|
|
22
|
-
<td align="center" width="25%"><h3>0 lines</h3>of their code run<br>
|
|
23
|
-
<td align="center" width="25%"><h3>52 of 52</h3>
|
|
24
|
-
<td align="center" width="25%"><h3>
|
|
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>
|
|
25
27
|
</tr>
|
|
26
28
|
</table>
|
|
27
29
|
|
|
28
30
|
<p align="center">
|
|
29
|
-
<a href="#
|
|
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> •
|
|
30
36
|
<a href="#commands">Commands</a> •
|
|
31
|
-
<a href="#also-checking-a-change-in-the-repository">Impact and gate</a> •
|
|
32
|
-
<a href="#experiments">Experiments</a> •
|
|
33
37
|
<a href="#limits">Limits</a> •
|
|
34
|
-
<a href="
|
|
38
|
+
<a href="docs/REFERENCE.md">Reference</a>
|
|
35
39
|
</p>
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
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.
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
a depth you choose, into a new folder at the same paths. Every module it
|
|
42
|
-
imports from the rest of the repository becomes a `.d.ts` stub with a
|
|
43
|
-
recording mock behind it, so the folder installs, builds and tests on its
|
|
44
|
-
own: the contractor gets the slice's source and the declarations of what it
|
|
45
|
-
uses, never the bodies behind them. When the folder comes back,
|
|
46
|
-
`carveout patch` turns the edits into a patch for the repository, runs none
|
|
47
|
-
of the contractor's code, and keeps out what they should not slip in: CI
|
|
48
|
-
and hook files, manifests and lockfiles, deleted or weakened tests, new
|
|
49
|
-
files in other packages.
|
|
45
|
+
---
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
# maintainer, at the repository's root
|
|
53
|
-
npx -y carveout plan apps/api/src/billing --depth 1 --with-tests # what the folder would hold; writes nothing
|
|
54
|
-
npx -y carveout cut apps/api/src/billing --depth 1 --with-tests --public --out ../billing-slice
|
|
55
|
-
npx -y carveout check ../billing-slice # npm ci, build, test, as the contractor will
|
|
47
|
+
## Before / After
|
|
56
48
|
|
|
57
|
-
|
|
49
|
+
<table>
|
|
50
|
+
<tr>
|
|
51
|
+
<td width="50%">
|
|
52
|
+
|
|
53
|
+
### 🔓 Without carveout: they get the repo
|
|
58
54
|
|
|
59
|
-
# maintainer, on the folder that came back
|
|
60
|
-
npx -y carveout status ../billing-slice # does the repository still match the cut?
|
|
61
|
-
npx -y carveout patch ../billing-slice --output billing.patch # runs none of its code
|
|
62
|
-
git apply --3way billing.patch
|
|
63
|
-
# or both, with a check: carveout merge ../billing-slice --into . --verify "npm test"
|
|
64
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
|
+
```
|
|
61
|
+
|
|
62
|
+
</td>
|
|
63
|
+
<td width="50%">
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
### 🔒 With carveout: they get one folder
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
75
|
+
```
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
</td>
|
|
78
|
+
</tr>
|
|
79
|
+
<tr>
|
|
80
|
+
<td width="50%">
|
|
81
|
+
|
|
82
|
+
### ✅ The work comes back
|
|
83
|
+
|
|
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
|
+
```
|
|
92
|
+
|
|
93
|
+
</td>
|
|
94
|
+
<td width="50%">
|
|
95
|
+
|
|
96
|
+
### 🚫 A shortcut does not
|
|
97
|
+
|
|
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>
|
|
83
111
|
|
|
84
|
-
|
|
112
|
+
**Same task. 15 files out of 709. The shortcut held before anything ran
|
|
113
|
+
it.**
|
|
85
114
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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.
|
|
122
|
+
|
|
123
|
+
## Install
|
|
89
124
|
|
|
90
125
|
```sh
|
|
91
|
-
|
|
92
|
-
carveout plan packages/api/src/quote.ts --depth 1 --with-tests --whole-package --copy-pure
|
|
93
|
-
carveout cut packages/api/src/quote.ts --depth 1 --with-tests --whole-package --copy-pure \
|
|
94
|
-
--public --out ../quote-slice
|
|
95
|
-
carveout check ../quote-slice # read what it holds before sending it
|
|
96
|
-
# contractor: npm ci, edit, npm test, send the folder back
|
|
97
|
-
# maintainer, on the folder that came back
|
|
98
|
-
carveout patch ../returned --output quote.patch # runs none of its code
|
|
99
|
-
less quote.patch # read all of it before anything runs it
|
|
100
|
-
git switch -c contrib/quote && git apply --3way quote.patch
|
|
101
|
-
carveout impact --base HEAD # the importers and tests the contractor never saw
|
|
102
|
-
# then a PR: CI decides
|
|
126
|
+
npx skills add lunox-work/carveout -g
|
|
103
127
|
```
|
|
104
128
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
with its lines, tests not counted: each folder there is a `--private`
|
|
119
|
-
decision to make before the folder is sent.
|
|
120
|
-
- **The folder that comes back.** Nothing in it is trusted. `patch` and
|
|
121
|
-
`apply` refuse a `carveout.json` that names a path outside the folder or
|
|
122
|
-
a `.git` path, or lists as the slice's a file the repository does not
|
|
123
|
-
hold at those bytes, and work against the checkout given with `--repo`
|
|
124
|
-
(or the current directory), which must hold the cut's commit. `patch`
|
|
125
|
-
runs none of the folder's code.
|
|
126
|
-
- **The hand-off policy.** The patch carries edits to the slice's files and
|
|
127
|
-
new files in the slice's packages. It leaves out, names, and exits 6 on:
|
|
128
|
-
new files in other packages (a cut module's folder among them);
|
|
129
|
-
protected paths (CI workflows, hooks, `.vscode`, `.devcontainer`,
|
|
130
|
-
`package.json`, lockfiles, `.npmrc`, `.env*`, Dockerfiles, Makefiles,
|
|
131
|
-
`*.sh`, `CODEOWNERS`, `carveout.config.json`); deleted tests; and edited
|
|
132
|
-
tests that remove or rewrite an assertion, skip or focus (`.only`) a
|
|
133
|
-
test, or leave an assertion unreachable or in a `catch` that swallows it
|
|
134
|
-
(an expected value changed may be the change itself, or a test bent to
|
|
135
|
-
pass: the maintainer reads it); and a script, new or edited, that gains
|
|
136
|
-
a reach it did not have: Node's disk, network or process modules, a
|
|
137
|
-
service SDK, `fetch`, `eval`, the environment, or a module or global
|
|
138
|
-
named by a computed value (a returned test that reads the closed source
|
|
139
|
-
and sends it out is one). `--allow <path|glob>` lets a reviewed one
|
|
140
|
-
through. `merge` applies nothing while any is refused. The policy reads
|
|
141
|
-
syntax, not intent: a new runner configuration (`vitest.config.ts`), or
|
|
142
|
-
code that does harm with only the reach its file already had, passes it.
|
|
143
|
-
- **Running what came back.** The contributor's tests, and every new file
|
|
144
|
-
the patch carries, are code that runs beside the whole repository, the
|
|
145
|
-
part the slice kept closed included. A branch pushed to the repository
|
|
146
|
-
runs CI with the repository's secrets and network, which a fork's pull
|
|
147
|
-
request does not: code there can read the source and send it out. Read
|
|
148
|
-
the whole patch before anything runs it, or run its first CI with no
|
|
149
|
-
secrets and no outbound network. `check --isolated` gives the folder a
|
|
150
|
-
scratch HOME and npm cache and no install scripts, and `merge --verify`
|
|
151
|
-
a scratch HOME and only the variables named with `--verify-env`:
|
|
152
|
-
neither is a sandbox, and `merge --verify` runs in the full checkout.
|
|
153
|
-
|
|
154
|
-
What it does not promise:
|
|
155
|
-
|
|
156
|
-
- **What a pass in the folder covers is stated.** `plan`, `cut` and
|
|
157
|
-
BOUNDARY.md name every test that runs the slice's code and stays in the
|
|
158
|
-
repository, and why (not asked for, another package's, Jest, the file
|
|
159
|
-
limit). When none is in the folder, a pass means the code builds and
|
|
160
|
-
keeps its exports, nothing more.
|
|
161
|
-
- **Passing in the slice is a pre-check, not the merge gate.** The slice's
|
|
162
|
-
tests run against mocks of everything it cuts. A test that needs a cut
|
|
163
|
-
module's real behaviour fails at the slice's baseline and is not counted,
|
|
164
|
-
and a test that imports `carveout/` to make a mock answer stays in the
|
|
165
|
-
folder: the change comes back without it, and `patch` says how many. In
|
|
166
|
-
the experiments, verdicts in the slice and after the merge agreed in 74%
|
|
167
|
-
of cases, nearly all disagreements in the safe direction (the slice
|
|
168
|
-
failed, the repository passed). `--copy-pure` turns most such baselines
|
|
169
|
-
green, and the surface check catches a changed export the importers in
|
|
170
|
-
other packages rely on; neither runs those importers. Those figures come
|
|
171
|
-
from earlier CLIs: the model-run benchmarks have not been run again since
|
|
172
|
-
`--copy-pure`, the surface check and the barrel-chain rule. The
|
|
173
|
-
repository's CI decides, once the patch has been read.
|
|
174
|
-
- **The slice's own source is the contributor's to read.** Choose the
|
|
175
|
-
entry points and `--private` paths with that in mind; `plan` lists every
|
|
176
|
-
file before anything is written.
|
|
177
|
-
|
|
178
|
-
## What a cut holds
|
|
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:
|
|
179
142
|
|
|
143
|
+
```sh
|
|
144
|
+
npx -y carveout handoff src/billing/quote.ts --task task.md --out ../quote-slice
|
|
180
145
|
```
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
146
|
+
|
|
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`.
|
|
150
|
+
|
|
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
|
|
157
|
+
|
|
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
|
|
196
164
|
```
|
|
197
165
|
|
|
198
|
-
|
|
199
|
-
own declarations, and narrowed to the names the slice uses.
|
|
200
|
-
- **Workspace packages** are stubbed from their source rather than
|
|
201
|
-
installed, unless the cut follows them (`--follow-workspaces`): then an
|
|
202
|
-
import of one is an import of its source file, copied or cut like any
|
|
203
|
-
module. Either way, a file in another package that imports the slice by
|
|
204
|
-
the package's name is one of its importers.
|
|
205
|
-
- **Installed packages** are those the repository pins from the public
|
|
206
|
-
registry. Each comes with its `@types` and its required peers, at the
|
|
207
|
-
repository's versions.
|
|
208
|
-
- **Mock packages** stand in for service SDKs (payments, cloud, databases,
|
|
209
|
-
queues, email, HTTP clients, LLM SDKs) and for private-registry packages.
|
|
210
|
-
- **Import rewrites.** Path aliases, workspace imports and bundler-style
|
|
211
|
-
extensionless imports become relative paths Node resolves. Each is
|
|
212
|
-
recorded, and `patch` undoes it.
|
|
213
|
-
- **Assets.** Stylesheets and images a module imports are copied, and load
|
|
214
|
-
in Node as their path. `import.meta.glob` and `import.meta.env` work
|
|
215
|
-
under Node.
|
|
216
|
-
- **The repository's checks.** The compiler options that only reject
|
|
217
|
-
code (`strict`, `noUnusedLocals` and the like) come from the
|
|
218
|
-
repository's tsconfig. `noImplicitAny` too, unless the slice does not
|
|
219
|
-
compile under it with its stubs and untyped packages, which the cut says. Node's types come at the repository's version.
|
|
220
|
-
When the package's own test sets coverage thresholds, `npm test` holds
|
|
221
|
-
the source files you add to them, and fails, as the package's script
|
|
222
|
-
does, on a file you add that no test loads (`assert-all-covered`).
|
|
223
|
-
- **The public surface.** Every export code outside the slice imports
|
|
224
|
-
must stay exported (the interface test) and keep its type: `npm test`
|
|
225
|
-
type-checks `carveout/surface/check.ts`, which holds each such value to
|
|
226
|
-
its type at the cut and each such type to its shape both ways, and
|
|
227
|
-
names the export whose type changed. Enums and classes with private
|
|
228
|
-
members are not checked; a check that fails before any edit is not
|
|
229
|
-
counted.
|
|
230
|
-
- **Credentials.** A credentials file is never copied. A credential found
|
|
231
|
-
in anything the cut would write, a `--copy` folder or the resolved
|
|
232
|
-
lockfile included, stops the cut: nothing is written.
|
|
233
|
-
|
|
234
|
-
The plan and the cut read the repository and never run its code. The cut
|
|
235
|
-
fetches package types from the public registry with install scripts off,
|
|
236
|
-
and resolves a lockfile; when the repository has an npm lockfile, the
|
|
237
|
-
slice starts from its locked versions. `--offline` skips both.
|
|
238
|
-
|
|
239
|
-
## What the walk follows
|
|
240
|
-
|
|
241
|
-
- **Names, not whole files.** Importing `a` from a barrel brings the barrel
|
|
242
|
-
and the module that declares `a`, at the barrel's depth. The barrel's
|
|
243
|
-
other re-exports are cut.
|
|
244
|
-
- **Code, not types.** A module the slice takes only types from
|
|
245
|
-
(`import type`, or an interface or type alias by name) is cut to its
|
|
246
|
-
stub and never copied.
|
|
247
|
-
- **Files read by path.** `new URL("./worker.ts", import.meta.url)` and
|
|
248
|
-
`join(__dirname, "get.sql")` copy the file. A folder read by path, or such
|
|
249
|
-
a file left outside the slice, is a `path_read` blocker.
|
|
250
|
-
- **Modules named to a test runner** (`vi.mock`, `jest.requireActual`,
|
|
251
|
-
`require.resolve`) are cut, not followed. Their specifiers are rewritten
|
|
252
|
-
like imports.
|
|
253
|
-
- **The file budget.** When it runs out, `plan` lists the files it left out.
|
|
254
|
-
- **The whole package**, with `--whole-package`: every file of the entry
|
|
255
|
-
points' package the imports reach comes along at any depth, so the cut
|
|
256
|
-
falls at package and service boundaries, and a sibling module is copied
|
|
257
|
-
rather than stubbed. A barrel's re-exports come only when they supply a
|
|
258
|
-
name the slice asks of it. Imports into other packages still stop at the
|
|
259
|
-
depth.
|
|
260
|
-
- **Barrel chains.** A test that takes a slice name through barrels
|
|
261
|
-
(`src/index.ts` → `pricing/index.ts` → `pricing/weight.ts`) is a test of
|
|
262
|
-
the slice: `--with-tests` brings it and the whole chain, each barrel cut
|
|
263
|
-
to what it forwards from the slice.
|
|
264
|
-
- **A test's helpers.** A test brought along by `--with-tests` brings what
|
|
265
|
-
it imports at runtime from its own package, or from a test folder
|
|
266
|
-
anywhere (`test/`, `__mocks__/`, `fixtures/`): a fake copied is a test
|
|
267
|
-
that passes, a fake stubbed is one that fails before any change.
|
|
268
|
-
- **Private paths** (`--private <path>`, or `private` in the
|
|
269
|
-
configuration) are never copied: wherever the walk reaches one it is
|
|
270
|
-
cut and stubbed, with literal values widened.
|
|
271
|
-
- **Pure code**, with `--copy-pure`: a module the slice runs from outside
|
|
272
|
-
it is copied rather than mocked, past the depth and across packages,
|
|
273
|
-
when nothing its runtime imports reach outside the slice imports a
|
|
274
|
-
service SDK, Node's disk, network or process modules, reads the
|
|
275
|
-
environment or a path, or is private. A mocked validator or error
|
|
276
|
-
class answers with more mocks, and the tests that use it fail before
|
|
277
|
-
any change; copied, they run as in the repository. Through a barrel,
|
|
278
|
-
only the modules that supply the names come. The copied code is the
|
|
279
|
-
contributor's to read: `plan` lists it.
|
|
280
|
-
|
|
281
|
-
Only the files the walk needs are parsed. Files that may import the slice
|
|
282
|
-
are found by name first, and the compiler confirms each one.
|
|
166
|
+
## How it works
|
|
283
167
|
|
|
284
|
-
|
|
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>
|
|
285
171
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
defaults apply otherwise. For tests that need the service itself, such
|
|
341
|
-
as a database driver against a local server;
|
|
342
|
-
- `--widen-literals`, which writes `string` in stubs in place of literal
|
|
343
|
-
values. A constant the stub reads as a type (`type Kind = (typeof
|
|
344
|
-
KINDS)[number]`) keeps its name-like strings (`"github"`, `"draft"`):
|
|
345
|
-
they are that type's members, and widened, the slice would stop checking
|
|
346
|
-
the code that relies on them. Its numbers, URLs and sentences widen;
|
|
347
|
-
- `--public`, for a folder handed to someone outside: literals widened,
|
|
348
|
-
and neither the checkout's path or folder name nor the files outside the
|
|
349
|
-
slice that import it written (each importer is named by its package
|
|
350
|
-
folder; compiler messages name files from the repository's root). The
|
|
351
|
-
commands that need the repository then take `--repo`, defaulting to the
|
|
352
|
-
current directory;
|
|
353
|
-
- `--offline`.
|
|
354
|
-
|
|
355
|
-
Options for `patch`, `status`, `apply` and `merge`: `--repo <checkout>`,
|
|
356
|
-
the repository to work against (it must hold the cut's commit; `merge`
|
|
357
|
-
uses `--into`). For `patch` and `merge`: `--allow <path|glob>`, which lets
|
|
358
|
-
a path the hand-off policy refuses through. For `merge`: `--verify-env
|
|
359
|
-
<name>`, an environment variable the verify command is given.
|
|
360
|
-
|
|
361
|
-
Exit codes: 0 done, 1 refused or failed, 2 written with blockers (`cut`)
|
|
362
|
-
or a step failed (`check`, `merge`, `impact --run`; `gate --hook` on a
|
|
363
|
-
hold), 3 nothing to patch, 4 stale (`status`), 5 the edits reach past the
|
|
364
|
-
slice, 6 the hand-off policy kept files out (`patch`, `merge`) or held a
|
|
365
|
-
change (`gate`).
|
|
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).
|
|
366
226
|
|
|
367
227
|
## Also: checking a change in the repository
|
|
368
228
|
|
|
369
|
-
The import graph a
|
|
370
|
-
|
|
371
|
-
involved: what does it reach, and what should a reviewer read before it
|
|
372
|
-
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:
|
|
373
231
|
|
|
374
232
|
```sh
|
|
375
|
-
npx -y carveout impact
|
|
376
|
-
npx -y carveout
|
|
377
|
-
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
|
|
378
235
|
```
|
|
379
236
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
| `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). |
|
|
383
|
-
| `carveout gate` | Holds a change's weakened or deleted tests, protected paths, check configuration and new suppressions (exit 6). `--base`, `--scope`, `--allow`, `--hook`. |
|
|
384
|
-
|
|
385
|
-
`impact` and `gate` read the change from Git: the base (default `HEAD`)
|
|
386
|
-
against the working tree, untracked files included. Neither runs the
|
|
387
|
-
repository's code, but for `impact --run`.
|
|
388
|
-
|
|
389
|
-
**`carveout impact`** reports:
|
|
390
|
-
|
|
391
|
-
- **Importers**: every file that imports a changed module, directly or
|
|
392
|
-
through others, found by name and confirmed by the compiler (aliases,
|
|
393
|
-
workspace package names, barrels, `vi.mock`). It follows names, as the
|
|
394
|
-
slice walk does: a barrel carries a change only through the names it
|
|
395
|
-
forwards from the changed module, and a module only through its exports
|
|
396
|
-
whose declarations reach a changed declaration. With a base, only the
|
|
397
|
-
exports the diff reaches start the search; named files start with all of
|
|
398
|
-
theirs.
|
|
399
|
-
- **Exports**: each export of a changed module whose type changed or that
|
|
400
|
-
is gone, as the TypeScript checker prints it at the base and now, one
|
|
401
|
-
level of members deep, and the files that take it.
|
|
402
|
-
- **Tests**: the tests among the importers, each with its runner
|
|
403
|
-
(`node:test`, Vitest or Jest; otherwise the package's own `npm test`),
|
|
404
|
-
and one command per package that runs just them. The commands run only
|
|
405
|
-
installed binaries (`npx --no`).
|
|
406
|
-
- **`--run`** runs those commands one at a time, in the repository, with
|
|
407
|
-
your environment, and exits 2 when one fails. `--depth N` bounds the hops;
|
|
408
|
-
`--json` gives it all as data.
|
|
409
|
-
|
|
410
|
-
On sandbox-factory (1,288 files) it takes under 1.5 s. A change to one
|
|
411
|
-
function of `packages/core/src/stages.ts` selects 21 of the repository's
|
|
412
|
-
241 tests, among them the API test that pins that function's message, two
|
|
413
|
-
hops away; naming the whole file selects 156.
|
|
414
|
-
|
|
415
|
-
**`carveout gate`** holds for review, and exits 6 on any:
|
|
416
|
-
|
|
417
|
-
- protected paths (CI workflows, hooks, manifests, lockfiles, `.env*`,
|
|
418
|
-
Dockerfiles; the hand-off policy's list above);
|
|
419
|
-
- configuration a check reads, when edited: `tsconfig*.json`, ESLint,
|
|
420
|
-
Biome, Vitest, Jest, coverage settings;
|
|
421
|
-
- a deleted test, and a test whose assertions were removed or rewritten,
|
|
422
|
-
skipped, focused with `.only`, made unreachable (an early `return`,
|
|
423
|
-
`if (false)`), or wrapped in a `catch` that swallows their failure;
|
|
424
|
-
- new suppressions in a script: `@ts-ignore`, `@ts-expect-error`,
|
|
425
|
-
`eslint-disable`, coverage ignores;
|
|
426
|
-
- with `--scope <path|glob>`, any file outside it.
|
|
427
|
-
|
|
428
|
-
A hold is a question for a reviewer, not a verdict: a change asked to
|
|
429
|
-
alter a behaviour rewrites the assertion that held it. `--allow
|
|
430
|
-
<path|glob>` lets a reviewed file through. As a Claude Code Stop hook,
|
|
431
|
-
`--hook` sends the holds back to the agent (exit 2) once per stop, asking
|
|
432
|
-
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:
|
|
433
239
|
|
|
434
240
|
```json
|
|
435
241
|
{
|
|
@@ -445,99 +251,46 @@ it to undo what the task did not ask for and to name the rest in its reply:
|
|
|
445
251
|
}
|
|
446
252
|
```
|
|
447
253
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
`carveout
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
`
|
|
469
|
-
|
|
470
|
-
The lazy graph walks as the whole graph does: identical results in 722
|
|
471
|
-
walks over five repositories, cal.com and n8n among them. On n8n (24k
|
|
472
|
-
files) the median walk takes 2.3 s against 13.7 s for the whole graph. A
|
|
473
|
-
slice whose files have common names can read most of the repository, and
|
|
474
|
-
then takes as long.
|
|
475
|
-
|
|
476
|
-
On n8n (pnpm, catalogs, Vitest globals), a one-module slice installs,
|
|
477
|
-
builds and passes `check`, where it once stopped at the install on a
|
|
478
|
-
`catalog:` pin; with `--with-tests` it carries five Vitest tests, and its
|
|
479
|
-
build then fails on limits named below (an `override` of a mocked base
|
|
480
|
-
class, `never[]` under the relaxed `noImplicitAny`).
|
|
481
|
-
|
|
482
|
-
## Experiments
|
|
483
|
-
|
|
484
|
-
`experiments/` holds the pre-registered experiments and their data
|
|
485
|
-
([RESULTS.md](experiments/RESULTS.md)).
|
|
486
|
-
|
|
487
|
-
- **What a slice shows.** Over the eight sandbox-factory slices in
|
|
488
|
-
`slices.json`, a default cut copies a median of 0.7% of the repository's
|
|
489
|
-
245k lines of TypeScript as source (0.03 to 2.4%); with
|
|
490
|
-
`--whole-package --with-tests --copy-pure`, 11% (0.3 to 24%)
|
|
491
|
-
([hero-exposure.mjs](experiments/hero-exposure.mjs)).
|
|
492
|
-
- **Agents in a slice (Phases 5 to 8).** An agent given a slice instead of
|
|
493
|
-
the repository did not get cheaper or better: it read 17 to 24% more
|
|
494
|
-
input, passed hidden tests 5 to 10 points less often, and a weaker model
|
|
495
|
-
did as well in the repository as in a slice. A full-repository agent read
|
|
496
|
-
a median of two files outside the slice. The slice's measured wins are
|
|
497
|
-
the ones without a model: setup at 0.35× the time and 0.12× the disk, and
|
|
498
|
-
a bounded view. That is why the slice is for someone who must not see
|
|
499
|
-
the rest, not a way to make an agent cheaper, and why `impact` and
|
|
500
|
-
`gate` work in the repository itself.
|
|
501
|
-
- **`status` (E1).** Over 345 later commits it never said `current` when a
|
|
502
|
-
fresh cut differed.
|
|
503
|
-
- **Three-way patches (E2).** Applied to a repository that had moved, a
|
|
504
|
-
patch applied plain in 27% of cases and with `git apply --3way` in 89%.
|
|
505
|
-
Every three-way result typechecked.
|
|
506
|
-
- **Co-change hints in `plan` (E3).** Precision was too low, so they were
|
|
507
|
-
dropped.
|
|
508
|
-
- **Graph time (E4).** The graph of a 24k-file monorepo takes 12 s, down
|
|
509
|
-
from 7.5 minutes before a fix that leaves the graph identical. A cache
|
|
510
|
-
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.
|
|
511
276
|
|
|
512
277
|
## Limits
|
|
513
278
|
|
|
514
|
-
- **
|
|
515
|
-
|
|
516
|
-
-
|
|
517
|
-
inferred from a package carveout could not read. The cut reports this; it
|
|
518
|
-
does not hide it.
|
|
519
|
-
- **Lockfiles.** Peers and locked versions are read from an npm
|
|
520
|
-
`package-lock.json`. pnpm and Yarn repositories fall back to the versions
|
|
521
|
-
in their package.json files; a `catalog:` pin takes the catalog's
|
|
522
|
-
version (`pnpm-workspace.yaml`, or `catalog(s)` in the root
|
|
523
|
-
package.json). A pnpm lock entry with no tarball URL shows a package is
|
|
524
|
-
public only when its integrity equals the public registry's for that
|
|
525
|
-
version (checked online; offline it is mocked). Such folders resolve
|
|
526
|
-
peers as pnpm and Yarn do (`legacy-peer-deps`).
|
|
527
|
-
- **Test runners.** `node:test` and Vitest. A Jest test is named as left
|
|
528
|
-
behind, never run.
|
|
529
|
-
- **Folders named like build output.** Files under `build/`, `dist/`,
|
|
530
|
-
`vendor/` and `third_party/` are not parsed: a source folder named
|
|
531
|
-
`build` is copied when reached, but its own imports are not followed.
|
|
532
|
-
- **What `impact` can miss.** It follows imports, not runtime: a module
|
|
533
|
-
loaded by a computed name, a file read by path, or configuration a test
|
|
534
|
-
reads is not traced, and a test it does not list can still fail. Inside a
|
|
535
|
-
module it traces names by the identifiers each top-level statement uses,
|
|
536
|
-
which errs toward more. A type is compared one level of members deep: a
|
|
537
|
-
change inside a named type shows on that type's own module. Its
|
|
538
|
-
`node:test` command for TypeScript uses Node's type stripping, which does
|
|
539
|
-
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
|
|
540
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).
|
|
541
294
|
|
|
542
295
|
<!-- Keep this section and AGENTS.md the same. -->
|
|
543
296
|
|
|
@@ -562,8 +315,9 @@ the person:
|
|
|
562
315
|
|
|
563
316
|
For a slice, run `npx -y carveout plan <entry> --depth 1` first. Show the
|
|
564
317
|
person what the slice would hold and what it cuts, and agree the entry
|
|
565
|
-
points and the depth with them. Then `cut` and `check
|
|
566
|
-
|
|
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.
|
|
567
321
|
|
|
568
322
|
For a change in the repository, run `npx -y carveout impact` (add
|
|
569
323
|
`--base <branch>` for committed work) and report the exports, importers and
|
|
@@ -597,14 +351,31 @@ What to tell them, accurately:
|
|
|
597
351
|
- `--copy-pure` copies the pure code the slice runs instead of mocking
|
|
598
352
|
it, so the folder's tests pass before any change; it shows the
|
|
599
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.
|
|
600
360
|
- For someone outside the repository, cut with `--public`, take the folder
|
|
601
361
|
back with `carveout patch` (it runs none of the folder's code and keeps
|
|
602
362
|
out CI, hook, manifest and lockfile files, new files in other packages,
|
|
603
|
-
deleted tests
|
|
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
|
|
604
365
|
environment), read the whole patch before anything runs it (its
|
|
605
366
|
tests and new files are code beside the closed repository, and a branch
|
|
606
367
|
pushed there gets CI's secrets and network), then let the repository's
|
|
607
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.
|
|
608
379
|
- `carveout check` passing means the folder installs, builds, loads every
|
|
609
380
|
export outside code uses and keeps their types. It tests the slice's
|
|
610
381
|
behaviour only when the cut was made with `--with-tests`, and then only
|
|
@@ -616,3 +387,7 @@ What to tell them, accurately:
|
|
|
616
387
|
can still fail. The repository's CI decides.
|
|
617
388
|
- `gate` names what a reviewer should read; it does not judge. A rewritten
|
|
618
389
|
assertion may be the change that was asked for.
|
|
390
|
+
|
|
391
|
+
## License
|
|
392
|
+
|
|
393
|
+
MIT. See [LICENSE](LICENSE) and [NOTICE.md](NOTICE.md).
|