loose-ends 0.0.0-stage → 1.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.
- package/README.md +13 -2
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +6 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/check.d.ts +21 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +75 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/fix.d.ts +4 -0
- package/dist/commands/fix.d.ts.map +1 -0
- package/dist/commands/fix.js +73 -0
- package/dist/commands/fix.js.map +1 -0
- package/dist/commands/inspect.d.ts +4 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +100 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/io.d.ts +12 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +22 -0
- package/dist/io.js.map +1 -0
- package/package.json +46 -4
package/README.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
#
|
|
1
|
+
# loose-ends
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Command-line interface for [Loose Ends](https://github.com/sid732/loose-ends), the graph-powered liveness checks for AI-written code.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx loose-ends check # report new loose ends in this change
|
|
7
|
+
npx loose-ends scan # report existing unreachable code
|
|
8
|
+
npx loose-ends explain <path#symbol>
|
|
9
|
+
npx loose-ends roots
|
|
10
|
+
npx loose-ends fix --verify --patch cleanup.diff
|
|
11
|
+
npx loose-ends init
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
See the [main README](https://github.com/sid732/loose-ends#readme) for details.
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
|
package/dist/bin.js
ADDED
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEjC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;IACvC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type FindingKind, type LooseEndsConfig, type ReportFormat } from "@loose-ends/core";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
import { type CliIO } from "../io.js";
|
|
4
|
+
export interface ReportFlags {
|
|
5
|
+
format: string;
|
|
6
|
+
output?: string;
|
|
7
|
+
color?: boolean;
|
|
8
|
+
linkBase?: string;
|
|
9
|
+
pathPrefix?: string;
|
|
10
|
+
failOn?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class UsageError extends Error {
|
|
13
|
+
}
|
|
14
|
+
export declare function parseFormat(value: string): ReportFormat;
|
|
15
|
+
export declare function parseKinds(value: string | undefined): FindingKind[] | undefined;
|
|
16
|
+
export declare function overrides(flags: ReportFlags & {
|
|
17
|
+
coverage?: string;
|
|
18
|
+
}): Partial<LooseEndsConfig>;
|
|
19
|
+
export declare function addReportOptions(cmd: Command): Command;
|
|
20
|
+
export declare function registerCheck(program: Command, io: CliIO, setExit: (code: number) => void): void;
|
|
21
|
+
//# sourceMappingURL=check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AACA,OAAO,EAA4D,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACvJ,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAQ,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AAE5C,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAExC,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAKvD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,EAAE,GAAG,SAAS,CAQ/E;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAM9F;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAStD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAmChG"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { FINDING_KINDS, REPORT_FORMATS, check, formatReport, scan } from "@loose-ends/core";
|
|
3
|
+
import { emit } from "../io.js";
|
|
4
|
+
export class UsageError extends Error {
|
|
5
|
+
}
|
|
6
|
+
export function parseFormat(value) {
|
|
7
|
+
if (!REPORT_FORMATS.includes(value)) {
|
|
8
|
+
throw new UsageError(`unknown format "${value}" (expected ${REPORT_FORMATS.join(", ")})`);
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
export function parseKinds(value) {
|
|
13
|
+
if (value === undefined)
|
|
14
|
+
return undefined;
|
|
15
|
+
const kinds = value
|
|
16
|
+
.split(",")
|
|
17
|
+
.map((k) => k.trim())
|
|
18
|
+
.filter(Boolean);
|
|
19
|
+
for (const k of kinds)
|
|
20
|
+
if (!FINDING_KINDS.includes(k))
|
|
21
|
+
throw new UsageError(`unknown finding kind "${k}"`);
|
|
22
|
+
return kinds;
|
|
23
|
+
}
|
|
24
|
+
export function overrides(flags) {
|
|
25
|
+
const out = {};
|
|
26
|
+
const failOn = parseKinds(flags.failOn);
|
|
27
|
+
if (failOn)
|
|
28
|
+
out.failOn = failOn;
|
|
29
|
+
if (flags.coverage)
|
|
30
|
+
out.coverage = flags.coverage;
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
export function addReportOptions(cmd) {
|
|
34
|
+
return cmd
|
|
35
|
+
.option("-f, --format <format>", `output format: ${REPORT_FORMATS.join(", ")}`, "text")
|
|
36
|
+
.option("-o, --output <file>", "write the report to a file instead of stdout")
|
|
37
|
+
.option("--color", "force colored text output")
|
|
38
|
+
.option("--no-color", "disable colored text output")
|
|
39
|
+
.option("--link-base <url>", "prefix for file links in markdown output")
|
|
40
|
+
.option("--path-prefix <path>", "prefix for file paths in SARIF output (project path inside the repo)")
|
|
41
|
+
.option("--fail-on <kinds>", "comma-separated finding kinds that fail the run");
|
|
42
|
+
}
|
|
43
|
+
export function registerCheck(program, io, setExit) {
|
|
44
|
+
addReportOptions(program
|
|
45
|
+
.command("check")
|
|
46
|
+
.description("compare a change against its base and report new loose ends")
|
|
47
|
+
.argument("[dir]", "project root", ".")
|
|
48
|
+
.option("-b, --base <ref>", "base revision (default: merge base with origin/HEAD, main, or master)")
|
|
49
|
+
.option("--head <ref>", "head revision (default: the working tree, including uncommitted changes)")
|
|
50
|
+
.option("--coverage <file>", "coverage report (istanbul JSON or lcov) used as supporting evidence")
|
|
51
|
+
.option("--no-fail", "always exit 0, even when blocking findings exist")).action((dir, flags) => {
|
|
52
|
+
const format = parseFormat(flags.format);
|
|
53
|
+
const result = check({ root: resolve(io.cwd, dir), base: flags.base, head: flags.head, config: overrides(flags) });
|
|
54
|
+
emit(io, formatReport(result, format, { color: flags.color ?? io.color, linkBase: flags.linkBase, pathPrefix: flags.pathPrefix }), flags.output);
|
|
55
|
+
if (flags.output) {
|
|
56
|
+
io.stderr(`${result.passed ? "pass" : "fail"}: ${result.failing.length} blocking of ${result.findings.length} findings → ${flags.output}\n`);
|
|
57
|
+
}
|
|
58
|
+
setExit(result.passed || !flags.fail ? 0 : 1);
|
|
59
|
+
});
|
|
60
|
+
addReportOptions(program
|
|
61
|
+
.command("scan")
|
|
62
|
+
.description("report every unreachable file, symbol, and dependency in the project")
|
|
63
|
+
.argument("[dir]", "project root", ".")
|
|
64
|
+
.option("--coverage <file>", "coverage report (istanbul JSON or lcov) used as supporting evidence")
|
|
65
|
+
.option("--fail", "exit 1 when findings in failOn exist (scan never fails by default)")).action((dir, flags) => {
|
|
66
|
+
const format = parseFormat(flags.format);
|
|
67
|
+
const config = overrides(flags);
|
|
68
|
+
if (flags.fail && !config.failOn)
|
|
69
|
+
config.failOn = ["orphaned", "invalid-suppression"];
|
|
70
|
+
const result = scan({ root: resolve(io.cwd, dir), config });
|
|
71
|
+
emit(io, formatReport(result, format, { color: flags.color ?? io.color, linkBase: flags.linkBase, pathPrefix: flags.pathPrefix }), flags.output);
|
|
72
|
+
setExit(flags.fail && !result.passed ? 1 : 0);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAA6D,MAAM,kBAAkB,CAAC;AAEvJ,OAAO,EAAE,IAAI,EAAc,MAAM,UAAU,CAAC;AAW5C,MAAM,OAAO,UAAW,SAAQ,KAAK;CAAG;AAExC,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,KAAqB,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,UAAU,CAAC,mBAAmB,KAAK,eAAe,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,KAAqB,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAyB;IAClD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,KAAK,GAAG,KAAK;SAChB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;IACnB,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAgB,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,yBAAyB,CAAC,GAAG,CAAC,CAAC;IAC1H,OAAO,KAAsB,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAA0C;IAClE,MAAM,GAAG,GAA6B,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,MAAM;QAAE,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;IAChC,IAAI,KAAK,CAAC,QAAQ;QAAE,GAAG,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;IAClD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,OAAO,GAAG;SACP,MAAM,CAAC,uBAAuB,EAAE,kBAAkB,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC;SACtF,MAAM,CAAC,qBAAqB,EAAE,8CAA8C,CAAC;SAC7E,MAAM,CAAC,SAAS,EAAE,2BAA2B,CAAC;SAC9C,MAAM,CAAC,YAAY,EAAE,6BAA6B,CAAC;SACnD,MAAM,CAAC,mBAAmB,EAAE,0CAA0C,CAAC;SACvE,MAAM,CAAC,sBAAsB,EAAE,sEAAsE,CAAC;SACtG,MAAM,CAAC,mBAAmB,EAAE,iDAAiD,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAgB,EAAE,EAAS,EAAE,OAA+B;IACxF,gBAAgB,CACd,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,6DAA6D,CAAC;SAC1E,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,CAAC;SACtC,MAAM,CAAC,kBAAkB,EAAE,uEAAuE,CAAC;SACnG,MAAM,CAAC,cAAc,EAAE,0EAA0E,CAAC;SAClG,MAAM,CAAC,mBAAmB,EAAE,qEAAqE,CAAC;SAClG,MAAM,CAAC,WAAW,EAAE,kDAAkD,CAAC,CAC3E,CAAC,MAAM,CAAC,CAAC,GAAW,EAAE,KAAuF,EAAE,EAAE;QAChH,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACnH,IAAI,CAAC,EAAE,EAAE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACjJ,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjB,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,OAAO,CAAC,MAAM,gBAAgB,MAAM,CAAC,QAAQ,CAAC,MAAM,eAAe,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;QAC/I,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,gBAAgB,CACd,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,sEAAsE,CAAC;SACnF,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,CAAC;SACtC,MAAM,CAAC,mBAAmB,EAAE,qEAAqE,CAAC;SAClG,MAAM,CAAC,QAAQ,EAAE,oEAAoE,CAAC,CAC1F,CAAC,MAAM,CAAC,CAAC,GAAW,EAAE,KAA0D,EAAE,EAAE;QACnF,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,MAAM,CAAC,MAAM,GAAG,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAC;QACtF,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC,EAAE,EAAE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACjJ,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fix.d.ts","sourceRoot":"","sources":["../../src/commands/fix.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAQ,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AAW5C,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAgE9F"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { applyCleanup, check, planCleanup, scan, verifyCleanup } from "@loose-ends/core";
|
|
3
|
+
import { emit } from "../io.js";
|
|
4
|
+
export function registerFix(program, io, setExit) {
|
|
5
|
+
program
|
|
6
|
+
.command("fix")
|
|
7
|
+
.description("plan removal of loose ends, verify it with your checks, and optionally apply it")
|
|
8
|
+
.argument("[dir]", "project root", ".")
|
|
9
|
+
.option("-b, --base <ref>", "base revision for change mode")
|
|
10
|
+
.option("--scan", "plan cleanup for the whole project instead of just this change")
|
|
11
|
+
.option("--verify", "apply the plan in a scratch copy and run the configured checks")
|
|
12
|
+
.option("--apply", "write the cleanup to the working tree (runs --verify first when checks exist)")
|
|
13
|
+
.option("--patch <file>", "write the cleanup as a unified diff")
|
|
14
|
+
.option("-c, --check <command...>", "check commands to run instead of config `checks`")
|
|
15
|
+
.action((dir, flags) => {
|
|
16
|
+
const root = resolve(io.cwd, dir);
|
|
17
|
+
const result = flags.scan ? scan({ root, keepAnalyses: true }) : check({ root, base: flags.base, keepAnalyses: true });
|
|
18
|
+
const plan = planCleanup(result.analyses.head, result.findings);
|
|
19
|
+
const checks = flags.check ?? result.config.checks;
|
|
20
|
+
if (plan.actions.length === 0) {
|
|
21
|
+
io.stdout(result.findings.length ? "Nothing can be removed automatically.\n" : "No loose ends to clean up.\n");
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
io.stdout(`Cleanup plan (${plan.actions.length} change${plan.actions.length === 1 ? "" : "s"}):\n`);
|
|
25
|
+
for (const a of plan.actions) {
|
|
26
|
+
io.stdout(a.type === "delete-file" ? ` delete ${a.path}\n` : ` remove ${a.path}#${a.symbol} (line ${a.line})\n`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (plan.skipped.length) {
|
|
30
|
+
io.stdout(`\nLeft for manual review (${plan.skipped.length}):\n`);
|
|
31
|
+
for (const s of plan.skipped)
|
|
32
|
+
io.stdout(` ${s.target}: ${s.reason}\n`);
|
|
33
|
+
}
|
|
34
|
+
if (plan.actions.length === 0) {
|
|
35
|
+
setExit(0);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const shouldVerify = flags.verify || (flags.apply && checks.length > 0);
|
|
39
|
+
const verification = verifyCleanup(root, plan, shouldVerify ? checks : []);
|
|
40
|
+
if (flags.patch) {
|
|
41
|
+
emit(io, verification.patch, flags.patch);
|
|
42
|
+
if (flags.patch !== "-")
|
|
43
|
+
io.stdout(`\nPatch written to ${flags.patch}\n`);
|
|
44
|
+
}
|
|
45
|
+
if (shouldVerify) {
|
|
46
|
+
if (checks.length === 0)
|
|
47
|
+
io.stdout("\nNo checks configured. Add `checks` to loose-ends.config.json or pass --check.\n");
|
|
48
|
+
for (const run of verification.checks) {
|
|
49
|
+
io.stdout(`\n${run.exitCode === 0 ? "✔" : "✖"} ${run.command} (${(run.durationMs / 1000).toFixed(1)}s)\n`);
|
|
50
|
+
if (run.exitCode !== 0 && run.output)
|
|
51
|
+
io.stdout(run.output.replace(/^/gm, " ") + "\n");
|
|
52
|
+
}
|
|
53
|
+
if (!verification.passed) {
|
|
54
|
+
io.stderr("\nVerification failed. The working tree was not changed.\n");
|
|
55
|
+
setExit(1);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (checks.length)
|
|
59
|
+
io.stdout("\nVerification passed.\n");
|
|
60
|
+
}
|
|
61
|
+
if (flags.apply) {
|
|
62
|
+
if (checks.length === 0)
|
|
63
|
+
io.stderr("warning: applying without verification; no checks are configured\n");
|
|
64
|
+
const touched = applyCleanup(root, plan);
|
|
65
|
+
io.stdout(`\nApplied cleanup to ${touched.length} file${touched.length === 1 ? "" : "s"}. Review with \`git diff\`.\n`);
|
|
66
|
+
}
|
|
67
|
+
else if (!flags.patch) {
|
|
68
|
+
io.stdout("\nDry run. Use --patch <file> to save the diff, --verify to run checks, or --apply to write it.\n");
|
|
69
|
+
}
|
|
70
|
+
setExit(0);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=fix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fix.js","sourceRoot":"","sources":["../../src/commands/fix.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../src/commands/inspect.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAQ,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AAG5C,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAkElG"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { analyze, discoverPackages, detectFrameworks, explain, formatExplanation, label, loadConfig, CONFIG_FILES, } from "@loose-ends/core";
|
|
4
|
+
import { emit } from "../io.js";
|
|
5
|
+
import { UsageError } from "./check.js";
|
|
6
|
+
export function registerInspect(program, io, setExit) {
|
|
7
|
+
program
|
|
8
|
+
.command("explain")
|
|
9
|
+
.description("show why a file, symbol, or dependency is live, test-only, or orphaned")
|
|
10
|
+
.argument("<target>", "path, path#symbol, or package name")
|
|
11
|
+
.argument("[dir]", "project root", ".")
|
|
12
|
+
.option("-f, --format <format>", "text or json", "text")
|
|
13
|
+
.option("--color", "force colored output")
|
|
14
|
+
.option("--no-color", "disable colored output")
|
|
15
|
+
.action((target, dir, flags) => {
|
|
16
|
+
const analysis = analyze(loadConfig(resolve(io.cwd, dir)));
|
|
17
|
+
const explanation = explain(analysis, target);
|
|
18
|
+
if (!explanation) {
|
|
19
|
+
io.stderr(`loose-ends: "${target}" is not a known file, symbol, or dependency\n`);
|
|
20
|
+
setExit(2);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (flags.format === "json")
|
|
24
|
+
emit(io, JSON.stringify(explanation, null, 2) + "\n");
|
|
25
|
+
else if (flags.format === "text")
|
|
26
|
+
emit(io, formatExplanation(explanation, { color: flags.color ?? io.color }));
|
|
27
|
+
else
|
|
28
|
+
throw new UsageError(`unknown format "${flags.format}" (expected text or json)`);
|
|
29
|
+
setExit(0);
|
|
30
|
+
});
|
|
31
|
+
program
|
|
32
|
+
.command("roots")
|
|
33
|
+
.description("list the entry points Loose Ends discovered")
|
|
34
|
+
.argument("[dir]", "project root", ".")
|
|
35
|
+
.option("-f, --format <format>", "text or json", "text")
|
|
36
|
+
.option("-k, --kind <kind>", "only production, tooling, or test roots")
|
|
37
|
+
.option("--symbols", "include exported symbols that are roots, not just files")
|
|
38
|
+
.action((dir, flags) => {
|
|
39
|
+
if (flags.kind && !["production", "tooling", "test"].includes(flags.kind))
|
|
40
|
+
throw new UsageError(`unknown root kind "${flags.kind}"`);
|
|
41
|
+
const analysis = analyze(loadConfig(resolve(io.cwd, dir)));
|
|
42
|
+
const roots = analysis.roots
|
|
43
|
+
.filter((r) => !flags.kind || r.kind === flags.kind)
|
|
44
|
+
.filter((r) => flags.symbols || r.node.startsWith("file:"))
|
|
45
|
+
.map((r) => ({ target: label(analysis.graph.nodes.get(r.node), r.node), kind: r.kind, source: r.source, reason: r.reason }));
|
|
46
|
+
if (flags.format === "json") {
|
|
47
|
+
emit(io, JSON.stringify(roots, null, 2) + "\n");
|
|
48
|
+
}
|
|
49
|
+
else if (flags.format === "text") {
|
|
50
|
+
if (roots.length === 0)
|
|
51
|
+
io.stdout("No roots found. Add `entries` to loose-ends.config.json.\n");
|
|
52
|
+
const width = Math.min(60, Math.max(0, ...roots.map((r) => r.target.length)));
|
|
53
|
+
for (const r of roots)
|
|
54
|
+
io.stdout(`${r.target.padEnd(width)} ${r.kind.padEnd(10)} ${r.reason}\n`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
throw new UsageError(`unknown format "${flags.format}" (expected text or json)`);
|
|
58
|
+
}
|
|
59
|
+
setExit(0);
|
|
60
|
+
});
|
|
61
|
+
program
|
|
62
|
+
.command("init")
|
|
63
|
+
.description("write a starter loose-ends.config.json")
|
|
64
|
+
.argument("[dir]", "project root", ".")
|
|
65
|
+
.option("--force", "overwrite an existing config")
|
|
66
|
+
.action((dir, flags) => {
|
|
67
|
+
const root = resolve(io.cwd, dir);
|
|
68
|
+
const existing = CONFIG_FILES.map((f) => join(root, f)).find((f) => existsSync(f));
|
|
69
|
+
if (existing && !flags.force)
|
|
70
|
+
throw new UsageError(`${existing} already exists (use --force to overwrite)`);
|
|
71
|
+
const { frameworks, ...config } = starterConfig(root);
|
|
72
|
+
const path = join(root, "loose-ends.config.json");
|
|
73
|
+
writeFileSync(path, JSON.stringify(config, null, 2) + "\n");
|
|
74
|
+
io.stdout(`Wrote ${path}\n`);
|
|
75
|
+
io.stdout(`Detected frameworks: ${frameworks.join(", ") || "none (plain Node.js)"}\n`);
|
|
76
|
+
io.stdout("Next: run `loose-ends roots` to review entry points, then `loose-ends check` on a branch.\n");
|
|
77
|
+
setExit(0);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function starterConfig(root) {
|
|
81
|
+
const packages = discoverPackages(root);
|
|
82
|
+
const frameworks = new Set();
|
|
83
|
+
for (const pkg of packages)
|
|
84
|
+
for (const f of detectFrameworks(pkg))
|
|
85
|
+
if (f !== "node")
|
|
86
|
+
frameworks.add(f);
|
|
87
|
+
const scripts = packages.find((p) => p.dir === "")?.manifest.scripts ?? {};
|
|
88
|
+
const checks = ["typecheck", "build", "test"].filter((s) => s in scripts).map((s) => `npm run ${s}`);
|
|
89
|
+
const config = loadConfig(root);
|
|
90
|
+
return {
|
|
91
|
+
$schema: "https://raw.githubusercontent.com/sid732/loose-ends/main/schema/config.schema.json",
|
|
92
|
+
entries: [],
|
|
93
|
+
dormant: [],
|
|
94
|
+
suppressions: [],
|
|
95
|
+
checks,
|
|
96
|
+
failOn: config.failOn,
|
|
97
|
+
frameworks: [...frameworks].sort(),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=inspect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.js","sourceRoot":"","sources":["../../src/commands/inspect.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { type CliIO } from "./io.js";
|
|
3
|
+
export type { CliIO } from "./io.js";
|
|
4
|
+
/** Build the command tree. Exposed so tests can run commands without a process. */
|
|
5
|
+
export declare function createProgram(io: CliIO, setExit: (code: number) => void): Command;
|
|
6
|
+
/** Run the CLI and return its exit code: 0 pass, 1 blocking findings, 2 usage or runtime error. */
|
|
7
|
+
export declare function run(argv: string[], io?: CliIO): Promise<number>;
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAC;AAIpD,OAAO,EAAa,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhD,YAAY,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAWjF;AAED,mGAAmG;AACnG,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAclF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ConfigError, CoverageError, GitError, VERSION } from "@loose-ends/core";
|
|
2
|
+
import { Command, CommanderError } from "commander";
|
|
3
|
+
import { registerCheck, UsageError } from "./commands/check.js";
|
|
4
|
+
import { registerFix } from "./commands/fix.js";
|
|
5
|
+
import { registerInspect } from "./commands/inspect.js";
|
|
6
|
+
import { processIO } from "./io.js";
|
|
7
|
+
/** Build the command tree. Exposed so tests can run commands without a process. */
|
|
8
|
+
export function createProgram(io, setExit) {
|
|
9
|
+
const program = new Command("loose-ends")
|
|
10
|
+
.description("Graph-powered liveness checks for AI-written code. Every change should lead somewhere.")
|
|
11
|
+
.version(VERSION, "-v, --version")
|
|
12
|
+
.configureOutput({ writeOut: io.stdout, writeErr: io.stderr })
|
|
13
|
+
.exitOverride()
|
|
14
|
+
.showHelpAfterError();
|
|
15
|
+
registerCheck(program, io, setExit);
|
|
16
|
+
registerInspect(program, io, setExit);
|
|
17
|
+
registerFix(program, io, setExit);
|
|
18
|
+
return program;
|
|
19
|
+
}
|
|
20
|
+
/** Run the CLI and return its exit code: 0 pass, 1 blocking findings, 2 usage or runtime error. */
|
|
21
|
+
export async function run(argv, io = processIO()) {
|
|
22
|
+
let code = 0;
|
|
23
|
+
const program = createProgram(io, (c) => (code = c));
|
|
24
|
+
try {
|
|
25
|
+
await program.parseAsync(argv, { from: "user" });
|
|
26
|
+
return code;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error instanceof CommanderError)
|
|
30
|
+
return error.exitCode === 0 ? 0 : 2;
|
|
31
|
+
const known = error instanceof UsageError || error instanceof ConfigError || error instanceof GitError || error instanceof CoverageError;
|
|
32
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
+
io.stderr(`loose-ends: ${message}\n`);
|
|
34
|
+
if (!known && io.env.LOOSE_ENDS_DEBUG && error instanceof Error)
|
|
35
|
+
io.stderr(`${error.stack}\n`);
|
|
36
|
+
return 2;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAc,MAAM,SAAS,CAAC;AAIhD,mFAAmF;AACnF,MAAM,UAAU,aAAa,CAAC,EAAS,EAAE,OAA+B;IACtE,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,YAAY,CAAC;SACtC,WAAW,CAAC,wFAAwF,CAAC;SACrG,OAAO,CAAC,OAAO,EAAE,eAAe,CAAC;SACjC,eAAe,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC;SAC7D,YAAY,EAAE;SACd,kBAAkB,EAAE,CAAC;IACxB,aAAa,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IACpC,eAAe,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IACtC,WAAW,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IAClC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,mGAAmG;AACnG,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAAc,EAAE,KAAY,SAAS,EAAE;IAC/D,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,MAAM,OAAO,GAAG,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;IACrD,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,cAAc;YAAE,OAAO,KAAK,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzE,MAAM,KAAK,GAAG,KAAK,YAAY,UAAU,IAAI,KAAK,YAAY,WAAW,IAAI,KAAK,YAAY,QAAQ,IAAI,KAAK,YAAY,aAAa,CAAC;QACzI,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,EAAE,CAAC,MAAM,CAAC,eAAe,OAAO,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,GAAG,CAAC,gBAAgB,IAAI,KAAK,YAAY,KAAK;YAAE,EAAE,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;QAC/F,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC"}
|
package/dist/io.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CliIO {
|
|
2
|
+
cwd: string;
|
|
3
|
+
stdout: (text: string) => void;
|
|
4
|
+
stderr: (text: string) => void;
|
|
5
|
+
/** Whether stdout is a terminal that supports color. */
|
|
6
|
+
color: boolean;
|
|
7
|
+
env: Record<string, string | undefined>;
|
|
8
|
+
}
|
|
9
|
+
export declare function processIO(): CliIO;
|
|
10
|
+
/** Write to a file (creating directories) or to stdout when no path is given. */
|
|
11
|
+
export declare function emit(io: CliIO, text: string, output?: string): void;
|
|
12
|
+
//# sourceMappingURL=io.d.ts.map
|
package/dist/io.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.d.ts","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,wDAAwD;IACxD,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CACzC;AAED,wBAAgB,SAAS,IAAI,KAAK,CAQjC;AAED,iFAAiF;AACjF,wBAAgB,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAQnE"}
|
package/dist/io.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
export function processIO() {
|
|
4
|
+
return {
|
|
5
|
+
cwd: process.cwd(),
|
|
6
|
+
stdout: (t) => process.stdout.write(t),
|
|
7
|
+
stderr: (t) => process.stderr.write(t),
|
|
8
|
+
color: !!process.stdout.isTTY && !("NO_COLOR" in process.env) && process.env.TERM !== "dumb",
|
|
9
|
+
env: process.env,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** Write to a file (creating directories) or to stdout when no path is given. */
|
|
13
|
+
export function emit(io, text, output) {
|
|
14
|
+
if (!output || output === "-") {
|
|
15
|
+
io.stdout(text);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const abs = resolve(io.cwd, output);
|
|
19
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
20
|
+
writeFileSync(abs, text);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=io.js.map
|
package/dist/io.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.js","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAW7C,MAAM,UAAU,SAAS;IACvB,OAAO;QACL,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;QAClB,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACtC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACtC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM;QAC5F,GAAG,EAAE,OAAO,CAAC,GAAG;KACjB,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,IAAI,CAAC,EAAS,EAAE,IAAY,EAAE,MAAe;IAC3D,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QAC9B,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChB,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACpC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAC3B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loose-ends",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Graph-powered liveness checks for AI-written code.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"loose-ends": "dist/bin.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"dead-code",
|
|
24
|
+
"reachability",
|
|
25
|
+
"static-analysis",
|
|
26
|
+
"typescript",
|
|
27
|
+
"ai",
|
|
28
|
+
"code-review"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -b"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@loose-ends/core": "1.1.0",
|
|
35
|
+
"commander": "^14.0.0"
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/sid732/loose-ends.git",
|
|
40
|
+
"directory": "packages/cli"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
}
|
|
48
|
+
}
|