repro-surgeon 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/CODE_OF_CONDUCT.md +7 -0
  3. package/CONTRIBUTING.md +31 -0
  4. package/LICENSE +21 -0
  5. package/README.md +159 -0
  6. package/SECURITY.md +15 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.js +165 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/config.d.ts +3 -0
  11. package/dist/config.js +177 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/dependencies.d.ts +37 -0
  14. package/dist/dependencies.js +237 -0
  15. package/dist/dependencies.js.map +1 -0
  16. package/dist/engine.d.ts +25 -0
  17. package/dist/engine.js +327 -0
  18. package/dist/engine.js.map +1 -0
  19. package/dist/export.d.ts +7 -0
  20. package/dist/export.js +520 -0
  21. package/dist/export.js.map +1 -0
  22. package/dist/graph.d.ts +3 -0
  23. package/dist/graph.js +247 -0
  24. package/dist/graph.js.map +1 -0
  25. package/dist/index.d.ts +7 -0
  26. package/dist/index.js +7 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/oracle.d.ts +2 -0
  29. package/dist/oracle.js +81 -0
  30. package/dist/oracle.js.map +1 -0
  31. package/dist/protection.d.ts +2 -0
  32. package/dist/protection.js +6 -0
  33. package/dist/protection.js.map +1 -0
  34. package/dist/report.d.ts +5 -0
  35. package/dist/report.js +539 -0
  36. package/dist/report.js.map +1 -0
  37. package/dist/runner.d.ts +3 -0
  38. package/dist/runner.js +195 -0
  39. package/dist/runner.js.map +1 -0
  40. package/dist/snapshot.d.ts +11 -0
  41. package/dist/snapshot.js +220 -0
  42. package/dist/snapshot.js.map +1 -0
  43. package/dist/state.d.ts +9 -0
  44. package/dist/state.js +143 -0
  45. package/dist/state.js.map +1 -0
  46. package/dist/transforms.d.ts +3 -0
  47. package/dist/transforms.js +188 -0
  48. package/dist/transforms.js.map +1 -0
  49. package/dist/types.d.ts +207 -0
  50. package/dist/types.js +2 -0
  51. package/dist/types.js.map +1 -0
  52. package/docs/architecture.md +91 -0
  53. package/docs/assets/report.png +0 -0
  54. package/docs/configuration.md +73 -0
  55. package/docs/evidence/dependency-removal.json +109 -0
  56. package/docs/evidence/next-frameworks.json +271 -0
  57. package/docs/evidence/next-pages.json +152 -0
  58. package/docs/evidence/rounding.json +46 -0
  59. package/docs/report-surface.md +22 -0
  60. package/docs/troubleshooting.md +47 -0
  61. package/docs/validation.md +103 -0
  62. package/examples/next-container/README.md +7 -0
  63. package/examples/next-container/app/about/page.jsx +1 -0
  64. package/examples/next-container/app/case.css +5 -0
  65. package/examples/next-container/app/layout.jsx +3 -0
  66. package/examples/next-container/app/page.jsx +4 -0
  67. package/examples/next-container/lib/catalog.js +2 -0
  68. package/examples/next-container/next.config.mjs +1 -0
  69. package/examples/next-container/package-lock.json +952 -0
  70. package/examples/next-container/package.json +13 -0
  71. package/examples/next-container/repro-surgeon.json +37 -0
  72. package/examples/next-css-bom/README.md +7 -0
  73. package/examples/next-css-bom/app/about/page.jsx +1 -0
  74. package/examples/next-css-bom/app/case.css +3 -0
  75. package/examples/next-css-bom/app/layout.jsx +3 -0
  76. package/examples/next-css-bom/app/page.jsx +4 -0
  77. package/examples/next-css-bom/lib/catalog.js +2 -0
  78. package/examples/next-css-bom/next.config.mjs +1 -0
  79. package/examples/next-css-bom/package-lock.json +952 -0
  80. package/examples/next-css-bom/package.json +13 -0
  81. package/examples/next-css-bom/repro-surgeon.json +36 -0
  82. package/examples/next-pages/README.md +14 -0
  83. package/examples/next-pages/lib/unused-catalog.js +5 -0
  84. package/examples/next-pages/next.config.mjs +1 -0
  85. package/examples/next-pages/package-lock.json +952 -0
  86. package/examples/next-pages/package.json +11 -0
  87. package/examples/next-pages/pages/health.jsx +3 -0
  88. package/examples/next-pages/pages/index.jsx +9 -0
  89. package/examples/next-pages/repro-surgeon.json +18 -0
  90. package/examples/next-root-param/README.md +7 -0
  91. package/examples/next-root-param/app/[lang-country]/about/page.jsx +1 -0
  92. package/examples/next-root-param/app/[lang-country]/layout.tsx +4 -0
  93. package/examples/next-root-param/app/[lang-country]/page.tsx +3 -0
  94. package/examples/next-root-param/lib/catalog.js +2 -0
  95. package/examples/next-root-param/next-env.d.ts +2 -0
  96. package/examples/next-root-param/next.config.mjs +1 -0
  97. package/examples/next-root-param/package-lock.json +1016 -0
  98. package/examples/next-root-param/package.json +19 -0
  99. package/examples/next-root-param/repro-surgeon.json +37 -0
  100. package/examples/next-root-param/tsconfig.json +35 -0
  101. package/examples/rounding/README.md +16 -0
  102. package/examples/rounding/check.mjs +6 -0
  103. package/examples/rounding/config/store.json +7 -0
  104. package/examples/rounding/fixtures/invoice.json +11 -0
  105. package/examples/rounding/package-lock.json +7 -0
  106. package/examples/rounding/package.json +7 -0
  107. package/examples/rounding/repro-surgeon.json +12 -0
  108. package/examples/rounding/src/catalog.mjs +9 -0
  109. package/examples/rounding/src/shipping.mjs +7 -0
  110. package/examples/rounding/src/totals.mjs +11 -0
  111. package/package.json +29 -0
@@ -0,0 +1,188 @@
1
+ import ts from 'typescript';
2
+ import { isLegalPath } from "./protection.js";
3
+ import { matchesPath } from "./snapshot.js";
4
+ const SOURCE_EXTENSIONS = /\.(?:[cm]?[jt]sx?)$/i;
5
+ const PROTECTED_BASENAME = /^(?:package(?:-lock)?\.json|npm-shrinkwrap\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb?)$/i;
6
+ function compareText(left, right) {
7
+ return left < right ? -1 : left > right ? 1 : 0;
8
+ }
9
+ function isPreserved(path, config) {
10
+ return matchesPath(path, config.preserve);
11
+ }
12
+ function isProtectedPath(path, config) {
13
+ const basename = path.slice(path.lastIndexOf('/') + 1);
14
+ return PROTECTED_BASENAME.test(basename) || isLegalPath(path) || isPreserved(path, config);
15
+ }
16
+ function scriptKind(path) {
17
+ if (/\.tsx$/i.test(path))
18
+ return ts.ScriptKind.TSX;
19
+ if (/\.jsx$/i.test(path))
20
+ return ts.ScriptKind.JSX;
21
+ if (/\.(?:mjs|cjs|js)$/i.test(path))
22
+ return ts.ScriptKind.JS;
23
+ return ts.ScriptKind.TS;
24
+ }
25
+ function diagnostics(sourceFile) {
26
+ return sourceFile.parseDiagnostics;
27
+ }
28
+ function parseSource(path, source) {
29
+ const parsed = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true, scriptKind(path));
30
+ return diagnostics(parsed).length === 0 ? parsed : null;
31
+ }
32
+ function listItemRange(items, index, sourceFile) {
33
+ const item = items[index];
34
+ if (!item)
35
+ throw new Error('list item index is out of bounds');
36
+ if (items.length === 1)
37
+ return { start: item.getStart(sourceFile), end: item.end, label: 'list item' };
38
+ const next = items[index + 1];
39
+ if (next)
40
+ return { start: item.getStart(sourceFile), end: next.getStart(sourceFile), label: 'list item' };
41
+ const previous = items[index - 1];
42
+ if (!previous)
43
+ throw new Error('missing previous list item');
44
+ return { start: previous.end, end: item.end, label: 'list item' };
45
+ }
46
+ function isDirective(statement) {
47
+ return ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression);
48
+ }
49
+ function statementRanges(statements, label) {
50
+ const ranges = [];
51
+ let inPrologue = true;
52
+ for (const statement of statements) {
53
+ if (inPrologue && isDirective(statement))
54
+ continue;
55
+ inPrologue = false;
56
+ ranges.push({
57
+ start: statement.getStart(),
58
+ end: statement.end,
59
+ label: `${label} ${ts.SyntaxKind[statement.kind]}`,
60
+ });
61
+ }
62
+ return ranges;
63
+ }
64
+ function buildCandidates(path, original, ranges, kind, valid) {
65
+ const seen = new Set();
66
+ const candidates = [];
67
+ const firstLineEnd = original.startsWith('#!') ? original.indexOf('\n') + 1 : 0;
68
+ const legalComments = [];
69
+ const scanner = ts.createScanner(ts.ScriptTarget.Latest, false, ts.LanguageVariant.Standard, original);
70
+ for (let token = scanner.scan(); token !== ts.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
71
+ if (token !== ts.SyntaxKind.SingleLineCommentTrivia && token !== ts.SyntaxKind.MultiLineCommentTrivia)
72
+ continue;
73
+ const text = scanner.getTokenText();
74
+ if (text.startsWith('/*!') || /@license|@preserve|copyright|SPDX-License-Identifier|SPDX-FileCopyrightText|permission is hereby granted/i.test(text)) {
75
+ legalComments.push({ start: scanner.getTokenPos(), end: scanner.getTextPos() });
76
+ }
77
+ }
78
+ for (const range of ranges) {
79
+ const start = firstLineEnd > 0 && range.start < firstLineEnd ? firstLineEnd : range.start;
80
+ if (start < 0 || range.end <= start || range.end > original.length)
81
+ continue;
82
+ if (legalComments.some(comment => start < comment.end && range.end > comment.start))
83
+ continue;
84
+ const content = original.slice(0, start) + original.slice(range.end);
85
+ if (content === original || !valid(content) || seen.has(content))
86
+ continue;
87
+ seen.add(content);
88
+ candidates.push({
89
+ id: `${kind}:${path}:${start}:${range.end}:${range.label}`,
90
+ kind,
91
+ description: `Remove ${range.label} from ${path}`,
92
+ paths: [path],
93
+ edits: [{ path, content: Buffer.from(content) }],
94
+ start,
95
+ end: range.end,
96
+ });
97
+ }
98
+ candidates.sort((left, right) => compareText(left.paths[0], right.paths[0]) ||
99
+ left.start - right.start ||
100
+ left.end - right.end ||
101
+ compareText(left.description, right.description));
102
+ return candidates.map(({ start: _start, end: _end, ...candidate }) => candidate);
103
+ }
104
+ export function syntaxCandidates(snapshot, config) {
105
+ const all = [];
106
+ for (const [path, entry] of [...snapshot.entries()].sort(([a], [b]) => compareText(a, b))) {
107
+ if (!SOURCE_EXTENSIONS.test(path) || isProtectedPath(path, config))
108
+ continue;
109
+ const source = entry.content.toString('utf8');
110
+ const sourceFile = parseSource(path, source);
111
+ if (!sourceFile)
112
+ continue;
113
+ const ranges = statementRanges(sourceFile.statements, 'top-level statement');
114
+ const visit = (node) => {
115
+ if (ts.isImportDeclaration(node) && node.importClause) {
116
+ const bindings = node.importClause.namedBindings;
117
+ if (bindings && ts.isNamedImports(bindings)) {
118
+ bindings.elements.forEach((element, index) => {
119
+ const range = listItemRange(bindings.elements, index, sourceFile);
120
+ ranges.push({ ...range, label: `import specifier ${element.name.text}` });
121
+ });
122
+ }
123
+ }
124
+ if (ts.isVariableStatement(node)) {
125
+ const declarations = node.declarationList.declarations;
126
+ declarations.forEach((_declaration, index) => {
127
+ const range = declarations.length === 1
128
+ ? { start: node.getStart(), end: node.end, label: 'variable declaration' }
129
+ : { ...listItemRange(declarations, index, sourceFile), label: 'variable declaration' };
130
+ ranges.push(range);
131
+ });
132
+ }
133
+ if (ts.isBlock(node))
134
+ ranges.push(...statementRanges(node.statements, 'statement'));
135
+ if (ts.isObjectLiteralExpression(node)) {
136
+ node.properties.forEach((property, index) => {
137
+ const range = listItemRange(node.properties, index, sourceFile);
138
+ const name = property.name && ts.isIdentifier(property.name) ? ` ${property.name.text}` : '';
139
+ ranges.push({ ...range, label: `object member${name}` });
140
+ });
141
+ }
142
+ if (ts.isJsxElement(node) || ts.isJsxFragment(node)) {
143
+ node.children.forEach((child) => {
144
+ if (ts.isJsxText(child) && child.getText(sourceFile).trim() === '')
145
+ return;
146
+ ranges.push({ start: child.getFullStart(), end: child.end, label: 'JSX child' });
147
+ });
148
+ }
149
+ ts.forEachChild(node, visit);
150
+ };
151
+ visit(sourceFile);
152
+ all.push(...buildCandidates(path, source, ranges, 'syntax', (candidate) => parseSource(path, candidate) !== null));
153
+ }
154
+ return all.sort((left, right) => compareText(left.id, right.id));
155
+ }
156
+ export function jsonCandidates(snapshot, config) {
157
+ const all = [];
158
+ for (const [path, entry] of [...snapshot.entries()].sort(([a], [b]) => compareText(a, b))) {
159
+ if (!/\.jsonc?$/i.test(path) || isProtectedPath(path, config))
160
+ continue;
161
+ const source = entry.content.toString('utf8');
162
+ if (ts.parseConfigFileTextToJson(path, source).error)
163
+ continue;
164
+ const sourceFile = ts.parseJsonText(path, source);
165
+ if (diagnostics(sourceFile).length > 0)
166
+ continue;
167
+ const ranges = [];
168
+ const visit = (node) => {
169
+ if (ts.isObjectLiteralExpression(node)) {
170
+ node.properties.forEach((property, index) => {
171
+ const range = listItemRange(node.properties, index, sourceFile);
172
+ const key = property.name && ts.isStringLiteral(property.name) ? ` ${property.name.text}` : '';
173
+ ranges.push({ ...range, label: `JSON member${key}` });
174
+ });
175
+ }
176
+ else if (ts.isArrayLiteralExpression(node)) {
177
+ node.elements.forEach((_element, index) => {
178
+ ranges.push({ ...listItemRange(node.elements, index, sourceFile), label: 'JSON array item' });
179
+ });
180
+ }
181
+ ts.forEachChild(node, visit);
182
+ };
183
+ visit(sourceFile);
184
+ all.push(...buildCandidates(path, source, ranges, 'json', candidate => !ts.parseConfigFileTextToJson(path, candidate).error));
185
+ }
186
+ return all.sort((left, right) => compareText(left.id, right.id));
187
+ }
188
+ //# sourceMappingURL=transforms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transforms.js","sourceRoot":"","sources":["../src/transforms.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,207 @@
1
+ export interface FileEntry {
2
+ content: Buffer;
3
+ mode: number;
4
+ }
5
+ export type Snapshot = Map<string, FileEntry>;
6
+ export interface Oracle {
7
+ exitCode: number;
8
+ allOf: string[];
9
+ noneOf: string[];
10
+ }
11
+ export interface Config {
12
+ version: 1;
13
+ name: string;
14
+ command: string[];
15
+ oracle: Oracle;
16
+ adapter: 'auto' | 'next' | 'generic';
17
+ budget: {
18
+ maxEvaluations: number;
19
+ maxSeconds: number;
20
+ };
21
+ runs: {
22
+ baseline: number;
23
+ candidate: number;
24
+ final: number;
25
+ };
26
+ execution: {
27
+ timeoutMs: number;
28
+ maxOutputBytes: number;
29
+ installTimeoutMs: number;
30
+ allowInstallScripts: boolean;
31
+ env: string[];
32
+ };
33
+ reduce: {
34
+ files: boolean;
35
+ syntax: boolean;
36
+ json: boolean;
37
+ dependencies: boolean;
38
+ };
39
+ include: string[];
40
+ exclude: string[];
41
+ preserve: string[];
42
+ }
43
+ export interface RunOptions {
44
+ cwd: string;
45
+ timeoutMs: number;
46
+ maxOutputBytes: number;
47
+ env?: Record<string, string>;
48
+ signal?: AbortSignal;
49
+ }
50
+ export interface CommandResult {
51
+ exitCode: number | null;
52
+ signal: string | null;
53
+ timedOut: boolean;
54
+ aborted: boolean;
55
+ outputLimitExceeded: boolean;
56
+ stdout: string;
57
+ stderr: string;
58
+ durationMs: number;
59
+ error?: string;
60
+ }
61
+ export type Outcome = 'reproduced' | 'absent' | 'invalid';
62
+ export interface OracleResult {
63
+ status: Outcome;
64
+ reason: string;
65
+ matched: string[];
66
+ missing: string[];
67
+ forbidden: string[];
68
+ diagnostics: string[];
69
+ }
70
+ export interface Observation {
71
+ execution: CommandResult;
72
+ oracle: OracleResult;
73
+ }
74
+ export interface FileEdit {
75
+ path: string;
76
+ content: Buffer | null;
77
+ }
78
+ export type TransformKind = 'files' | 'syntax' | 'json' | 'dependencies';
79
+ export interface Transformation {
80
+ id: string;
81
+ kind: TransformKind;
82
+ description: string;
83
+ paths: string[];
84
+ edits: FileEdit[];
85
+ }
86
+ export interface ProjectGraph {
87
+ imports: Record<string, string[]>;
88
+ external: Record<string, string[]>;
89
+ unresolved: Record<string, string[]>;
90
+ }
91
+ export interface AdapterInfo {
92
+ name: 'next' | 'generic';
93
+ version: string | null;
94
+ router: 'app' | 'pages' | 'mixed' | 'none';
95
+ entrypoints: string[];
96
+ protectedPaths: string[];
97
+ warnings: string[];
98
+ }
99
+ export interface Metrics {
100
+ files: number;
101
+ bytes: number;
102
+ sourceBytes: number;
103
+ dependencies: number;
104
+ }
105
+ export interface ReviewFinding {
106
+ path: string;
107
+ kind: string;
108
+ message: string;
109
+ }
110
+ export interface Inventory {
111
+ snapshot: Snapshot;
112
+ excluded: {
113
+ path: string;
114
+ reason: string;
115
+ }[];
116
+ warnings: string[];
117
+ }
118
+ export interface Trial {
119
+ index: number;
120
+ candidateHash: string;
121
+ kind: TransformKind;
122
+ description: string;
123
+ paths: string[];
124
+ status: Outcome;
125
+ accepted: boolean;
126
+ reason: string;
127
+ durationMs: number;
128
+ before: Metrics;
129
+ after: Metrics;
130
+ confirmations: number;
131
+ diagnostics: string[];
132
+ }
133
+ export interface RuntimeInfo {
134
+ node: string;
135
+ npm: string;
136
+ platform: string;
137
+ arch: string;
138
+ tool: string;
139
+ }
140
+ export interface Verification {
141
+ status: 'pending' | 'verified' | 'failed';
142
+ runs: number;
143
+ reason: string;
144
+ snapshotHash: string;
145
+ environment: 'fresh-directory';
146
+ }
147
+ export interface RunState {
148
+ version: 1;
149
+ id: string;
150
+ sourceRoot: string;
151
+ createdAt: string;
152
+ updatedAt: string;
153
+ config: Config;
154
+ configHash: string;
155
+ runtime: RuntimeInfo;
156
+ originalHash: string;
157
+ bestHash: string;
158
+ initial: Metrics;
159
+ current: Metrics;
160
+ adapter: AdapterInfo;
161
+ status: 'calibrating' | 'reducing' | 'paused' | 'verifying' | 'complete' | 'failed';
162
+ stopReason: string;
163
+ evaluations: number;
164
+ elapsedMs: number;
165
+ baseline: OracleResult[];
166
+ trials: Trial[];
167
+ rejectedHashes: string[];
168
+ excluded: {
169
+ path: string;
170
+ reason: string;
171
+ }[];
172
+ warnings: string[];
173
+ verification: Verification;
174
+ reviewFindings: ReviewFinding[];
175
+ }
176
+ export interface RunReport {
177
+ version: 1;
178
+ id: string;
179
+ name: string;
180
+ createdAt: string;
181
+ status: RunState['status'];
182
+ stopReason: string;
183
+ command: string[];
184
+ oracle: Oracle;
185
+ runtime: RuntimeInfo;
186
+ adapter: AdapterInfo;
187
+ initial: Metrics;
188
+ current: Metrics;
189
+ evaluations: number;
190
+ elapsedMs: number;
191
+ baseline: {
192
+ completed: number;
193
+ required: number;
194
+ };
195
+ trials: Trial[];
196
+ files: {
197
+ path: string;
198
+ bytes: number;
199
+ }[];
200
+ excluded: {
201
+ path: string;
202
+ reason: string;
203
+ }[];
204
+ warnings: string[];
205
+ verification: Verification;
206
+ reviewFindings: ReviewFinding[];
207
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,91 @@
1
+ # Architecture
2
+
3
+ Repro Surgeon turns a failing application into a smaller source project that another developer can install and run. Every accepted change must preserve the configured failure oracle. The final export is checked again from a fresh directory and dependency installation.
4
+
5
+ ## Contract
6
+
7
+ The input is an owned, single-package npm project, an argv command, an exact expected exit code, and distinctive required output fragments. Optional forbidden fragments rule out known competing failures. A successful reduction is the smallest snapshot found within the run's budget that repeatedly matches this contract. This is evidence about the configured observation, not a proof that two failures have the same semantic root cause.
8
+
9
+ The default release targets deterministic command and Next.js build failures on Linux and macOS with Node.js 22.18 or newer and npm 10 or newer. Framework support is based on executed cases, not inference from a dependency name. Browser interactions and service-backed failures can be authored as finite commands, but automatic recording, server management, monorepos and other package managers require separate adapters and validation.
10
+
11
+ ## Lifecycle
12
+
13
+ 1. `init` writes a versioned JSON configuration from an explicit command and target text. It never guesses the user's failure silently.
14
+ 2. `doctor` inventories a project, identifies its adapter, and reports unsupported dependencies or environmental requirements without executing project code.
15
+ 3. `reduce` snapshots the input into a new run directory, provisions dependencies, and calibrates three uncached baseline runs.
16
+ 4. Hierarchical file reductions and syntax/data transformations are evaluated in disposable working directories. Two uncached confirmations are required before a candidate becomes the accepted state.
17
+ 5. Each decision is checkpointed atomically. `resume` validates the saved state and runtime, recalibrates the accepted snapshot, and continues with the remaining or explicitly expanded budget.
18
+ 6. The exporter writes the reduced source, its lockfile, reproduction instructions, an independent assertion runner, and an evidence manifest. It scans retained material for review findings.
19
+ 7. The exact export is copied to a fresh directory, installed with `npm ci`, and checked three times without reduction caches. Only then can the result say it is verified.
20
+ 8. Offline HTML and JSON reports expose real metrics, accepted changes, rejected/invalid trials, remaining files, failure checks, verification and review findings.
21
+
22
+ ## Components
23
+
24
+ | Module | Responsibility |
25
+ |---|---|
26
+ | `config.ts` | Parse, normalize and validate versioned user configuration |
27
+ | `runner.ts` | Shell-free bounded child execution, environment construction, cancellation and process-tree cleanup |
28
+ | `oracle.ts` | Classify executions and normalize diagnostic observations |
29
+ | `snapshot.ts` | Safe inventory, immutable content, hashing, materialization and integrity checks |
30
+ | `transforms.ts` | Deterministic syntax and JSON candidates with narrow source edits |
31
+ | `graph.ts` | Static import graph and Next.js structure as search hints |
32
+ | `dependencies.ts` | npm lock integrity, installation and dependency-removal candidates |
33
+ | `engine.ts` | Baselines, hierarchical reduction, budgets, confirmations and events |
34
+ | `state.ts` | Atomic checkpoints, snapshot storage, resume compatibility and run locking |
35
+ | `export.ts` | Standalone reproduction, independent verifier and clean verification |
36
+ | `report.ts` | Safe, accessible offline HTML and machine-readable evidence |
37
+ | `cli.ts` | Commands, progress, errors and exit semantics |
38
+
39
+ These are modules in one package. A package network would add release coordination without improving the first user's workflow.
40
+
41
+ ## Execution and failure identity
42
+
43
+ The oracle has three outcomes. `reproduced` means normal termination with the exact expected exit and all required fragments, with no forbidden fragments. `absent` means a normal execution did not match. `invalid` means the execution could not establish an observation: failed setup, spawn error, signal, timeout, cancellation or output overflow. Invalid outcomes can never preserve a candidate.
44
+
45
+ Required positive fragments cannot be empty. Broad phrases such as “failed” should be replaced with a diagnostic identifier, offending symbol, location or other distinctive signal. A fixed control and a deliberately different failure are the strongest checks of an oracle. The tool displays its exact predicate and diagnostic observations; it does not silently strengthen or weaken them. A missing-import diagnostic may itself be the target, so the engine cannot blanket-ban that error family.
46
+
47
+ Baseline and final checks require three runs; acceptance requires two. Repetition is not a statistical guarantee of determinism. A failed calibration stops the run. Candidate caches skip previously rejected snapshots; they never supply confirmation or final verification.
48
+
49
+ Commands are argv arrays and use no implicit shell. The working directory, temporary home, installation configuration and build outputs are owned by the run. The environment inherits only operating-system essentials plus explicitly named variables. Logs are bounded and private to the run; report data contains summaries, not raw source/log dumps. Terminal control sequences are removed from displayed excerpts.
50
+
51
+ Local execution is not a security sandbox. Only run projects and commands you trust. npm installation scripts are disabled by default and can be enabled explicitly when required. Network access is needed for uncached packages and may also be used by the supplied command. Repro Surgeon has no telemetry or source-upload service.
52
+
53
+ ## Source and dependency integrity
54
+
55
+ The original project is read into an immutable snapshot. Git history, installed dependencies, generated output, environment files and known credential files are excluded. Ordinary untracked files are included; ignore rules and explicit overrides are visible in the inventory. Symlinks are rejected or excluded without following them. Export and state paths must not overlap the source. No command executes inside the original project.
56
+
57
+ Package manifests, lockfiles, licenses and notices are protected from generic file deletion. The package manager owns dependency reconciliation. Source candidates share only a tool-provisioned dependency installation for the same manifest/lock hash; they never link to the user's `node_modules`. Each candidate restores accepted source and clears build artifacts. Dependency changes use a new installation and cannot resolve removed dependencies from a stale tree. Retained package versions and integrity fields must not drift during lock reconciliation.
58
+
59
+ Projects with workspace, local file/link dependencies or private registry requirements are diagnosed explicitly. A portable export cannot quietly depend on the original checkout or developer credentials. Required environment variable names are reported; values are not copied into exported configuration.
60
+
61
+ ## Search
62
+
63
+ The objective is lower source bytes, then fewer files; dependency bytes and counts are reported separately. The scheduler is deterministic and serial by default. It starts with hierarchical file partitions and complements so it can remove mutually dependent irrelevant groups. It then tries syntax declarations/statements/import members/JSX children and JSON members/array items. Static imports, aliases and framework conventions prioritize candidates; only execution decides acceptance.
64
+
65
+ Range-based edits preserve unrelated formatting. Directives, shebangs, framework entry files, licenses and user-pinned paths receive conservative handling. Parseable source must remain parseable after structural edits. Files that already contain syntax errors can still be removed but are not structurally rewritten. JSON fixtures are reduced without changing package metadata. Dependency trials run after broad source reductions. Every accepted change restarts applicable search passes because new opportunities may appear.
66
+
67
+ All trials have stable content hashes. A checkpoint records config, runtime, baseline, current accepted snapshot, attempts, decisions, metrics and accumulated budget. Resuming from a valid accepted snapshot may repeat proposal enumeration, but cached rejected hashes and deterministic ordering prevent repeated execution. Interrupted trials are never marked accepted. Corrupted checkpoints and incompatible runtime/configuration are refused.
68
+
69
+ ## Export and report
70
+
71
+ The export preserves the source license and notices and places generated metadata under a reserved `.repro/` directory to avoid altering application scripts. The original command still fails as expected. A standalone verifier returns success only when that expected failure matches. It must work without installing Repro Surgeon.
72
+
73
+ All final source bytes, including generated verification metadata, are copied into a new validation directory before fresh installation and repeated checks. The checkpoint records the runtime and package manager. Its verification record stores the outcome, completed check count, reason, export snapshot hash and fresh-directory method; individual final command observations are not persisted. A clean directory on the same host is described exactly that way; container results are identified separately.
74
+
75
+ Review findings include likely secrets, absolute local paths, private package references and retained files. Findings are prompts for inspection, never privacy certification. The CLI never publishes a reproduction. The report is self-contained, escapes every untrusted string, uses no network resources, and labels accepted, rejected and invalid trials in text as well as color.
76
+
77
+ ## Release acceptance
78
+
79
+ - Each CLI command has meaningful success and failure-path coverage.
80
+ - Oracle controls reject a different error, timeout, signal and missing executable.
81
+ - Source hashes remain unchanged after success, failure and cancellation.
82
+ - File grouping, JavaScript/TypeScript/JSX, JSON, dependency integrity, checkpoint corruption, resume and export have executed regression cases.
83
+ - Packaged installation runs the complete example and independent exported verifier.
84
+ - Pinned App Router and Pages Router examples run through the real Next.js build command.
85
+ - Three independently chosen public bug cases are recorded with provenance, exact commands, controls, results and limitations. Seeded cases and reconstructions are labeled.
86
+ - CI executes the claimed runtime/OS matrix. Independent review checks correctness and the report's accessibility and rendering.
87
+ - README, license, contributing guide, security policy, issue templates, changelog and release artifacts are present and accurate. No fabricated benchmark, endorsement or adoption claim.
88
+
89
+ ## Prior art
90
+
91
+ The search builds on [delta debugging](https://www.st.cs.uni-saarland.de/papers/tse2002/). [treereduce](https://github.com/langston-barrett/treereduce) already supplies syntax-aware property-preserving reduction. [Replay](https://www.replay.io/debugging) investigates recorded execution. Repro Surgeon's intended contribution is a complete local application-source workflow with explicit failure checks and independently verified exports. Comparative superiority remains an empirical question.
Binary file
@@ -0,0 +1,73 @@
1
+ # Configuration
2
+
3
+ `repro-surgeon.json` is strict JSON. Unknown fields and invalid values fail early. `command` and `oracle` are required; everything else has a default.
4
+
5
+ ```json
6
+ {
7
+ "version": 1,
8
+ "name": "My reproduction",
9
+ "command": ["npm", "run", "build"],
10
+ "oracle": {
11
+ "exitCode": 1,
12
+ "allOf": ["a distinctive diagnostic"],
13
+ "noneOf": []
14
+ },
15
+ "adapter": "auto",
16
+ "budget": { "maxEvaluations": 200, "maxSeconds": 900 },
17
+ "runs": { "baseline": 3, "candidate": 2, "final": 3 },
18
+ "execution": {
19
+ "timeoutMs": 60000,
20
+ "maxOutputBytes": 1048576,
21
+ "installTimeoutMs": 120000,
22
+ "allowInstallScripts": false,
23
+ "env": []
24
+ },
25
+ "reduce": { "files": true, "syntax": true, "json": true, "dependencies": true },
26
+ "include": [],
27
+ "exclude": [],
28
+ "preserve": []
29
+ }
30
+ ```
31
+
32
+ ## Command and oracle
33
+
34
+ `command` is an argv array, run from a disposable copy of the project. There is no implicit shell, so redirection, pipes, globs and shell substitutions are literal arguments. Put a multi-step check in a script and name that script explicitly. Prefer portable executable names such as `node` and `npm`; absolute paths may not work for a recipient.
35
+
36
+ The oracle matches combined stdout/stderr after terminal-control normalization. `exitCode` is an integer from 0 through 255. `allOf` must contain at least one non-empty, case-sensitive literal fragment. Every required fragment must occur, and no `noneOf` fragment may occur. These are literals, not regular expressions. A normal nonmatching exit is `absent`; execution/setup failures are `invalid`.
37
+
38
+ Pin a checker with `preserve` when its assertion must remain unchanged. A target that prints its own entire diagnostic can sometimes be reduced to that print; a precise external assertion and controls make the observation more useful. A match alone does not prove a semantic root cause.
39
+
40
+ ## Search and execution limits
41
+
42
+ `maxEvaluations` counts actual candidate observations, including confirmation runs, plus failed dependency preparations. Baseline and export checks do not consume this count. `maxSeconds` covers calibration and search, including their setup. Inventory and the initial npm availability probe precede this timer. Filesystem cleanup/checkpoint work may finish shortly after a deadline, but an incomplete confirmation cannot be accepted.
43
+
44
+ After calibrated search reaches a budget, independent export verification runs separately. Each final target has `timeoutMs`, each new installation has `installTimeoutMs`, and output is bounded by `maxOutputBytes` combined across both streams. Set appropriate limits for your application. Abort remains available during final verification.
45
+
46
+ All limits must be positive safe integers. Baseline and final repetition counts cannot be below 3; candidate counts cannot be below 2. Increasing repetition can expose flakes but does not prove determinism.
47
+
48
+ `allowInstallScripts` defaults to false. Set it to true only for trusted dependencies whose lifecycle setup is necessary. Source-mutating install scripts may prevent independent verification; the exact exported source must still match its manifest after installation.
49
+
50
+ `env` lists variable **names**, not values. Essential OS settings such as PATH and locale are inherited; HOME and npm config/cache locations belong to the run. The tool sets CI, disables Next.js telemetry and requests plain output. Required values must be supplied independently by a recipient and are never embedded automatically. Avoid forwarding credentials. Explicitly forwarding loader or module-path variables changes your execution environment and can undermine portability.
51
+
52
+ ## Paths and adapters
53
+
54
+ `include`, `exclude`, and `preserve` accept project-relative paths, directory prefixes and glob patterns using `/` separators. Ordinary `.gitignore` rules, including nested rules, apply. `include` can override an ignore rule; explicit `exclude` wins. Neither can reintroduce hard-excluded material such as `.env` (including `.env.example`), `.npmrc`, credential keys, `node_modules`, `.git`, `.next`, `dist`, `build`, `.repro` or symbolic links.
55
+
56
+ `preserve` prevents eligible source from being deleted or rewritten; it does not include a file excluded from the initial inventory. Pinning either `package.json` or `package-lock.json` disables dependency reduction. Package metadata and license/notice files receive default protection from generic reductions.
57
+
58
+ `adapter` is `auto`, `next` or `generic`. Next.js detection records router and entrypoints, protects framework paths conservatively and guides file proposals. Protection of an entrypoint's path does not pin its contents; use `preserve` if its contents must remain unchanged. Heuristics never replace execution checks.
59
+
60
+ The snapshot limit is 128 MiB of included source. Installed dependencies are excluded from that limit and from source-byte metrics. Reported source bytes exclude `package.json` and lock metadata. Total snapshot bytes include them. Neither metric includes the generated `.repro` verifier or installed dependencies.
61
+
62
+ ## CLI results
63
+
64
+ | Command | Purpose |
65
+ |---|---|
66
+ | `init [project] --match TEXT -- COMMAND ARGS` | Write configuration without overwriting an existing file |
67
+ | `doctor [project] [--config FILE] [--json]` | Inventory and runtime/dependency diagnosis; no project code execution |
68
+ | `reduce [project] --out DIRECTORY [--config FILE]` | Start a new run outside the source tree |
69
+ | `resume DIRECTORY [--max-evaluations N] [--max-seconds N]` | Continue an accepted checkpoint with total-budget overrides |
70
+ | `verify EXPORTED_DIRECTORY [--json]` | Independently check the exported source |
71
+ | `report RUN_DIRECTORY [--json]` | Rebuild offline HTML and JSON from the checkpoint |
72
+
73
+ `--json` keeps progress on stderr and emits a structured result on stdout. Exit 0 means the requested command succeeded; `reduce`/`resume`/`verify` require verified export for exit 0. Exit 1 means unverified/paused result, exit 2 means an input or operational error, and an interrupted reduction returns 130 when a result can be checkpointed. Do not treat the application's expected nonzero exit as the CLI's failure.