tend-cli 0.3.0 → 0.4.1
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 +6 -5
- package/dist/bin.js +2 -3
- package/dist/{config-DMOjMbMD.js → config-pmGLB0x1.js} +63 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,8 +25,8 @@ Run the latest published package directly from the registry:
|
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
npx tend-cli@latest # changed files vs HEAD (the default)
|
|
28
|
-
npx tend-cli@latest
|
|
29
|
-
npx tend-cli@latest
|
|
28
|
+
npx tend-cli@latest src/scanners # only findings under this path
|
|
29
|
+
npx tend-cli@latest --all # the entire backlog, repo-wide
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
Or install it and use the product command:
|
|
@@ -34,8 +34,9 @@ Or install it and use the product command:
|
|
|
34
34
|
```bash
|
|
35
35
|
npm install -g tend-cli
|
|
36
36
|
tend # changed files vs HEAD (the default)
|
|
37
|
-
tend
|
|
38
|
-
tend
|
|
37
|
+
tend src/scanners
|
|
38
|
+
tend --all
|
|
39
|
+
tend run src/scanners # explicit form is also available
|
|
39
40
|
```
|
|
40
41
|
|
|
41
42
|
Requires **Node ≥ 20**, a git repo, and the [Claude Code](https://www.anthropic.com/claude-code)
|
|
@@ -47,7 +48,7 @@ do not need to match: `tend` is the command users run, and `tend-cli` is the reg
|
|
|
47
48
|
When developing inside this repo, use the local script instead of `npx tend-cli`:
|
|
48
49
|
|
|
49
50
|
```bash
|
|
50
|
-
pnpm cli
|
|
51
|
+
pnpm cli -- src/scanners
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
## What it does
|
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { ClaudeSession, EFFORT_LEVELS, EventBus, ReportBuilder, ReportSchema, Snapshot, addUsage, applyCliOverrides, assertGitRepo, buildProgram, changedVsHead, createGit, detectPackageManager, filesUnder, filterToChanged, formatClock, loadConfig, makeTheme, normalize, orchestrate, planWork, reasonLabel, renderSummary, resolveRetryTarget, retryCommand, runScanner, scannerStatus, showCommand, zeroUsage } from "./config-
|
|
2
|
+
import { ClaudeSession, EFFORT_LEVELS, EventBus, ReportBuilder, ReportSchema, Snapshot, addUsage, applyCliOverrides, assertGitRepo, buildProgram, changedVsHead, createGit, detectPackageManager, filesUnder, filterToChanged, formatClock, loadConfig, makeTheme, normalize, orchestrate, planWork, reasonLabel, renderSummary, resolveRetryTarget, retryCommand, runScanner, scannerStatus, showCommand, zeroUsage } from "./config-pmGLB0x1.js";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { execa } from "execa";
|
|
@@ -1763,8 +1763,7 @@ const program = buildProgram({
|
|
|
1763
1763
|
},
|
|
1764
1764
|
retry: (id) => runRetry(id)
|
|
1765
1765
|
});
|
|
1766
|
-
|
|
1767
|
-
program.parseAsync(argv).catch((e) => {
|
|
1766
|
+
program.parseAsync(process.argv).catch((e) => {
|
|
1768
1767
|
if (e instanceof Error && e.name === "CommanderError") {
|
|
1769
1768
|
process.exitCode = e.exitCode ?? 1;
|
|
1770
1769
|
return;
|
|
@@ -13,12 +13,65 @@ import gradient from "gradient-string";
|
|
|
13
13
|
import { cosmiconfig } from "cosmiconfig";
|
|
14
14
|
|
|
15
15
|
//#region src/cli.ts
|
|
16
|
+
const COMMAND_NAMES = new Set([
|
|
17
|
+
"diff",
|
|
18
|
+
"help",
|
|
19
|
+
"retry",
|
|
20
|
+
"run",
|
|
21
|
+
"show",
|
|
22
|
+
"undo"
|
|
23
|
+
]);
|
|
24
|
+
const RUN_OPTION_NAMES = new Set([
|
|
25
|
+
"--all",
|
|
26
|
+
"--effort",
|
|
27
|
+
"--include-tests",
|
|
28
|
+
"--max-loops",
|
|
29
|
+
"--max-sessions",
|
|
30
|
+
"--model",
|
|
31
|
+
"--no-color",
|
|
32
|
+
"--plain",
|
|
33
|
+
"--verbose"
|
|
34
|
+
]);
|
|
35
|
+
function addRunOptions(command) {
|
|
36
|
+
return command.option("--all", "fix the entire backlog, not just changed files").option("--max-loops <n>", "cap on fix loops", (v) => parseInt(v, 10)).option("--max-sessions <n>", "concurrent AI sessions", (v) => parseInt(v, 10)).option("--model <model>", "model for fixes: sonnet (default), opus, haiku, or a full model id").option("--effort <level>", "reasoning effort for fixes: low | medium | high | xhigh | max").option("--include-tests", "also fix findings in test files (excluded by default)").option("--plain", "plain one-line-per-event output for pipes/CI (no color, no spinners)").option("--no-color", "disable color output").option("--verbose", "show the full per-tool / per-finding breakdown in the summary");
|
|
37
|
+
}
|
|
38
|
+
function looksLikePath(value) {
|
|
39
|
+
return value.includes("/") || value.includes("\\") || value.startsWith(".") || existsSync(value);
|
|
40
|
+
}
|
|
41
|
+
function isRunOption(value) {
|
|
42
|
+
const name = value.split("=")[0] ?? value;
|
|
43
|
+
return RUN_OPTION_NAMES.has(name);
|
|
44
|
+
}
|
|
45
|
+
function shouldInsertRun(args) {
|
|
46
|
+
const first = args[0];
|
|
47
|
+
if (!first) return true;
|
|
48
|
+
if (first === "--help" || first === "-h" || COMMAND_NAMES.has(first)) return false;
|
|
49
|
+
return isRunOption(first) || looksLikePath(first);
|
|
50
|
+
}
|
|
51
|
+
function withDefaultRun(argv, from) {
|
|
52
|
+
const prefixLength = from === "user" ? 0 : 2;
|
|
53
|
+
const prefix = argv.slice(0, prefixLength);
|
|
54
|
+
const args = argv.slice(prefixLength);
|
|
55
|
+
return shouldInsertRun(args) ? [
|
|
56
|
+
...prefix,
|
|
57
|
+
"run",
|
|
58
|
+
...args
|
|
59
|
+
] : [...argv];
|
|
60
|
+
}
|
|
61
|
+
function enableDefaultRun(program) {
|
|
62
|
+
const parse = program.parse.bind(program);
|
|
63
|
+
const parseAsync = program.parseAsync.bind(program);
|
|
64
|
+
program.parse = (argv, options) => parse(withDefaultRun(argv ?? process.argv, options?.from), options);
|
|
65
|
+
program.parseAsync = (argv, options) => parseAsync(withDefaultRun(argv ?? process.argv, options?.from), options);
|
|
66
|
+
}
|
|
16
67
|
/** Build the commander program wiring each subcommand to a handler. */
|
|
17
68
|
function buildProgram(handlers) {
|
|
18
69
|
const program = new Command();
|
|
19
70
|
program.name("tend").description("Audit a JS/TS repo and fix findings with AI in a safe loop.");
|
|
20
71
|
program.exitOverride();
|
|
21
|
-
program.
|
|
72
|
+
program.addHelpCommand(true);
|
|
73
|
+
const run = program.command("run").description("snapshot → audit → fix loop → report (changed files)").argument("[paths...]", "fix only findings under these files/dirs (committed or not)");
|
|
74
|
+
addRunOptions(run).action((paths, opts) => handlers.run({
|
|
22
75
|
...opts,
|
|
23
76
|
paths
|
|
24
77
|
}));
|
|
@@ -26,6 +79,7 @@ function buildProgram(handlers) {
|
|
|
26
79
|
program.command("undo").description("restore the pre-run snapshot").action(() => handlers.undo());
|
|
27
80
|
program.command("show <id>").description("full detail on one finding").action((id) => handlers.show(id));
|
|
28
81
|
program.command("retry <id>").description("re-attempt a stubborn finding with a larger budget").action((id) => handlers.retry(id));
|
|
82
|
+
enableDefaultRun(program);
|
|
29
83
|
return program;
|
|
30
84
|
}
|
|
31
85
|
|
|
@@ -328,9 +382,11 @@ function revertFile(snapshot, file) {
|
|
|
328
382
|
//#endregion
|
|
329
383
|
//#region src/git/client.ts
|
|
330
384
|
const UNSAFE_GIT_ENV_KEYS = [
|
|
385
|
+
"EDITOR",
|
|
386
|
+
"VISUAL",
|
|
331
387
|
"GIT_EDITOR",
|
|
332
|
-
"GIT_PAGER",
|
|
333
388
|
"GIT_SEQUENCE_EDITOR",
|
|
389
|
+
"GIT_PAGER",
|
|
334
390
|
"PAGER"
|
|
335
391
|
];
|
|
336
392
|
function gitEnv(extra = {}) {
|
|
@@ -341,8 +397,8 @@ function gitEnv(extra = {}) {
|
|
|
341
397
|
for (const key of UNSAFE_GIT_ENV_KEYS) delete env[key];
|
|
342
398
|
return env;
|
|
343
399
|
}
|
|
344
|
-
function createGit(root) {
|
|
345
|
-
return simpleGit(root).env(gitEnv());
|
|
400
|
+
function createGit(root, extraEnv = {}) {
|
|
401
|
+
return simpleGit(root).env(gitEnv(extraEnv));
|
|
346
402
|
}
|
|
347
403
|
|
|
348
404
|
//#endregion
|
|
@@ -360,7 +416,7 @@ let indexCounter = 0;
|
|
|
360
416
|
async function writeWorkingTree(root) {
|
|
361
417
|
const idxPath = join(tmpdir(), `tend-index-${process.pid}-${indexCounter++}`);
|
|
362
418
|
try {
|
|
363
|
-
const g = createGit(root
|
|
419
|
+
const g = createGit(root, { GIT_INDEX_FILE: idxPath });
|
|
364
420
|
await g.raw(["add", "-A"]);
|
|
365
421
|
return (await g.raw(["write-tree"])).trim();
|
|
366
422
|
} finally {
|
|
@@ -404,7 +460,8 @@ var Snapshot = class Snapshot {
|
|
|
404
460
|
this.root = root;
|
|
405
461
|
this.sha = sha;
|
|
406
462
|
}
|
|
407
|
-
static async capture(
|
|
463
|
+
static async capture(_git, cwd) {
|
|
464
|
+
const git = createGit(cwd);
|
|
408
465
|
const root = (await git.revparse(["--show-toplevel"])).trim();
|
|
409
466
|
const gitDir = (await git.revparse(["--absolute-git-dir"])).trim();
|
|
410
467
|
ensureTendIgnored(gitDir);
|
package/dist/index.d.ts
CHANGED
|
@@ -404,7 +404,7 @@ declare class Snapshot {
|
|
|
404
404
|
private readonly root;
|
|
405
405
|
private readonly sha;
|
|
406
406
|
private constructor();
|
|
407
|
-
static capture(
|
|
407
|
+
static capture(_git: SimpleGit, cwd: string): Promise<Snapshot>;
|
|
408
408
|
/** Serialize to a tiny object for `.tend/snapshot.json` (powers `undo` across invocations). */
|
|
409
409
|
toJSON(): {
|
|
410
410
|
cwd: string;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ClaudeSession, ConfigSchema, EventBus, FindingSchema, FindingStore, ReportBuilder, ReportSchema, Snapshot, addUsage, applyCliOverrides, assertGitRepo, buildProgram, changedFiles, changedVsHead, detectPackageManager, dispatch, filterToChanged, fingerprint, groupRemaining, isAvailable, loadConfig, normalize, orchestrate, planWork, renderSummary, retryCommand, revertFile, route, runScanner, scopeFindings, showCommand, trackForTool, zeroUsage } from "./config-
|
|
1
|
+
import { ClaudeSession, ConfigSchema, EventBus, FindingSchema, FindingStore, ReportBuilder, ReportSchema, Snapshot, addUsage, applyCliOverrides, assertGitRepo, buildProgram, changedFiles, changedVsHead, detectPackageManager, dispatch, filterToChanged, fingerprint, groupRemaining, isAvailable, loadConfig, normalize, orchestrate, planWork, renderSummary, retryCommand, revertFile, route, runScanner, scopeFindings, showCommand, trackForTool, zeroUsage } from "./config-pmGLB0x1.js";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tend-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Audit a JS/TS repo with established scanners, then fix the findings with parallel AI sessions in a safe scan-fix-rescan loop.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lint",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
],
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsdown",
|
|
42
|
-
"cli": "
|
|
42
|
+
"cli": "node scripts/cli-dev.mjs",
|
|
43
43
|
"prepublishOnly": "tsdown",
|
|
44
44
|
"test": "vitest run",
|
|
45
45
|
"test:watch": "vitest",
|