carveout 0.1.1 → 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 +248 -473
  4. package/dist/src/account.d.ts +37 -0
  5. package/dist/src/account.js +275 -0
  6. package/dist/src/api.d.ts +148 -0
  7. package/dist/src/api.js +120 -0
  8. package/dist/src/carve.d.ts +788 -0
  9. package/dist/src/carve.js +1721 -125
  10. package/dist/src/cli.d.ts +10 -0
  11. package/dist/src/cli.js +674 -47
  12. package/dist/src/command.d.ts +10 -0
  13. package/dist/src/core.d.ts +11 -0
  14. package/dist/src/cut.d.ts +78 -0
  15. package/dist/src/cut.js +25 -7
  16. package/dist/src/extract/compiler-config.d.ts +49 -0
  17. package/dist/src/extract/fixture.d.ts +103 -0
  18. package/dist/src/extract/hash.d.ts +6 -0
  19. package/dist/src/extract/package-registry.d.ts +47 -0
  20. package/dist/src/extract/package-types.d.ts +52 -0
  21. package/dist/src/extract/surface.d.ts +44 -0
  22. package/dist/src/extract/typescript.d.ts +180 -0
  23. package/dist/src/gate.d.ts +55 -0
  24. package/dist/src/gate.js +1 -0
  25. package/dist/src/graph.d.ts +142 -0
  26. package/dist/src/graph.js +4 -3
  27. package/dist/src/impact.d.ts +113 -0
  28. package/dist/src/imports.d.ts +67 -0
  29. package/dist/src/mergeback.d.ts +182 -0
  30. package/dist/src/mergeback.js +580 -35
  31. package/dist/src/platform.d.ts +69 -0
  32. package/dist/src/platform.js +184 -0
  33. package/dist/src/policy.d.ts +181 -0
  34. package/dist/src/policy.js +716 -230
  35. package/dist/src/project/build.d.ts +23 -0
  36. package/dist/src/project/catalog.d.ts +25 -0
  37. package/dist/src/project/project.d.ts +287 -0
  38. package/dist/src/project/project.js +479 -52
  39. package/dist/src/project/provenance.d.ts +73 -0
  40. package/dist/src/project/provenance.js +12 -0
  41. package/dist/src/project/runner.d.ts +19 -0
  42. package/dist/src/project/secrets.d.ts +64 -0
  43. package/dist/src/project/vitest-config.d.ts +70 -0
  44. package/dist/src/project/vitest-config.js +291 -0
  45. package/dist/src/pseudonyms/aliases.d.ts +107 -0
  46. package/dist/src/pseudonyms/aliases.js +394 -0
  47. package/dist/src/pseudonyms/draft.d.ts +64 -0
  48. package/dist/src/pseudonyms/draft.js +284 -0
  49. package/dist/src/pseudonyms/folder.d.ts +172 -0
  50. package/dist/src/pseudonyms/folder.js +527 -0
  51. package/dist/src/pseudonyms/index.d.ts +11 -0
  52. package/dist/src/pseudonyms/index.js +11 -0
  53. package/dist/src/pseudonyms/vocabulary.d.ts +113 -0
  54. package/dist/src/pseudonyms/vocabulary.js +522 -0
  55. package/dist/src/readiness.d.ts +71 -0
  56. package/dist/src/readiness.js +131 -0
  57. package/dist/src/receipt.d.ts +154 -0
  58. package/dist/src/receipt.js +296 -0
  59. package/dist/src/sandbox.d.ts +53 -0
  60. package/dist/src/sandbox.js +154 -0
  61. package/dist/src/slice/analysis.d.ts +75 -0
  62. package/dist/src/slice/externals.d.ts +51 -0
  63. package/dist/src/slice/graph.d.ts +69 -0
  64. package/dist/src/slice/manifest.d.ts +262 -0
  65. package/dist/src/slice/manifest.js +4 -3
  66. package/dist/src/slice/reach.d.ts +204 -0
  67. package/dist/src/slice/reach.js +11 -5
  68. package/dist/src/slice/report.d.ts +49 -0
  69. package/dist/src/slice/report.js +5 -2
  70. package/dist/src/specifiers.d.ts +48 -0
  71. package/dist/src/status.d.ts +46 -0
  72. package/dist/src/status.js +3 -2
  73. package/dist/src/test-integrity.d.ts +54 -0
  74. package/dist/src/test-integrity.js +812 -0
  75. package/package.json +24 -5
  76. package/skills/carveout/SKILL.md +517 -0
  77. package/skills/carveout-gate/SKILL.md +98 -0
  78. package/SKILL.md +0 -423
@@ -0,0 +1,180 @@
1
+ import { type SurfaceFile } from "./surface.js";
2
+ export { declarationPath as declarationPathFor, packageNameOf, } from "../core.js";
3
+ export { loadConfig } from "./compiler-config.js";
4
+ import type { BoundaryModule, CutEdge, SliceBlocker } from "../core.js";
5
+ import ts from "typescript";
6
+ export declare const TYPESCRIPT_EXTRACTOR_VERSION: string;
7
+ export declare const ALL = "*";
8
+ export type RequestedNames = ReadonlySet<string> | typeof ALL;
9
+ export declare const STUB_CLOSURE_MAX = 500;
10
+ /**
11
+ * Repository files one extraction may parse. The compiler runs on the
12
+ * event loop; a program the size of a monorepo would hold it past the
13
+ * heartbeat and lose the lease, so the slice stops with a blocker instead.
14
+ */
15
+ export declare const PROGRAM_FILES_MAX = 2000;
16
+ /** Modules Node provides; never a package the sandbox must install. */
17
+ export declare const NODE_BUILTINS: readonly string[];
18
+ export declare function isScriptPath(path: string): boolean;
19
+ /**
20
+ * Files a script imports as data (`import data from "./data.json"`) or
21
+ * reads by path (a query, a schema, a template). They ride along in a
22
+ * TypeScript slice; code in any other language does not.
23
+ */
24
+ export declare function isDataPath(path: string): boolean;
25
+ export declare function isNodeBuiltin(specifier: string): boolean;
26
+ /** The stub's artifact path for a module: `stubs/<path>.d.ts`. */
27
+ export declare function stubPathFor(module: string): string;
28
+ export interface ImportFact {
29
+ readonly specifier: string;
30
+ readonly names: RequestedNames;
31
+ readonly line: number;
32
+ }
33
+ /**
34
+ * The names read off a namespace binding (`ns.a`, `ns.T` in a type), or
35
+ * `ALL` once it escapes: passed or returned as a value, re-exported, read
36
+ * with a computed key, taken as `typeof ns`, or shadowed by another
37
+ * binding of the same name. Only a namespace that never escapes can be
38
+ * stubbed with less than the whole module.
39
+ */
40
+ export declare function namespaceUses(nodes: readonly ts.Node[], name: string, binding: ts.Node): RequestedNames;
41
+ /** Every import a file makes, with the names it takes, from the syntax. */
42
+ export declare function importsOf(source: ts.SourceFile): ImportFact[];
43
+ /** Where a snapshot's bare specifiers resolve, having no node_modules. */
44
+ export interface ModuleFallbacks {
45
+ /** The repository path of one of its own packages; see `workspaceModuleResolver`. */
46
+ readonly workspaceModule?: (specifier: string) => string | null;
47
+ /** A directory whose node_modules holds fetched package types. */
48
+ readonly packageTypes?: string | null;
49
+ }
50
+ /**
51
+ * Resolves imports as the compiler does, then a bare specifier it cannot
52
+ * resolve as one of the repository's own packages, then from the fetched
53
+ * package types, as if imported beside their node_modules.
54
+ */
55
+ export declare function resolveModulesWith(host: ts.CompilerHost, root: string, options: ts.CompilerOptions, fallbacks: ModuleFallbacks): void;
56
+ /**
57
+ * A declaration file with the module specifiers `to` maps replaced, in
58
+ * imports, re-exports and `import("...")` types; the rest is left as is.
59
+ */
60
+ export declare function rewriteModuleSpecifiers(text: string, to: (specifier: string) => string | null): string;
61
+ /**
62
+ * A compiler host that reads only the source tree, the compiler's libs
63
+ * and the fetched package types.
64
+ */
65
+ export declare function boundedHost(root: string, options: ts.CompilerOptions, fallbacks?: ModuleFallbacks): ts.CompilerHost;
66
+ /** Options for declaration emit: the source's, with output settings forced. */
67
+ export declare function extractionOptions(base: ts.CompilerOptions): ts.CompilerOptions;
68
+ /**
69
+ * Options for a stub's declaration emit. A stub leaves the repository, so
70
+ * it carries no comments; the context builders, which stay private, keep
71
+ * them through `extractionOptions`. `@internal` declarations are emitted
72
+ * like any other, without their comments: the compiler's `stripInternal`
73
+ * would drop one the slice's code imports or a kept declaration names
74
+ * across the cut, and the stub would not declare what that code compiles
75
+ * against. `filterDeclaration` keeps only what is asked for and what that
76
+ * needs, so one nothing reaches is left out all the same.
77
+ */
78
+ export declare function stubExtractionOptions(base: ts.CompilerOptions): ts.CompilerOptions;
79
+ /**
80
+ * An emitted declaration file as a stub may show it. The emitter writes a
81
+ * literal constant's value (`declare const KEY = "sk_live_..."`, `static
82
+ * readonly KEY = "..."`), the literal types an `as const` value or an
83
+ * annotation spells it with, every comment it was not told to drop and
84
+ * each private member's name. A stub keeps each value's type with its
85
+ * literals widened to `string`, `number`, `bigint` or `boolean`, no
86
+ * comments, and private members only as one nameless brand, since no code
87
+ * outside the class can use them.
88
+ *
89
+ * A constant the file reads as a type (`(typeof PROVIDERS)[number]`, `kind:
90
+ * typeof KIND`) keeps its name-like string literals (`"github"`, `"draft"`):
91
+ * they are that type's members, and widened, the type would be `string` and
92
+ * the slice would stop checking the code that relies on it (a guard taken
93
+ * out compiles). Its other literals (numbers, URLs, keys, sentences) widen
94
+ * as any value's do.
95
+ *
96
+ * A string enum member's value becomes its own name: it stays a string
97
+ * enum, each member its own type, without the text it stood for. Numeric
98
+ * members keep their values, which an ambient enum needs for its members
99
+ * to be constant, and a number names nothing.
100
+ */
101
+ export declare function sanitizeDeclaration(text: string, options?: {
102
+ readonly widenLiterals?: boolean;
103
+ }): string;
104
+ export interface FilteredDeclaration {
105
+ readonly text: string;
106
+ /** Requested names that no kept statement exports and no `export *` could. */
107
+ readonly missing: readonly string[];
108
+ /** Names forwarded to `export *` targets because nothing local declared them. */
109
+ readonly forwarded: readonly {
110
+ specifier: string;
111
+ names: RequestedNames;
112
+ }[];
113
+ /** Modules the kept declarations import from, with the names taken. */
114
+ readonly imports: readonly {
115
+ specifier: string;
116
+ names: RequestedNames;
117
+ }[];
118
+ readonly symbols: readonly {
119
+ name: string;
120
+ kind: string;
121
+ declaration: string;
122
+ }[];
123
+ }
124
+ /**
125
+ * Narrows an emitted declaration file to the requested exports and what
126
+ * they need. `ALL` keeps the file whole.
127
+ */
128
+ export declare function filterDeclaration(text: string, requested: RequestedNames): FilteredDeclaration;
129
+ export interface TypeScriptBoundary {
130
+ readonly extractorVersion: string;
131
+ readonly outbound: readonly BoundaryModule[];
132
+ readonly inbound: readonly BoundaryModule[];
133
+ /** Declaration files standing in for cut modules, by artifact path. */
134
+ readonly stubs: readonly {
135
+ path: string;
136
+ module: string;
137
+ text: string;
138
+ }[];
139
+ readonly blockers: readonly SliceBlocker[];
140
+ /** Configuration files the included files are read with, sorted. */
141
+ readonly configs: readonly string[];
142
+ /** Package specifiers imported by included files or kept stubs, with the files. */
143
+ readonly packages: ReadonlyMap<string, ReadonlySet<string>>;
144
+ /** The options the fixture compiles with. */
145
+ readonly compilerOptions: ts.CompilerOptions;
146
+ /**
147
+ * Imports of the repository's own packages, which resolve to their
148
+ * source: the cuts and internal imports they add, with specifiers, and
149
+ * each specifier's module.
150
+ */
151
+ readonly workspace: {
152
+ readonly outbound: readonly CutEdge[];
153
+ readonly internal: readonly CutEdge[];
154
+ readonly modules: ReadonlyMap<string, string>;
155
+ };
156
+ /**
157
+ * The public surface's declarations and the check `npm test` holds them
158
+ * to (`carveout/surface/`); empty when there is nothing to check.
159
+ */
160
+ readonly surface: readonly SurfaceFile[];
161
+ }
162
+ export interface TypeScriptBoundaryInput {
163
+ readonly root: string;
164
+ readonly included: readonly string[];
165
+ readonly outbound: readonly CutEdge[];
166
+ readonly inbound: readonly CutEdge[];
167
+ readonly texts: ReadonlyMap<string, string>;
168
+ /** Defaults to `PROGRAM_FILES_MAX`. */
169
+ readonly maxProgramFiles?: number;
170
+ /** The source of a repository package's specifier; see `workspaceModuleResolver`. */
171
+ readonly workspaceModule?: (specifier: string) => string | null;
172
+ /** A directory whose node_modules holds fetched package types. */
173
+ readonly packageTypes?: string | null;
174
+ /**
175
+ * Whether stubs widen literal values to their types, so no constant's
176
+ * value leaves the repository. Defaults to true.
177
+ */
178
+ readonly widenLiterals?: boolean;
179
+ }
180
+ export declare function extractTypeScriptBoundary(input: TypeScriptBoundaryInput): TypeScriptBoundary;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * `carveout gate`: the hand-off policy on a change made in the repository
3
+ * itself, by an agent or anyone else, rather than on a folder that came
4
+ * back from outside.
5
+ *
6
+ * It reads the change from Git (the base commit against the working tree,
7
+ * untracked files included) and names what a change to code should not
8
+ * slip in unread: protected paths (CI, hooks, manifests, lockfiles,
9
+ * environment), deleted tests, tests whose assertions were removed or
10
+ * rewritten or that were skipped or focused with `.only`, edits to the
11
+ * configuration a check reads (tsconfig, linter, test runner), and new
12
+ * check suppressions (`@ts-ignore`, `eslint-disable`, coverage ignores).
13
+ * With `scope`, a file outside the paths the change was meant to touch is
14
+ * named too. A refusal is a question for a reviewer, not a verdict: a
15
+ * change asked to alter a behaviour rewrites the assertion that held it.
16
+ *
17
+ * Reads only: it runs no code of the repository.
18
+ */
19
+ import { type Refusal } from "./policy.js";
20
+ export type ChangeKind = "added" | "changed" | "deleted";
21
+ export interface RepositoryChange {
22
+ readonly path: string;
23
+ readonly change: ChangeKind;
24
+ }
25
+ export interface Gate {
26
+ /** The checkout's root; every path is relative to it. */
27
+ readonly repo: string;
28
+ /** The base as given, and the commit it names. */
29
+ readonly base: string;
30
+ readonly baseCommit: string;
31
+ readonly changes: readonly RepositoryChange[];
32
+ readonly refused: readonly Refusal[];
33
+ }
34
+ /** The root of the Git checkout holding `dir`. */
35
+ export declare function checkoutRoot(dir: string): string;
36
+ /** The commit `base` names in `root`, or an error that says so. */
37
+ export declare function baseCommitOf(root: string, base: string): string;
38
+ /**
39
+ * What changed from `baseCommit` to the working tree: tracked files by
40
+ * `git diff` (a rename is a deletion and an addition), and untracked files
41
+ * Git does not ignore as added. Sorted by path.
42
+ */
43
+ export declare function changesSince(root: string, baseCommit: string): RepositoryChange[];
44
+ /** A file's text at `commit`, or null when it has none there. */
45
+ export declare function textAt(root: string, commit: string, path: string): string | null;
46
+ /** A file's text in the working tree, or null when it is not a file. */
47
+ export declare function textNow(root: string, path: string): string | null;
48
+ /** Judges the change from `base` (default `HEAD`) to the working tree. */
49
+ export declare function gateRepository(options: {
50
+ readonly repo: string;
51
+ readonly base?: string;
52
+ readonly allow?: readonly string[];
53
+ /** Paths or globs the change was meant to stay within. */
54
+ readonly scope?: readonly string[];
55
+ }): Gate;
package/dist/src/gate.js CHANGED
@@ -98,6 +98,7 @@ export function gateRepository(options) {
98
98
  const changes = changesSince(root, baseCommit);
99
99
  const judge = handoffPolicy({
100
100
  checks: true,
101
+ integrity: true,
101
102
  testNoun: "a test",
102
103
  ...(options.allow === undefined ? {} : { allow: options.allow }),
103
104
  });
@@ -0,0 +1,142 @@
1
+ /**
2
+ * The import graph of a checkout, as the slice walk reads it.
3
+ *
4
+ * Every script file the checkout tracks becomes a `file:` node, with the
5
+ * names it exports itself. Each import, re-export and module reference the
6
+ * compiler resolves to a file in the checkout is a link to that file, with
7
+ * the names it takes and whether only types; each literal path a file reads
8
+ * at runtime is a `reads` link. Every other import (a package, a path that
9
+ * resolves to nothing, a dynamic import of a computed name) is a link to a
10
+ * `dependency:` node, which the walk reports rather than follows. Modules resolve with
11
+ * the nearest `tsconfig.json` (Node's resolution when there is none), and
12
+ * never through `node_modules`, so a workspace package is a bare specifier
13
+ * here, as it is in a snapshot without its installs. With
14
+ * `followWorkspaces`, an import of one of the repository's own packages is
15
+ * resolved to its source through the package's manifest and becomes a link
16
+ * to that file, so the walk copies or cuts it as it does any module.
17
+ *
18
+ * Nothing here runs repository code. The same files give the same graph.
19
+ */
20
+ import ts from "typescript";
21
+ import { type ReachOptions, type ReachResult, type SliceBudget, type SliceGraph, type SliceGraphLink, type SliceRelation } from "./core.js";
22
+ /** Directories never read, whatever Git tracks. */
23
+ export declare const IGNORED_DIRECTORIES: readonly string[];
24
+ /** Files a graph reads before it stops; past it the checkout is too large. */
25
+ export declare const GRAPH_FILES_MAX = 50000;
26
+ /**
27
+ * The checkout's files, `/`-separated and sorted: what Git tracks plus
28
+ * untracked files it does not ignore, or every file outside Git.
29
+ */
30
+ export declare function listFiles(root: string): string[];
31
+ /**
32
+ * The checkout's files a Vite glob's patterns match, as the bundler reads
33
+ * them: relative to the importing file's folder, or to the root for a
34
+ * pattern starting with `/`; a `!` pattern excludes. Never node_modules.
35
+ */
36
+ export declare function globMatches(root: string, directory: string, patterns: readonly string[]): string[];
37
+ export interface BuiltGraph {
38
+ readonly graph: SliceGraph;
39
+ /** Script files read, sorted. */
40
+ readonly files: readonly string[];
41
+ /** Files whose `tsconfig.json` could not be read. */
42
+ readonly invalidConfigs: readonly string[];
43
+ /** Where the time went, in milliseconds: resolving is the compiler's
44
+ * configuration and module resolution, parsing the rest of the walk. */
45
+ readonly timings: {
46
+ readonly listMs: number;
47
+ readonly parseMs: number;
48
+ readonly resolveMs: number;
49
+ };
50
+ }
51
+ /** How the graph reads imports of the repository's own packages. */
52
+ export interface GraphOptions {
53
+ /** Resolve a workspace package's import to its source file. */
54
+ readonly followWorkspaces?: boolean;
55
+ /** Files read as if the checkout had none (the owner's acceptance tests). */
56
+ readonly unseen?: readonly string[];
57
+ }
58
+ /** What `walkGraph` takes: the walk's options and the graph's. */
59
+ export type WalkOptions = ReachOptions & GraphOptions;
60
+ /** The graph of every script file of the checkout. */
61
+ export declare function buildGraph(root: string, listed?: readonly string[], options?: GraphOptions): BuiltGraph;
62
+ /**
63
+ * The graph a slice walk needs, read lazily: the entry points, every file
64
+ * the walk reaches, what those files name and what that re-exports (so a
65
+ * barrel and an import of types are judged from their targets), and every
66
+ * file that may import the slice. The walk on this graph gives what it
67
+ * gives on the whole checkout's graph, without parsing the rest of a large
68
+ * monorepo.
69
+ *
70
+ * Importers are found by text first and confirmed by the compiler: a file
71
+ * is read when it mentions an included file's name (its folder's, for an
72
+ * `index`), an alias with no wildcard that maps to it, `import.meta.glob`,
73
+ * or an import of folders alone (`.`, `..`, `../..`). A relative or wildcard-alias import spells
74
+ * the file's name or its folder's, and a workspace package's name is a
75
+ * package here, not a link, so no importer is missed for being unread.
76
+ */
77
+ export declare function walkGraph(root: string, entryPoints: readonly string[], budget: SliceBudget, options?: WalkOptions): {
78
+ built: BuiltGraph;
79
+ walk: ReachResult;
80
+ };
81
+ /**
82
+ * The walk with the files that import the slice by a workspace package's
83
+ * name among its inbound cuts. Unfollowed, such an import is a package
84
+ * here, not a link, and the walk never sees it; but the package's other
85
+ * workspaces compile against the module its name resolves to, so a change
86
+ * to that module's exports breaks them. The public surface, the interface
87
+ * test and `status` read the inbound cuts, and must count them. The walk
88
+ * itself is unchanged: nothing is copied or cut for it.
89
+ */
90
+ export declare function workspaceImportersOf(root: string): (walk: ReachResult, graph: SliceGraph) => ReachResult;
91
+ /** A confirmed link from an importer to a file `importersOf` searched for. */
92
+ export interface ImporterLink {
93
+ readonly from: string;
94
+ readonly to: string;
95
+ readonly specifier: string | null;
96
+ readonly relation: SliceRelation;
97
+ readonly typeOnly: boolean;
98
+ readonly forwards: SliceGraphLink["forwards"];
99
+ }
100
+ /**
101
+ * Every file that imports `targets`, directly or through other importers,
102
+ * up to `maxDepth` hops (unbounded by default): the reverse of the slice
103
+ * walk, read as lazily. Each hop finds candidates by text (a target's name,
104
+ * its folder's for an `index`, an exact alias, a workspace package's name,
105
+ * a folder-only import, `import.meta.glob`) and keeps those whose import
106
+ * the compiler resolves to a target. A target that no longer exists (a
107
+ * deleted module) is matched by resolving imports as if it still were.
108
+ * `references` (`vi.mock`) count: such a test runs against the module.
109
+ *
110
+ * It follows names, as the slice walk does: a barrel carries the change
111
+ * only through the names it forwards from a changed module, so a file that
112
+ * imports other names from the barrel is not reached. A file whose own code
113
+ * takes a name that carries the change is reached, and carries it on only
114
+ * through its exports whose declarations reach that name, by way of its
115
+ * other top-level declarations. Nothing here runs repository code.
116
+ */
117
+ export declare function importersOf(root: string, targets: readonly string[], options?: {
118
+ readonly maxDepth?: number;
119
+ /** The names of each target that changed (`changedExports`); all by default. */
120
+ readonly seeds?: ReadonlyMap<string, ReadonlySet<string> | "*">;
121
+ }): {
122
+ built: BuiltGraph;
123
+ /** Hops from the nearest target; the targets themselves at 0. */
124
+ depthOf: ReadonlyMap<string, number>;
125
+ links: readonly ImporterLink[];
126
+ };
127
+ /**
128
+ * The exports of a module whose declarations reach one of `locals`, through
129
+ * its other top-level declarations. Read by the identifiers each statement
130
+ * names, so a shadowing name counts too: it errs toward more.
131
+ */
132
+ export declare function exportsReachingIn(source: ts.SourceFile, locals: ReadonlySet<string>): Set<string> | "*";
133
+ /**
134
+ * The exports of a module that a change from `before` to `after` reaches:
135
+ * those whose own declaration changed, those that reach a changed
136
+ * declaration or import through the module's other declarations, and
137
+ * those added, removed or re-pointed by an export list or a re-export. A
138
+ * changed statement that declares nothing (code run at load, `export *`)
139
+ * reaches everything. Null `before` or `after`: an added or deleted module,
140
+ * all of it.
141
+ */
142
+ export declare function changedExports(path: string, before: string | null, after: string | null): Set<string> | "*";
package/dist/src/graph.js CHANGED
@@ -140,8 +140,9 @@ export function buildGraph(root, listed, options = {}) {
140
140
  */
141
141
  export function walkGraph(root, entryPoints, budget, options = {}) {
142
142
  const started = performance.now();
143
- const files = listFiles(root);
144
- const { followWorkspaces, ...rest } = options;
143
+ const { followWorkspaces, unseen, ...rest } = options;
144
+ const hidden = new Set(unseen ?? []);
145
+ const files = listFiles(root).filter((file) => !hidden.has(file));
145
146
  const graphOptions = followWorkspaces === undefined ? {} : { followWorkspaces };
146
147
  const builder = graphBuilder(root, files, performance.now() - started, graphOptions);
147
148
  const absoluteRoot = resolve(root);
@@ -150,7 +151,7 @@ export function walkGraph(root, entryPoints, budget, options = {}) {
150
151
  packageOf: rest.packageOf ?? packageFolder(absoluteRoot),
151
152
  };
152
153
  const scripts = files.filter((file) => SCRIPT.test(file));
153
- const prefixes = entryPoints.map((entry) => entry.replaceAll("\\", "/").replace(/^\.\//, "").replace(/^\/+|\/+$/g, ""));
154
+ const prefixes = [...entryPoints, ...(options.roots ?? [])].map((entry) => entry.replaceAll("\\", "/").replace(/^\.\//, "").replace(/^\/+|\/+$/g, ""));
154
155
  builder.add(scripts.filter((file) => prefixes.some((prefix) => file === prefix || file.startsWith(`${prefix}/`))));
155
156
  const aliases = exactAliases(absoluteRoot, files);
156
157
  // A workspace package is imported by its name, which spells neither the
@@ -0,0 +1,113 @@
1
+ /**
2
+ * `carveout impact`: what a change made in the repository reaches, for the
3
+ * agent or person who made it, before the repository's CI says so.
4
+ *
5
+ * From the change (Git's base commit against the working tree, or the
6
+ * files named) it finds:
7
+ *
8
+ * - every file that imports a changed module, directly or through others,
9
+ * by the same lazy, compiler-confirmed search the slice walk uses;
10
+ * - the exports of each changed module whose type changed or that are
11
+ * gone, read by the TypeScript checker at the base and now, and which of
12
+ * those importers take each one (through barrels too);
13
+ * - the tests among the importers, with their runner, and the commands
14
+ * that run just them, package by package.
15
+ *
16
+ * The search, the type comparison and the test selection run no code of
17
+ * the repository; `runImpactTests` runs the commands, in the repository,
18
+ * with the caller's environment, as its own test script would.
19
+ *
20
+ * A type is compared as the checker prints it, one level of members deep:
21
+ * a change inside a named type another module declares shows on that
22
+ * module's own exports when it changed too, not on this one.
23
+ */
24
+ import { type ChangeKind } from "./gate.js";
25
+ export type ImpactRunner = "node" | "vitest" | "jest";
26
+ export interface ImpactExport {
27
+ readonly file: string;
28
+ readonly name: string;
29
+ readonly change: "removed" | "changed";
30
+ /** The type as the checker prints it, one level of members deep. */
31
+ readonly before: string;
32
+ readonly after: string | null;
33
+ /** Files outside the change that take this export, sorted. */
34
+ readonly usedBy: readonly string[];
35
+ }
36
+ export interface ImpactTest {
37
+ readonly file: string;
38
+ /** Hops from the nearest changed module; 0 for a test the change edits. */
39
+ readonly depth: number;
40
+ readonly package: string;
41
+ /** Null: no runner carveout knows (mocha, ava, a custom script). */
42
+ readonly runner: ImpactRunner | null;
43
+ }
44
+ export interface ImpactCommand {
45
+ /** The package folder to run it in, from the checkout's root ("" the root). */
46
+ readonly cwd: string;
47
+ readonly runner: ImpactRunner | null;
48
+ readonly argv: readonly string[];
49
+ readonly tests: readonly string[];
50
+ /** Runs the package's whole test script, not only these tests. */
51
+ readonly whole: boolean;
52
+ }
53
+ export interface Impact {
54
+ readonly repo: string;
55
+ /** The base as given and its commit; null when files were named. */
56
+ readonly base: string | null;
57
+ readonly baseCommit: string | null;
58
+ /** The script files the change touches, with how. */
59
+ readonly changed: readonly {
60
+ readonly path: string;
61
+ readonly change: ChangeKind;
62
+ }[];
63
+ /** Files that import a changed module, with their hops from it, sorted. */
64
+ readonly importers: readonly {
65
+ readonly file: string;
66
+ readonly depth: number;
67
+ }[];
68
+ readonly exports: readonly ImpactExport[];
69
+ readonly tests: readonly ImpactTest[];
70
+ readonly commands: readonly ImpactCommand[];
71
+ /** Changed modules whose types could not be compared, with why. */
72
+ readonly unchecked: readonly {
73
+ readonly file: string;
74
+ readonly reason: string;
75
+ }[];
76
+ readonly timings: {
77
+ readonly graphMs: number;
78
+ readonly typesMs: number;
79
+ };
80
+ }
81
+ /**
82
+ * A test file's runner: the one it imports (`node:test`, `vitest`,
83
+ * `@jest/globals`), else the one its package (or the root) depends on
84
+ * when the file calls a runner's globals.
85
+ */
86
+ export declare function impactRunnerResolver(root: string, packageOf: (file: string) => string): (file: string, specifiers: readonly string[]) => ImpactRunner | null;
87
+ /** The commands that run `tests`, one per package and runner. */
88
+ export declare function impactCommands(tests: readonly ImpactTest[]): ImpactCommand[];
89
+ /** What the change from `base` (or the files named) reaches. */
90
+ export declare function impactOf(options: {
91
+ readonly repo: string;
92
+ readonly base?: string;
93
+ /** Files to start from instead of the change since `base`. */
94
+ readonly files?: readonly string[];
95
+ /** Hops of importers to follow; unbounded by default. */
96
+ readonly maxDepth?: number;
97
+ }): Impact;
98
+ export interface ImpactRun {
99
+ readonly command: ImpactCommand;
100
+ readonly ok: boolean;
101
+ readonly exitCode: number | null;
102
+ /** The last lines of what it printed. */
103
+ readonly output: string;
104
+ }
105
+ /**
106
+ * Runs each command at its package's folder, one at a time, with the
107
+ * caller's environment: these are the repository's own tests, run where
108
+ * its test script would run them. `timeoutMs` bounds each.
109
+ */
110
+ export declare function runImpactTests(root: string, commands: readonly ImpactCommand[], options?: {
111
+ readonly timeoutMs?: number;
112
+ readonly log?: (line: string) => void;
113
+ }): ImpactRun[];
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Imports as the repository writes them. The cut rewrites a slice's
3
+ * imports to paths Node resolves inside the folder, and records each
4
+ * rewrite; `patch` turns them back. An import the folder's own edits added
5
+ * has no record, so it is restored by what it resolves to: the specifier
6
+ * the slice already uses for that target, else one the target's other
7
+ * importers use, else a relative path in the package's own style.
8
+ */
9
+ import { type ImportRewrite } from "./core.js";
10
+ /**
11
+ * `text` with the folder's import rewrites put back as the source wrote
12
+ * them. Two specifiers the cut rewrote to one (`@/lib/format` and
13
+ * `../../lib/format` both to `../../lib/format.js`) are told apart by
14
+ * `original`, the file as the cut read it: they come back in the order it
15
+ * wrote them. Without it, the last recorded wins. `path` tells the parser
16
+ * what kind of file the text is (`.tsx` reads JSX); a `.ts` without it.
17
+ */
18
+ export declare function undoRewrites(text: string, rewrites: readonly ImportRewrite[], original?: string | null, path?: string): string;
19
+ /** Every specifier a source writes, in source order. */
20
+ export declare function specifiersOf(text: string, path?: string): Set<string>;
21
+ /** How a package spells a relative import's extension. */
22
+ export type ExtensionStyle = "none" | "js" | "ts";
23
+ /** What a cut knows about the slice's imports, for restoring new ones. */
24
+ export interface ImportContext {
25
+ /** Import edges the cut resolved, by importer, target and specifier. */
26
+ readonly edges: readonly {
27
+ from: string;
28
+ to: string;
29
+ specifier: string;
30
+ }[];
31
+ /** The folder's source files, by repository path: slice and new files. */
32
+ readonly files: ReadonlySet<string>;
33
+ /** Cut modules by repository path; their stubs sit beside them. */
34
+ readonly stubs: ReadonlySet<string>;
35
+ /** The repository's package folder holding a path, "" for the root. */
36
+ readonly packageOf: (path: string) => string;
37
+ /**
38
+ * How a package writes the extension of a relative import: none, the
39
+ * emitted `.js`, or the source's own `.ts` (Node's type stripping,
40
+ * `allowImportingTsExtensions`).
41
+ */
42
+ readonly extensionStyle: (pkg: string) => ExtensionStyle;
43
+ }
44
+ /** The folder file or cut module a relative specifier in `file` names. */
45
+ export declare function importTarget(file: string, specifier: string, context: Pick<ImportContext, "files" | "stubs">): string | null;
46
+ export interface RestoredImports {
47
+ readonly text: string;
48
+ /** Specifiers restored relative across packages: no spelling was known. */
49
+ readonly crossPackage: readonly string[];
50
+ }
51
+ /**
52
+ * A slice file's text with its imports as the repository writes them.
53
+ * `original` is the file as the cut read it (null for a new file): the
54
+ * specifiers it wrote are kept as they are.
55
+ */
56
+ export declare function restoreImports(file: string, text: string, original: string | null, rewrites: readonly ImportRewrite[], context: ImportContext): RestoredImports;
57
+ /** The nearest folder above `path` with a package.json in `repo`, "" for none. */
58
+ export declare function packageFolder(repo: string, cache?: Map<string, string>): (path: string) => string;
59
+ /**
60
+ * How each package writes relative imports, by the imports the cut read
61
+ * there: without an extension, with the emitted `.js`, or with the
62
+ * source's `.ts`. The most common style wins; `.js` on a tie or none.
63
+ */
64
+ export declare function extensionStyles(edges: readonly {
65
+ from: string;
66
+ specifier: string;
67
+ }[], packageOf: (path: string) => string): (pkg: string) => ExtensionStyle;