fallow 3.41.0 → 3.43.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/README.md +3 -1
- package/capabilities.json +200 -41
- package/issue-registry.json +98 -32
- package/package.json +9 -10
- package/schema.json +116 -8
- package/scripts/lazy-verify.js +5 -4
- package/scripts/platform-package.js +25 -0
- package/scripts/run-binary.js +149 -30
- package/scripts/verify-binary.js +25 -21
- package/skills/fallow/SKILL.md +25 -3
- package/skills/fallow/references/cli-reference.md +55 -31
- package/skills/fallow/references/gotchas.md +2 -0
- package/skills/fallow/references/issue-types.md +2 -0
- package/skills/fallow/references/mcp.md +4 -4
- package/skills/fallow/references/similar-code.md +1 -1
- package/types/output-contract.d.ts +819 -36
|
@@ -23,6 +23,31 @@ function getPlatformPackage(platform, arch, libcFamily) {
|
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// The platform packages declare `libc`, so npm installs only the package for
|
|
27
|
+
// the libc that it detects at install time. The musl binaries are statically
|
|
28
|
+
// linked and also run on glibc, so a glibc host accepts an installed musl
|
|
29
|
+
// package (for example a node_modules tree that was built on Alpine). A glibc
|
|
30
|
+
// binary does not run on musl, so there is no fallback in that direction.
|
|
31
|
+
function getPlatformPackageCandidates(platform, arch, libcFamily) {
|
|
32
|
+
const primary = getPlatformPackage(platform, arch, libcFamily);
|
|
33
|
+
if (primary === null) return [];
|
|
34
|
+
if (primary.endsWith("-gnu")) return [primary, primary.replace(/-gnu$/, "-musl")];
|
|
35
|
+
return [primary];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resolveInstalledPlatformPackage(candidates, resolve = require.resolve) {
|
|
39
|
+
for (const pkg of candidates) {
|
|
40
|
+
try {
|
|
41
|
+
return { pkg, manifestPath: resolve(`${pkg}/package.json`) };
|
|
42
|
+
} catch {
|
|
43
|
+
// Not installed; try the next candidate.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
26
49
|
module.exports = {
|
|
27
50
|
getPlatformPackage,
|
|
51
|
+
getPlatformPackageCandidates,
|
|
52
|
+
resolveInstalledPlatformPackage,
|
|
28
53
|
};
|
package/scripts/run-binary.js
CHANGED
|
@@ -13,20 +13,23 @@ const path = require("node:path");
|
|
|
13
13
|
const fs = require("node:fs");
|
|
14
14
|
const os = require("node:os");
|
|
15
15
|
|
|
16
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
getPlatformPackageCandidates,
|
|
18
|
+
resolveInstalledPlatformPackage,
|
|
19
|
+
} = require("./platform-package");
|
|
17
20
|
const { ensureVerified } = require("./lazy-verify");
|
|
18
21
|
const { isPreSigningVersion } = require("./verify-binary");
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const SIMILAR_CODE_PROVIDER_ENV = "FALLOW_SIMILAR_CODE_PROVIDER";
|
|
24
|
+
|
|
25
|
+
function detectLibcFamily() {
|
|
26
|
+
if (process.platform !== "linux") return undefined;
|
|
24
27
|
try {
|
|
25
|
-
|
|
26
|
-
return getPlatformPackage(process.platform, process.arch, familySync());
|
|
28
|
+
return require("detect-libc").familySync();
|
|
27
29
|
} catch {
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
+
// Unknown libc selects the musl package: musl binaries are statically
|
|
31
|
+
// linked and work on both glibc and musl.
|
|
32
|
+
return null;
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
|
|
@@ -77,20 +80,28 @@ function describeVerifiedStatus(result) {
|
|
|
77
80
|
// Resolve the platform package directory + manifest path, or print an
|
|
78
81
|
// actionable error and exit. Keeps `runBinary` a flat top-level sequence.
|
|
79
82
|
function resolvePlatformPaths() {
|
|
80
|
-
const
|
|
81
|
-
|
|
83
|
+
const libcFamily = detectLibcFamily();
|
|
84
|
+
const candidates = getPlatformPackageCandidates(process.platform, process.arch, libcFamily);
|
|
85
|
+
if (candidates.length === 0) {
|
|
82
86
|
process.stderr.write(`Unsupported platform: ${process.platform}-${process.arch}\n`);
|
|
83
87
|
process.exit(1);
|
|
84
88
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
} catch {
|
|
89
|
-
process.stderr.write(
|
|
90
|
-
`Could not find ${pkg}. Run 'npm install' to install the platform-specific binary.\n`,
|
|
91
|
-
);
|
|
89
|
+
const resolved = resolveInstalledPlatformPackage(candidates);
|
|
90
|
+
if (resolved === null) {
|
|
91
|
+
process.stderr.write(missingPlatformPackageMessage(candidates[0], libcFamily));
|
|
92
92
|
process.exit(1);
|
|
93
93
|
}
|
|
94
|
+
return { ...resolved, platformPkgDir: path.dirname(resolved.manifestPath) };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// npm installs a Linux platform package only for the libc that it detects at
|
|
98
|
+
// install time, so a tree installed for another libc, or from macOS or
|
|
99
|
+
// Windows without `--libc`, has no matching binary.
|
|
100
|
+
function missingPlatformPackageMessage(pkg, libcFamily) {
|
|
101
|
+
const hint = libcFamily
|
|
102
|
+
? ` This host uses ${libcFamily}. When node_modules was installed on another libc or OS, reinstall with \`npm install --os=linux --cpu=${process.arch} --libc=${libcFamily}\`.`
|
|
103
|
+
: "";
|
|
104
|
+
return `Could not find ${pkg}. Run 'npm install' to install the platform-specific binary.${hint}\n`;
|
|
94
105
|
}
|
|
95
106
|
|
|
96
107
|
function printVerifyError(verifyResult) {
|
|
@@ -134,22 +145,113 @@ function readResolvedVersion(manifestPath) {
|
|
|
134
145
|
}
|
|
135
146
|
}
|
|
136
147
|
|
|
148
|
+
// `fallow` does not depend on its companions (`fallow-type-aware`,
|
|
149
|
+
// `fallow-similar-code`): a project installs one at the same version when it
|
|
150
|
+
// uses it. npm and Yarn hoist it next to `fallow`. pnpm links it only into the
|
|
151
|
+
// project node_modules, so a second lookup starts at the project root. That
|
|
152
|
+
// lookup accepts a companion only when it is in the same node_modules tree as
|
|
153
|
+
// the running `fallow` package, so a global `fallow` never runs a companion
|
|
154
|
+
// that a cloned project ships. Only an exact version match is accepted.
|
|
155
|
+
function resolveCompanionManifest(
|
|
156
|
+
packageName,
|
|
157
|
+
fallowVersion,
|
|
158
|
+
{
|
|
159
|
+
resolvePackage = require.resolve,
|
|
160
|
+
readFile = fs.readFileSync,
|
|
161
|
+
projectRoot = process.cwd(),
|
|
162
|
+
launcherDir = __dirname,
|
|
163
|
+
} = {},
|
|
164
|
+
) {
|
|
165
|
+
const local = matchingManifest(packageName, fallowVersion, resolvePackage, readFile, undefined);
|
|
166
|
+
if (local !== undefined) return local;
|
|
167
|
+
const project = matchingManifest(packageName, fallowVersion, resolvePackage, readFile, {
|
|
168
|
+
paths: [projectRoot],
|
|
169
|
+
});
|
|
170
|
+
if (project === undefined) return undefined;
|
|
171
|
+
const tree = installTree(launcherDir);
|
|
172
|
+
return tree !== null && tree === installTree(project) ? project : undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function matchingManifest(packageName, fallowVersion, resolvePackage, readFile, options) {
|
|
176
|
+
try {
|
|
177
|
+
const manifestPath = resolvePackage(`${packageName}/package.json`, options);
|
|
178
|
+
const manifest = JSON.parse(readFile(manifestPath, "utf8"));
|
|
179
|
+
return manifest.version === fallowVersion ? manifestPath : undefined;
|
|
180
|
+
} catch {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// The outermost node_modules directory of a real path, or null outside one.
|
|
186
|
+
function installTree(file) {
|
|
187
|
+
let real;
|
|
188
|
+
try {
|
|
189
|
+
real = fs.realpathSync.native(file);
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const parts = real.split(path.sep);
|
|
194
|
+
const index = parts.indexOf("node_modules");
|
|
195
|
+
return index === -1 ? null : parts.slice(0, index + 1).join(path.sep);
|
|
196
|
+
}
|
|
197
|
+
|
|
137
198
|
function resolveTypeAwareCompanion(
|
|
138
199
|
fallowVersion,
|
|
139
200
|
resolvePackage = require.resolve,
|
|
140
201
|
readFile = fs.readFileSync,
|
|
202
|
+
projectRoot = process.cwd(),
|
|
203
|
+
launcherDir = __dirname,
|
|
141
204
|
) {
|
|
205
|
+
const manifestPath = resolveCompanionManifest("fallow-type-aware", fallowVersion, {
|
|
206
|
+
resolvePackage,
|
|
207
|
+
readFile,
|
|
208
|
+
projectRoot,
|
|
209
|
+
launcherDir,
|
|
210
|
+
});
|
|
211
|
+
if (manifestPath === undefined) return undefined;
|
|
212
|
+
const companion = path.join(path.dirname(manifestPath), "fallow-type-aware.mjs");
|
|
213
|
+
return fs.existsSync(companion) ? companion : undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// The binary of the exact-version `fallow-similar-code` package, after its own
|
|
217
|
+
// resolver and verifier accept it (the Node API loader uses the same pair).
|
|
218
|
+
// The Rust code checks the signature again before it runs the binary.
|
|
219
|
+
function resolveSimilarCodeProvider(fallowVersion, options = {}) {
|
|
220
|
+
const { report = reportToStderr } = options;
|
|
221
|
+
const manifestPath = resolveCompanionManifest("fallow-similar-code", fallowVersion, options);
|
|
222
|
+
if (manifestPath === undefined) return undefined;
|
|
223
|
+
const scripts = path.join(path.dirname(manifestPath), "scripts");
|
|
142
224
|
try {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
225
|
+
const { resolveBinaryArtifact, resolvePlatformPackage } = require(
|
|
226
|
+
path.join(scripts, "run-binary.js"),
|
|
227
|
+
);
|
|
228
|
+
const { verifyBinary } = require(path.join(scripts, "verify-binary.js"));
|
|
229
|
+
const artifact = resolveBinaryArtifact(resolvePlatformPackage());
|
|
230
|
+
const verification = verifyBinary(artifact);
|
|
231
|
+
if (verification.ok) return artifact.binaryPath;
|
|
232
|
+
report(`fallow: the similar-code companion failed verification: ${verification.message}\n`);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
report(`fallow: the similar-code companion failed verification: ${error.message}\n`);
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Write to fd 2 directly: under Bun, `process.stderr` makes the pipes that the
|
|
240
|
+
// child inherits non-blocking (issue #3276). A failed write is ignored.
|
|
241
|
+
function reportToStderr(message) {
|
|
242
|
+
try {
|
|
243
|
+
fs.writeSync(2, message);
|
|
148
244
|
} catch {
|
|
149
|
-
|
|
245
|
+
// Best effort.
|
|
150
246
|
}
|
|
151
247
|
}
|
|
152
248
|
|
|
249
|
+
// Only these commands start the similar-code provider, so other runs skip the
|
|
250
|
+
// lookup and its digest check.
|
|
251
|
+
function usesSimilarCode(args) {
|
|
252
|
+
return args.includes("similar-code") || args.includes("mcp-server");
|
|
253
|
+
}
|
|
254
|
+
|
|
153
255
|
function typeAwareCommand(
|
|
154
256
|
companion,
|
|
155
257
|
{ platform = process.platform, execPath = process.execPath } = {},
|
|
@@ -163,14 +265,27 @@ function childEnvironment(
|
|
|
163
265
|
resolvedVersion,
|
|
164
266
|
resolveCompanion = resolveTypeAwareCompanion,
|
|
165
267
|
commandOptions,
|
|
268
|
+
{ args = [], resolveProvider = resolveSimilarCodeProvider } = {},
|
|
166
269
|
) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
270
|
+
let environment = process.env;
|
|
271
|
+
if (!process.env.FALLOW_TYPE_AWARE_BIN) {
|
|
272
|
+
const companion = resolveCompanion(resolvedVersion);
|
|
273
|
+
if (companion !== undefined) {
|
|
274
|
+
environment = typeAwareEnvironment(environment, typeAwareCommand(companion, commandOptions));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (!process.env[SIMILAR_CODE_PROVIDER_ENV] && usesSimilarCode(args)) {
|
|
278
|
+
const provider = resolveProvider(resolvedVersion);
|
|
279
|
+
if (provider !== undefined) {
|
|
280
|
+
environment = { ...environment, [SIMILAR_CODE_PROVIDER_ENV]: provider };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return environment;
|
|
284
|
+
}
|
|
170
285
|
|
|
171
|
-
|
|
286
|
+
function typeAwareEnvironment(base, command) {
|
|
172
287
|
const environment = {
|
|
173
|
-
...
|
|
288
|
+
...base,
|
|
174
289
|
FALLOW_TYPE_AWARE_BIN: command.binary,
|
|
175
290
|
// Marks the wiring as launcher-provided node_modules resolution so
|
|
176
291
|
// `type-aware status` does not report it as a user-set override.
|
|
@@ -232,7 +347,9 @@ function runBinary(binaryBaseName, options = {}) {
|
|
|
232
347
|
try {
|
|
233
348
|
execFileSync(binaryPath, [...prependArgs, ...process.argv.slice(2)], {
|
|
234
349
|
stdio: "inherit",
|
|
235
|
-
env: childEnvironment(resolvedVersion
|
|
350
|
+
env: childEnvironment(resolvedVersion, undefined, undefined, {
|
|
351
|
+
args: [...prependArgs, ...process.argv.slice(2)],
|
|
352
|
+
}),
|
|
236
353
|
});
|
|
237
354
|
} catch (e) {
|
|
238
355
|
if (e.status === undefined) throw e;
|
|
@@ -254,6 +371,8 @@ module.exports = {
|
|
|
254
371
|
isVersionQuery, // test-only
|
|
255
372
|
guardBrokenStdout, // test-only
|
|
256
373
|
exitCodeForChildFailure, // test-only
|
|
374
|
+
missingPlatformPackageMessage, // test-only
|
|
257
375
|
resolveTypeAwareCompanion, // test-only
|
|
376
|
+
resolveSimilarCodeProvider, // test-only
|
|
258
377
|
childEnvironment, // test-only
|
|
259
378
|
};
|
package/scripts/verify-binary.js
CHANGED
|
@@ -31,7 +31,10 @@ const crypto = require("node:crypto");
|
|
|
31
31
|
const fs = require("node:fs");
|
|
32
32
|
const https = require("node:https");
|
|
33
33
|
const path = require("node:path");
|
|
34
|
-
const {
|
|
34
|
+
const {
|
|
35
|
+
getPlatformPackageCandidates,
|
|
36
|
+
resolveInstalledPlatformPackage,
|
|
37
|
+
} = require("./platform-package");
|
|
35
38
|
|
|
36
39
|
const GITHUB_REPO = "fallow-rs/fallow";
|
|
37
40
|
const DIGEST_TIMEOUT_MS = 10000;
|
|
@@ -296,16 +299,10 @@ function binaryTargetsForPlatform(platformId) {
|
|
|
296
299
|
// and tests can synthesize a Windows verify without running on Windows.
|
|
297
300
|
const isWindows = typeof platformId === "string" && platformId.startsWith("win32");
|
|
298
301
|
const ext = isWindows ? ".exe" : "";
|
|
299
|
-
// The platform package ships the multicall CLI
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
return [
|
|
303
|
-
{ binary: `fallow${ext}`, asset: `fallow-${platformId}${ext}` },
|
|
304
|
-
{
|
|
305
|
-
binary: `fallow-similar-code${ext}`,
|
|
306
|
-
asset: `fallow-similar-code-${platformId}${ext}`,
|
|
307
|
-
},
|
|
308
|
-
];
|
|
302
|
+
// The platform package ships only the multicall CLI. The similar-code
|
|
303
|
+
// provider ships in the `fallow-similar-code` package, whose own verifier
|
|
304
|
+
// checks it.
|
|
305
|
+
return [{ binary: `fallow${ext}`, asset: `fallow-${platformId}${ext}` }];
|
|
309
306
|
}
|
|
310
307
|
|
|
311
308
|
function isSkipRequested(env = process.env) {
|
|
@@ -313,17 +310,24 @@ function isSkipRequested(env = process.env) {
|
|
|
313
310
|
return v === "1" || v === "true" || v === "yes";
|
|
314
311
|
}
|
|
315
312
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
313
|
+
// The same selection as the launcher in run-binary.js, so that verification
|
|
314
|
+
// checks the binary that the launcher runs.
|
|
315
|
+
function currentPlatformPackageName(resolveFrom) {
|
|
320
316
|
let libcFamily;
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
317
|
+
if (process.platform === "linux") {
|
|
318
|
+
try {
|
|
319
|
+
libcFamily = require("detect-libc").familySync();
|
|
320
|
+
} catch {
|
|
321
|
+
libcFamily = undefined;
|
|
322
|
+
}
|
|
325
323
|
}
|
|
326
|
-
|
|
324
|
+
const candidates = getPlatformPackageCandidates(process.platform, process.arch, libcFamily);
|
|
325
|
+
if (candidates.length === 0) return null;
|
|
326
|
+
const options = resolveFrom ? { paths: [resolveFrom] } : undefined;
|
|
327
|
+
const installed = resolveInstalledPlatformPackage(candidates, (request) =>
|
|
328
|
+
require.resolve(request, options),
|
|
329
|
+
);
|
|
330
|
+
return installed ? installed.pkg : candidates[0];
|
|
327
331
|
}
|
|
328
332
|
|
|
329
333
|
function readManifestForPackage(manifestPath, pkg) {
|
|
@@ -366,7 +370,7 @@ function resolvePlatformPackageForVerify(opts) {
|
|
|
366
370
|
};
|
|
367
371
|
}
|
|
368
372
|
|
|
369
|
-
const pkg = currentPlatformPackageName();
|
|
373
|
+
const pkg = currentPlatformPackageName(opts.resolveFrom);
|
|
370
374
|
if (!pkg) {
|
|
371
375
|
return {
|
|
372
376
|
ok: false,
|
package/skills/fallow/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fallow
|
|
3
|
-
description: Codebase intelligence for TypeScript and JavaScript. Static analysis reports changed-code risk, cleanup opportunities, duplication, circular dependencies, complexity hotspots, architecture boundaries, design-system drift, feature flags, and opt-in security candidates. Optional local similar-code discovery finds functions that may implement the same intent despite different syntax. Runtime coverage can merge production execution data. Use when asked to audit PR risk, find unused code or dependencies, compare semantically similar functions, detect duplicates, inspect architecture boundaries, merge runtime coverage, auto-fix supported issues, or run fallow.
|
|
3
|
+
description: Codebase intelligence for TypeScript and JavaScript. Static analysis reports changed-code risk, cleanup opportunities, duplication, circular dependencies, complexity hotspots, architecture boundaries, design-system drift, feature flags, and opt-in security candidates. Opt-in mutation testing reports the code changes that no test detects. Optional local similar-code discovery finds functions that may implement the same intent despite different syntax. Runtime coverage can merge production execution data. Use when asked to audit PR risk, find unused code or dependencies, compare semantically similar functions, detect duplicates, inspect architecture boundaries, merge runtime coverage, improve or check the tests of a change, auto-fix supported issues, or run fallow.
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -19,6 +19,7 @@ Fallow analyzes code and styles for changed-code risk, cleanup opportunities, ci
|
|
|
19
19
|
- Investigate why a specific export, dependency, file, or issue type was reported.
|
|
20
20
|
- Surface local security candidates for an agent to verify (`fallow security`).
|
|
21
21
|
- Find untested but runtime-reachable code (`fallow health --coverage-gaps`).
|
|
22
|
+
- Improve or check the tests of a change: find the small code changes (mutants) that no test detects (`fallow mutation`, opt-in, because a run executes the project tests).
|
|
22
23
|
- Rank complexity hotspots, owners, and refactoring targets (`fallow health --hotspots --ownership --targets`).
|
|
23
24
|
- Review what fallow has surfaced over time (`fallow impact`).
|
|
24
25
|
- Confirm exact TypeScript symbol use, affected tests, API leaks, or public type coupling when syntactic evidence is insufficient (`--type-aware`).
|
|
@@ -92,6 +93,7 @@ Route by intent before reaching for the big analysis commands. Same matrix as `f
|
|
|
92
93
|
| prioritize refactoring | `fallow health --hotspots --targets` |
|
|
93
94
|
| ask who owns code | `fallow health --ownership` |
|
|
94
95
|
| check untested-but-reachable code | `fallow health --coverage-gaps` |
|
|
96
|
+
| improve or check the tests of a change | `fallow mutation --plan --base <ref>`; The plan runs no code. Without `--plan`, the command runs the project tests against each mutant, so ask the user first. |
|
|
95
97
|
| consolidate duplication | `fallow dupes --trace dup:<fingerprint>` |
|
|
96
98
|
| find feature flags | `fallow flags` |
|
|
97
99
|
| check which architecture rules apply to a file before changing it | `fallow guard <files>` |
|
|
@@ -183,6 +185,17 @@ Step 3 is your review. Read `digest.decisions[]` and `change_anchors[]` in the g
|
|
|
183
185
|
|
|
184
186
|
Fallow validates the judgment against the live graph. It rejects an id that it did not emit. It refuses the whole file as `stale` when the hash no longer matches. Report only the `accepted` judgments. Keep the guide outside the project: a new file in the tree changes the hash. Do not create `.fallow/` for it, because a folder that you create has no ignore file. On `stale: true`, fetch the guide again.
|
|
185
187
|
|
|
188
|
+
### Improve the tests of a change
|
|
189
|
+
```bash
|
|
190
|
+
fallow mutation --plan --base main --format json --quiet # the plan: runs no code
|
|
191
|
+
fallow mutation --base main --format json --quiet # the run: executes the project tests
|
|
192
|
+
fallow mutation tasks --format json --quiet # the open mutants: runs no code
|
|
193
|
+
fallow mutation inspect <mutant_id> --format json --quiet # one mutant: runs no code
|
|
194
|
+
fallow mutation --recheck --format json --quiet # after new tests: runs only the open mutants
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Start the run when the user asks for better tests or for mutation testing. In each other task, show the plan and ask. A run exits 2 on uncommitted files: pass `--allow-dirty`, and do not commit for the user. A `survived` mutant is a code change that no test detects. Write a behavior test for each one that a caller can observe, then recheck. Do not write a test that only pins an implementation detail, and do not pass `--allow-side-effects` unless the user asks. The runners are Vitest, `node:test` and `bun test`. The Fallow plugin has a `fallow-mutation` skill with the full method.
|
|
198
|
+
|
|
186
199
|
### Find code duplication
|
|
187
200
|
```bash
|
|
188
201
|
fallow dupes --format json --quiet
|
|
@@ -215,6 +228,13 @@ fallow dead-code --format json --quiet --production
|
|
|
215
228
|
|
|
216
229
|
Excludes test/dev files (`*.test.*`, `*.spec.*`, `*.stories.*`) and only analyzes production scripts.
|
|
217
230
|
|
|
231
|
+
### Review parameters that each call passes in one way
|
|
232
|
+
```bash
|
|
233
|
+
fallow dead-code --constant-parameters --production --format json --quiet
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The opt-in rule reports a function parameter that each visible call passes the same value (`identical-argument`), or an optional parameter that no call passes (`never-passed`). The same value is the same literal, or the same `const` with a literal initializer or enum member (`value.kind` is `binding`). A finding with `option_key` is about one key of an object-pattern parameter, such as `retries` in `{ retries = 3 }`. It reports only when it sees each caller, and it never reports a function that a test file declares. Start with findings where `condition_reads` is above 0: a condition tests the constant, so a branch cannot run. Read the `inspected_call_sites` and the intent before a change. Keep a deliberate option, a test seam, or a parameter that matches a sibling API. `--production` drops test callers. With `--type-aware`, the rule also reports `unused-default` (each call passes a value whose type excludes `undefined`, so the default never applies) and reads the methods of exported classes (`function_name` is `Class.method`). These findings carry `evidence: "type-aware"`: a type does not prove a run-time value, so check that no `any` value or cast reaches the parameter. There is no auto-fix.
|
|
237
|
+
|
|
218
238
|
### Analyze specific workspaces
|
|
219
239
|
```bash
|
|
220
240
|
fallow dead-code --format json --quiet --workspace my-package # single package (lists: web,admin)
|
|
@@ -355,7 +375,9 @@ fallow dead-code --type-aware --symbol-impact src/api.ts:Client --format json --
|
|
|
355
375
|
fallow health --type-aware --type-coupling --format json --quiet
|
|
356
376
|
```
|
|
357
377
|
|
|
358
|
-
The
|
|
378
|
+
The `fallow-type-aware` companion is an install on demand
|
|
379
|
+
(`npm install --save-dev fallow-type-aware@<fallow version>`), and it must
|
|
380
|
+
match the installed Fallow version. Semantic
|
|
359
381
|
results expose completeness, per-candidate decisions, and omissions.
|
|
360
382
|
`confirmed-used` and `contract-preserved` remove syntactic false positives.
|
|
361
383
|
`confirmed-no-static-references` retains the finding and only enables a guarded
|
|
@@ -375,7 +397,7 @@ Auto-detects `knip.json`, `knip.jsonc`, `.knip.json`, `.knip.jsonc`, `.jscpd.jso
|
|
|
375
397
|
```bash
|
|
376
398
|
fallow init # creates .fallowrc.json, adds .fallow/ to .gitignore (--toml for fallow.toml)
|
|
377
399
|
fallow init --agents # scaffolds a starter AGENTS.md prefilled from detected project info (never overwrites)
|
|
378
|
-
fallow hooks install --target git # pre-commit gate
|
|
400
|
+
fallow hooks install --target git # pre-commit gate: audits the staged index (fallow audit --staged)
|
|
379
401
|
```
|
|
380
402
|
|
|
381
403
|
## Exit Codes
|