tend-cli 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.
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/configs/default.eslint.config.mjs +47 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +1754 -0
- package/dist/config-B5rO-fvz.js +1745 -0
- package/dist/index.d.ts +1274 -0
- package/dist/index.js +87 -0
- package/package.json +67 -0
- package/prompts/fix.md +29 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1274 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { SimpleGit } from "simple-git";
|
|
4
|
+
|
|
5
|
+
//#region src/findings/finding.d.ts
|
|
6
|
+
declare const TOOLS: readonly ["sonarjs", "knip", "jscpd", "semgrep", "osv", "gitleaks"];
|
|
7
|
+
declare const FindingSchema: z.ZodObject<{
|
|
8
|
+
id: z.ZodString;
|
|
9
|
+
retryId: z.ZodOptional<z.ZodString>;
|
|
10
|
+
tool: z.ZodEnum<["sonarjs", "knip", "jscpd", "semgrep", "osv", "gitleaks"]>;
|
|
11
|
+
rule: z.ZodString;
|
|
12
|
+
category: z.ZodEnum<["bug", "smell", "dead-code", "duplication", "security", "secret", "vuln-dep"]>;
|
|
13
|
+
severity: z.ZodEnum<["error", "warning", "info"]>;
|
|
14
|
+
file: z.ZodString;
|
|
15
|
+
range: z.ZodObject<{
|
|
16
|
+
startLine: z.ZodNumber;
|
|
17
|
+
startCol: z.ZodNumber;
|
|
18
|
+
endLine: z.ZodNumber;
|
|
19
|
+
endCol: z.ZodNumber;
|
|
20
|
+
}, "strip", z.ZodTypeAny, {
|
|
21
|
+
startLine: number;
|
|
22
|
+
startCol: number;
|
|
23
|
+
endLine: number;
|
|
24
|
+
endCol: number;
|
|
25
|
+
}, {
|
|
26
|
+
startLine: number;
|
|
27
|
+
startCol: number;
|
|
28
|
+
endLine: number;
|
|
29
|
+
endCol: number;
|
|
30
|
+
}>;
|
|
31
|
+
message: z.ZodString;
|
|
32
|
+
helpUri: z.ZodOptional<z.ZodString>;
|
|
33
|
+
flowPath: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
34
|
+
file: z.ZodString;
|
|
35
|
+
line: z.ZodNumber;
|
|
36
|
+
}, "strip", z.ZodTypeAny, {
|
|
37
|
+
file: string;
|
|
38
|
+
line: number;
|
|
39
|
+
}, {
|
|
40
|
+
file: string;
|
|
41
|
+
line: number;
|
|
42
|
+
}>, "many">>;
|
|
43
|
+
remediation: z.ZodOptional<z.ZodString>;
|
|
44
|
+
track: z.ZodEnum<["ai-fix", "deterministic", "report-only"]>;
|
|
45
|
+
status: z.ZodEnum<["pending", "fixing", "fixed", "reverted", "unfixable", "skipped"]>;
|
|
46
|
+
attempts: z.ZodNumber;
|
|
47
|
+
revertReason: z.ZodOptional<z.ZodEnum<["broke-test", "suppression", "regression", "typecheck", "session-error"]>>;
|
|
48
|
+
firstSeenLoop: z.ZodNumber;
|
|
49
|
+
lastSeenLoop: z.ZodNumber;
|
|
50
|
+
inScope: z.ZodOptional<z.ZodBoolean>;
|
|
51
|
+
}, "strip", z.ZodTypeAny, {
|
|
52
|
+
id: string;
|
|
53
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
54
|
+
status: "pending" | "fixing" | "fixed" | "reverted" | "unfixable" | "skipped";
|
|
55
|
+
message: string;
|
|
56
|
+
rule: string;
|
|
57
|
+
category: "bug" | "smell" | "dead-code" | "duplication" | "security" | "secret" | "vuln-dep";
|
|
58
|
+
severity: "error" | "warning" | "info";
|
|
59
|
+
file: string;
|
|
60
|
+
range: {
|
|
61
|
+
startLine: number;
|
|
62
|
+
startCol: number;
|
|
63
|
+
endLine: number;
|
|
64
|
+
endCol: number;
|
|
65
|
+
};
|
|
66
|
+
track: "ai-fix" | "deterministic" | "report-only";
|
|
67
|
+
attempts: number;
|
|
68
|
+
firstSeenLoop: number;
|
|
69
|
+
lastSeenLoop: number;
|
|
70
|
+
retryId?: string | undefined;
|
|
71
|
+
helpUri?: string | undefined;
|
|
72
|
+
flowPath?: {
|
|
73
|
+
file: string;
|
|
74
|
+
line: number;
|
|
75
|
+
}[] | undefined;
|
|
76
|
+
remediation?: string | undefined;
|
|
77
|
+
revertReason?: "broke-test" | "suppression" | "regression" | "typecheck" | "session-error" | undefined;
|
|
78
|
+
inScope?: boolean | undefined;
|
|
79
|
+
}, {
|
|
80
|
+
id: string;
|
|
81
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
82
|
+
status: "pending" | "fixing" | "fixed" | "reverted" | "unfixable" | "skipped";
|
|
83
|
+
message: string;
|
|
84
|
+
rule: string;
|
|
85
|
+
category: "bug" | "smell" | "dead-code" | "duplication" | "security" | "secret" | "vuln-dep";
|
|
86
|
+
severity: "error" | "warning" | "info";
|
|
87
|
+
file: string;
|
|
88
|
+
range: {
|
|
89
|
+
startLine: number;
|
|
90
|
+
startCol: number;
|
|
91
|
+
endLine: number;
|
|
92
|
+
endCol: number;
|
|
93
|
+
};
|
|
94
|
+
track: "ai-fix" | "deterministic" | "report-only";
|
|
95
|
+
attempts: number;
|
|
96
|
+
firstSeenLoop: number;
|
|
97
|
+
lastSeenLoop: number;
|
|
98
|
+
retryId?: string | undefined;
|
|
99
|
+
helpUri?: string | undefined;
|
|
100
|
+
flowPath?: {
|
|
101
|
+
file: string;
|
|
102
|
+
line: number;
|
|
103
|
+
}[] | undefined;
|
|
104
|
+
remediation?: string | undefined;
|
|
105
|
+
revertReason?: "broke-test" | "suppression" | "regression" | "typecheck" | "session-error" | undefined;
|
|
106
|
+
inScope?: boolean | undefined;
|
|
107
|
+
}>;
|
|
108
|
+
type Finding = z.infer<typeof FindingSchema>;
|
|
109
|
+
type Tool = (typeof TOOLS)[number];
|
|
110
|
+
type Track = Finding["track"];
|
|
111
|
+
/** The components that give a finding its stable identity. */
|
|
112
|
+
type FingerprintInput = {
|
|
113
|
+
tool: string;
|
|
114
|
+
rule: string;
|
|
115
|
+
file: string;
|
|
116
|
+
line: number;
|
|
117
|
+
message: string;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Stable identity for a finding: hash(tool | rule | file | line | message).
|
|
121
|
+
* Same components → same fingerprint, across loops and runs.
|
|
122
|
+
*/
|
|
123
|
+
declare function fingerprint(input: FingerprintInput): string; //#endregion
|
|
124
|
+
//#region src/findings/normalize.d.ts
|
|
125
|
+
/** A scanner-produced record, before tend assigns identity, track, and loop state. */
|
|
126
|
+
type RawFinding = {
|
|
127
|
+
tool: Tool;
|
|
128
|
+
rule: string;
|
|
129
|
+
category: Finding["category"];
|
|
130
|
+
severity: Finding["severity"];
|
|
131
|
+
file: string;
|
|
132
|
+
range: Finding["range"];
|
|
133
|
+
message: string;
|
|
134
|
+
helpUri?: string;
|
|
135
|
+
flowPath?: Finding["flowPath"];
|
|
136
|
+
remediation?: string;
|
|
137
|
+
};
|
|
138
|
+
/** Which track a tool's findings flow into. */
|
|
139
|
+
declare function trackForTool(tool: Tool): Track;
|
|
140
|
+
/** Turn a raw scanner record into a tracked `Finding` for the given loop. */
|
|
141
|
+
declare function normalize(raw: RawFinding, loop: number): Finding;
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/findings/store.d.ts
|
|
145
|
+
type RevertReason$1 = NonNullable<Finding["revertReason"]>;
|
|
146
|
+
/** Holds Finding records keyed by fingerprint and tracks their state across loops. */
|
|
147
|
+
declare class FindingStore {
|
|
148
|
+
private readonly findings;
|
|
149
|
+
add(finding: Finding): void;
|
|
150
|
+
get(id: string): Finding | undefined;
|
|
151
|
+
all(): Finding[];
|
|
152
|
+
/**
|
|
153
|
+
* Diff a fresh audit against what the store knows, by fingerprint:
|
|
154
|
+
* - known but absent now → marked `fixed`
|
|
155
|
+
* - present both loops → stays as-is, carries attempts/history, bumps lastSeenLoop
|
|
156
|
+
* - new fingerprint → added `pending`, firstSeenLoop = loop
|
|
157
|
+
*/
|
|
158
|
+
reconcile(fresh: Finding[], loop: number): void;
|
|
159
|
+
/** Findings matching every provided filter (track / status / file). */
|
|
160
|
+
query(filter: {
|
|
161
|
+
track?: Finding["track"];
|
|
162
|
+
status?: Finding["status"];
|
|
163
|
+
file?: string;
|
|
164
|
+
}): Finding[];
|
|
165
|
+
/** Record a failed fix attempt against a finding's fingerprint. */
|
|
166
|
+
recordFailedAttempt(id: string, reason: RevertReason$1): void;
|
|
167
|
+
/** A finding's per-issue budget is exhausted once it has used `budget` attempts. */
|
|
168
|
+
isBudgetExhausted(id: string, budget: number): boolean;
|
|
169
|
+
/** Serialize to a plain array — `report.json`'s findings section. */
|
|
170
|
+
toJSON(): Finding[];
|
|
171
|
+
/** Rebuild a store from serialized findings, validating each against the schema. */
|
|
172
|
+
static fromJSON(data: unknown): FindingStore;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/findings/router.d.ts
|
|
177
|
+
type RouteResult = {
|
|
178
|
+
aiFix: Finding[];
|
|
179
|
+
deterministic: Finding[];
|
|
180
|
+
reportOnly: Finding[];
|
|
181
|
+
skipped: Finding[];
|
|
182
|
+
};
|
|
183
|
+
/** Split findings into their assigned fix tracks; unknown tools are skipped with a warning. */
|
|
184
|
+
declare function route(findings: Finding[], opts?: {
|
|
185
|
+
warn?: (message: string) => void;
|
|
186
|
+
}): RouteResult;
|
|
187
|
+
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/scanners/scanner.d.ts
|
|
190
|
+
type ScanContext = {
|
|
191
|
+
cwd: string;
|
|
192
|
+
/** Files in scope (e.g. changed vs HEAD); a scanner may ignore this if it scans wide. */
|
|
193
|
+
files: string[];
|
|
194
|
+
loop: number;
|
|
195
|
+
};
|
|
196
|
+
type SpawnResult = {
|
|
197
|
+
stdout: string;
|
|
198
|
+
stderr: string;
|
|
199
|
+
exitCode: number;
|
|
200
|
+
};
|
|
201
|
+
/** Runs a binary, never throwing on non-zero exit (parse decides), but may throw on timeout/ENOENT. */
|
|
202
|
+
type Spawn = (binary: string, args: string[], opts: {
|
|
203
|
+
cwd: string;
|
|
204
|
+
timeout?: number;
|
|
205
|
+
}) => Promise<SpawnResult>;
|
|
206
|
+
/** Resolves whether a binary is on PATH. */
|
|
207
|
+
type Which = (binary: string) => Promise<boolean>;
|
|
208
|
+
interface Scanner {
|
|
209
|
+
readonly tool: Tool;
|
|
210
|
+
readonly binary: string;
|
|
211
|
+
buildArgs(ctx: ScanContext): string[];
|
|
212
|
+
/** Parse raw process output into findings. Throws on malformed output. */
|
|
213
|
+
parse(raw: SpawnResult, ctx: ScanContext): RawFinding[];
|
|
214
|
+
}
|
|
215
|
+
type ScanResult = {
|
|
216
|
+
tool: Tool;
|
|
217
|
+
findings: Finding[];
|
|
218
|
+
skipped: boolean;
|
|
219
|
+
error?: string;
|
|
220
|
+
};
|
|
221
|
+
/** Outcome of a scanner this loop, for the report's scanner-status line. */
|
|
222
|
+
type ScannerStatusKind = "ran" | "skipped" | "failed";
|
|
223
|
+
type ScannerStatus$1 = {
|
|
224
|
+
tool: Tool;
|
|
225
|
+
status: ScannerStatusKind;
|
|
226
|
+
reason?: string;
|
|
227
|
+
};
|
|
228
|
+
/** Collapse a ScanResult to its reportable status: skipped → ran → failed (error present). */
|
|
229
|
+
|
|
230
|
+
declare function isAvailable(scanner: Scanner, which: Which): Promise<boolean>;
|
|
231
|
+
/**
|
|
232
|
+
* Shared run sequence for every scanner:
|
|
233
|
+
* availability → args → spawn → parse → normalize.
|
|
234
|
+
* Missing binary → skipped (not fatal). Timeout/spawn error or malformed output → error result.
|
|
235
|
+
*/
|
|
236
|
+
declare function runScanner(scanner: Scanner, ctx: ScanContext, deps: {
|
|
237
|
+
which: Which;
|
|
238
|
+
spawn: Spawn;
|
|
239
|
+
timeout?: number;
|
|
240
|
+
}): Promise<ScanResult>;
|
|
241
|
+
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/scanners/scope.d.ts
|
|
244
|
+
/**
|
|
245
|
+
* Files changed vs `HEAD` (tracked modifications/additions/renames plus untracked),
|
|
246
|
+
* scoped and re-based to `git`'s working directory — see `changedVsHead` in git/repo.ts
|
|
247
|
+
* for why this matters when tend runs from a subdirectory of the repo.
|
|
248
|
+
*/
|
|
249
|
+
declare function changedFiles(git: SimpleGit): Promise<string[]>;
|
|
250
|
+
/** Keep only findings whose file is in the changed set. */
|
|
251
|
+
declare function filterToChanged(findings: Finding[], changed: string[]): Finding[];
|
|
252
|
+
/** Apply the fix scope: `--all` fixes everything, otherwise only changed files. */
|
|
253
|
+
declare function scopeFindings(findings: Finding[], opts: {
|
|
254
|
+
all: boolean;
|
|
255
|
+
changed: string[];
|
|
256
|
+
}): Finding[];
|
|
257
|
+
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/gate/check.d.ts
|
|
260
|
+
type RevertReason = NonNullable<Finding["revertReason"]>;
|
|
261
|
+
type CheckResult = {
|
|
262
|
+
ok: true;
|
|
263
|
+
} | {
|
|
264
|
+
ok: false;
|
|
265
|
+
reason: RevertReason;
|
|
266
|
+
detail: string;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/gate/gate.d.ts
|
|
271
|
+
type Check = {
|
|
272
|
+
name: string;
|
|
273
|
+
run: () => Promise<CheckResult> | CheckResult;
|
|
274
|
+
};
|
|
275
|
+
type GateOutcome = {
|
|
276
|
+
kept: true;
|
|
277
|
+
} | {
|
|
278
|
+
kept: false;
|
|
279
|
+
reason: RevertReason;
|
|
280
|
+
detail: string;
|
|
281
|
+
failedCheck: string;
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Run the verification checks in order, stopping at the first rejection and
|
|
285
|
+
* surfacing its revert reason. A fix is kept only if every check passes.
|
|
286
|
+
*/
|
|
287
|
+
declare function runGate(checks: Check[]): Promise<GateOutcome>;
|
|
288
|
+
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/fixing/change-set.d.ts
|
|
291
|
+
type FileEdit = {
|
|
292
|
+
path: string;
|
|
293
|
+
contents: string;
|
|
294
|
+
};
|
|
295
|
+
/**
|
|
296
|
+
* The atomic unit of a fix: edits to one file plus (optionally) its sibling test,
|
|
297
|
+
* applied and reverted together. Captures each file's prior state on apply so a
|
|
298
|
+
* revert — even after a partial apply — restores the working tree exactly.
|
|
299
|
+
*/
|
|
300
|
+
declare class ChangeSet {
|
|
301
|
+
private readonly edits;
|
|
302
|
+
/** path → original contents, or null if the file did not exist before. */
|
|
303
|
+
private readonly originals;
|
|
304
|
+
constructor(edits: FileEdit[]);
|
|
305
|
+
apply(): void;
|
|
306
|
+
revert(): void;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/fixing/dispatch.d.ts
|
|
311
|
+
type WorkUnit = {
|
|
312
|
+
/** The code file this unit owns (the group key). */
|
|
313
|
+
file: string;
|
|
314
|
+
/** Every file this worker reserves — the code file plus any sibling test. */
|
|
315
|
+
files: string[];
|
|
316
|
+
findings: Finding[];
|
|
317
|
+
};
|
|
318
|
+
/** Whether a repo-relative path is a test file (`*.test.*` / `*.spec.*`). */
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Group findings into work units so each worker owns a disjoint set of files
|
|
322
|
+
* (a code file plus its sibling test). No two sessions ever touch the same file.
|
|
323
|
+
*/
|
|
324
|
+
declare function planWork(findings: Finding[]): WorkUnit[];
|
|
325
|
+
/** Run each work unit through `runUnit`, capped at `concurrency` concurrent sessions. */
|
|
326
|
+
declare function dispatch<T>(units: WorkUnit[], runUnit: (unit: WorkUnit) => Promise<T>, opts: {
|
|
327
|
+
concurrency: number;
|
|
328
|
+
}): Promise<T[]>;
|
|
329
|
+
|
|
330
|
+
//#endregion
|
|
331
|
+
//#region src/session/types.d.ts
|
|
332
|
+
type SessionRequest = {
|
|
333
|
+
/** The file this session owns (plus its sibling test). */
|
|
334
|
+
file: string;
|
|
335
|
+
/** Findings to fix in this file. */
|
|
336
|
+
findings: Finding[];
|
|
337
|
+
/** The fully-rendered prompt for the AI. */
|
|
338
|
+
prompt: string;
|
|
339
|
+
};
|
|
340
|
+
/**
|
|
341
|
+
* Estimated AI cost/usage for a unit of work. `total_cost_usd` from Claude's
|
|
342
|
+
* stream-json `result` message is a **client-side estimate**, never authoritative
|
|
343
|
+
* billing — always surface it as "estimated AI cost".
|
|
344
|
+
*/
|
|
345
|
+
type AiUsage = {
|
|
346
|
+
/** Claude's `total_cost_usd` estimate (USD). A client-side estimate, not a bill. */
|
|
347
|
+
estimatedCostUsd: number;
|
|
348
|
+
inputTokens: number;
|
|
349
|
+
outputTokens: number;
|
|
350
|
+
cacheCreationInputTokens: number;
|
|
351
|
+
cacheReadInputTokens: number;
|
|
352
|
+
/** Number of Claude sessions (result messages) observed. */
|
|
353
|
+
sessions: number;
|
|
354
|
+
};
|
|
355
|
+
/** Cost/token portion of usage parsed from a single stream (sessions tracked separately). */
|
|
356
|
+
|
|
357
|
+
/** A usage record with everything zeroed. */
|
|
358
|
+
declare const zeroUsage: () => AiUsage;
|
|
359
|
+
/** Sum two usage records field-by-field (used to roll usage up through the run). */
|
|
360
|
+
declare function addUsage(a: AiUsage, b: AiUsage): AiUsage;
|
|
361
|
+
type SessionResult = {
|
|
362
|
+
ok: true;
|
|
363
|
+
edits: FileEdit[];
|
|
364
|
+
usage?: AiUsage;
|
|
365
|
+
} | {
|
|
366
|
+
ok: false;
|
|
367
|
+
error: string;
|
|
368
|
+
rateLimited: boolean;
|
|
369
|
+
usage?: AiUsage;
|
|
370
|
+
};
|
|
371
|
+
/** One of the two interfaces in tend: drives an AI fix session. */
|
|
372
|
+
interface SessionRunner {
|
|
373
|
+
run(request: SessionRequest): Promise<SessionResult>;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
//#endregion
|
|
377
|
+
//#region src/session/claude.d.ts
|
|
378
|
+
type ClaudeSpawn = (request: SessionRequest) => Promise<{
|
|
379
|
+
stdout: string;
|
|
380
|
+
exitCode: number;
|
|
381
|
+
}>;
|
|
382
|
+
/** Drives a real `claude -p` session and parses its stream-json into edits. */
|
|
383
|
+
declare class ClaudeSession implements SessionRunner {
|
|
384
|
+
private readonly deps;
|
|
385
|
+
constructor(deps: {
|
|
386
|
+
spawn: ClaudeSpawn;
|
|
387
|
+
});
|
|
388
|
+
run(request: SessionRequest): Promise<SessionResult>;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region src/git/snapshot.d.ts
|
|
393
|
+
/**
|
|
394
|
+
* A silent restore point for the working tree, stored as a git commit object pinned by a private
|
|
395
|
+
* ref (`refs/tend/snapshot`) — nothing committed to any branch, the editor sees no change. Backs
|
|
396
|
+
* `tend undo` (exact restore) and `tend diff` (only the tool's edits). Reuses git's content store,
|
|
397
|
+
* so the on-disk record is a 40-char id rather than a copy of every file.
|
|
398
|
+
*/
|
|
399
|
+
declare class Snapshot {
|
|
400
|
+
private readonly cwd;
|
|
401
|
+
private readonly root;
|
|
402
|
+
private readonly sha;
|
|
403
|
+
private constructor();
|
|
404
|
+
static capture(git: SimpleGit, cwd: string): Promise<Snapshot>;
|
|
405
|
+
/** Serialize to a tiny object for `.tend/snapshot.json` (powers `undo` across invocations). */
|
|
406
|
+
toJSON(): {
|
|
407
|
+
cwd: string;
|
|
408
|
+
root: string;
|
|
409
|
+
sha: string;
|
|
410
|
+
};
|
|
411
|
+
static fromJSON(data: {
|
|
412
|
+
cwd: string;
|
|
413
|
+
root: string;
|
|
414
|
+
sha: string;
|
|
415
|
+
}): Snapshot;
|
|
416
|
+
/** Files whose contents differ from the snapshot, or that are new/deleted since it (sorted). */
|
|
417
|
+
changedSince(_git: SimpleGit): Promise<string[]>;
|
|
418
|
+
/** Restore a single file to its captured contents (worktree only — the user's index is untouched). */
|
|
419
|
+
restoreFile(rel: string): Promise<void>;
|
|
420
|
+
/** Restore the working tree exactly to the captured state (incl. deleting files created since). */
|
|
421
|
+
restore(_git: SimpleGit): Promise<void>;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
//#endregion
|
|
425
|
+
//#region src/git/repo.d.ts
|
|
426
|
+
/** Refuse to run outside a git repo — the snapshot/restore safety net needs it. */
|
|
427
|
+
declare function assertGitRepo(git: SimpleGit): Promise<void>;
|
|
428
|
+
/**
|
|
429
|
+
* Files changed vs `HEAD`: tracked modifications/additions/renames plus untracked files,
|
|
430
|
+
* scoped and re-based to `git`'s working directory (so a run from `apps/foo` only sees
|
|
431
|
+
* `apps/foo`'s changes, pathed as the scanners path them).
|
|
432
|
+
*/
|
|
433
|
+
declare function changedVsHead(git: SimpleGit): Promise<string[]>;
|
|
434
|
+
/**
|
|
435
|
+
* Concrete files under the given path(s) — tracked plus untracked (so newly-added files
|
|
436
|
+
* are scoped too, mirroring `changedVsHead`). `git ls-files` reports paths relative to
|
|
437
|
+
* `git`'s working directory and interprets the pathspecs the same way, so the result is
|
|
438
|
+
* already in the coordinate system the scanners and `filterToChanged` expect. Expanding to
|
|
439
|
+
* concrete files (not bare directories) matters: `filterToChanged` matches exact paths.
|
|
440
|
+
*/
|
|
441
|
+
|
|
442
|
+
/** Revert a single file to its snapshot state. */
|
|
443
|
+
declare function revertFile(snapshot: Snapshot, file: string): Promise<void>;
|
|
444
|
+
|
|
445
|
+
//#endregion
|
|
446
|
+
//#region src/detect/package-manager.d.ts
|
|
447
|
+
type PackageManager = "pnpm" | "yarn" | "bun" | "npm";
|
|
448
|
+
/** Detect the package manager from the lockfile present; defaults to npm. */
|
|
449
|
+
declare function detectPackageManager(cwd: string): PackageManager;
|
|
450
|
+
|
|
451
|
+
//#endregion
|
|
452
|
+
//#region src/config/config.d.ts
|
|
453
|
+
declare const ConfigSchema: z.ZodObject<{
|
|
454
|
+
maxSessions: z.ZodDefault<z.ZodNumber>;
|
|
455
|
+
maxLoops: z.ZodDefault<z.ZodNumber>;
|
|
456
|
+
perIssueBudget: z.ZodDefault<z.ZodNumber>;
|
|
457
|
+
test: z.ZodOptional<z.ZodString>;
|
|
458
|
+
teethCheck: z.ZodDefault<z.ZodBoolean>;
|
|
459
|
+
includeTests: z.ZodDefault<z.ZodBoolean>;
|
|
460
|
+
/** Model passed to `claude -p` for fixes — an alias (sonnet/opus/haiku) or a full model id. */
|
|
461
|
+
model: z.ZodDefault<z.ZodString>;
|
|
462
|
+
/** Reasoning effort for fixes; unset → claude's own default. */
|
|
463
|
+
effort: z.ZodOptional<z.ZodEnum<["low", "medium", "high", "xhigh", "max"]>>;
|
|
464
|
+
tools: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
465
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
466
|
+
configPath: z.ZodOptional<z.ZodString>;
|
|
467
|
+
}, "strip", z.ZodTypeAny, {
|
|
468
|
+
enabled: boolean;
|
|
469
|
+
configPath?: string | undefined;
|
|
470
|
+
}, {
|
|
471
|
+
enabled?: boolean | undefined;
|
|
472
|
+
configPath?: string | undefined;
|
|
473
|
+
}>>>;
|
|
474
|
+
}, "strip", z.ZodTypeAny, {
|
|
475
|
+
maxSessions: number;
|
|
476
|
+
maxLoops: number;
|
|
477
|
+
perIssueBudget: number;
|
|
478
|
+
teethCheck: boolean;
|
|
479
|
+
includeTests: boolean;
|
|
480
|
+
model: string;
|
|
481
|
+
tools: Record<string, {
|
|
482
|
+
enabled: boolean;
|
|
483
|
+
configPath?: string | undefined;
|
|
484
|
+
}>;
|
|
485
|
+
test?: string | undefined;
|
|
486
|
+
effort?: "low" | "medium" | "high" | "xhigh" | "max" | undefined;
|
|
487
|
+
}, {
|
|
488
|
+
maxSessions?: number | undefined;
|
|
489
|
+
maxLoops?: number | undefined;
|
|
490
|
+
perIssueBudget?: number | undefined;
|
|
491
|
+
test?: string | undefined;
|
|
492
|
+
teethCheck?: boolean | undefined;
|
|
493
|
+
includeTests?: boolean | undefined;
|
|
494
|
+
model?: string | undefined;
|
|
495
|
+
effort?: "low" | "medium" | "high" | "xhigh" | "max" | undefined;
|
|
496
|
+
tools?: Record<string, {
|
|
497
|
+
enabled?: boolean | undefined;
|
|
498
|
+
configPath?: string | undefined;
|
|
499
|
+
}> | undefined;
|
|
500
|
+
}>;
|
|
501
|
+
type TendConfig = z.infer<typeof ConfigSchema>;
|
|
502
|
+
/** CLI flags that can override config; only defined keys take effect. */
|
|
503
|
+
type CliOverrides = Partial<Pick<TendConfig, "maxSessions" | "maxLoops" | "perIssueBudget" | "test" | "teethCheck" | "includeTests" | "model" | "effort">>;
|
|
504
|
+
/**
|
|
505
|
+
* Load config via cosmiconfig (searching from `cwd`), validate with zod, and apply
|
|
506
|
+
* zero-config defaults when no file is found. Invalid config throws a clear message.
|
|
507
|
+
*/
|
|
508
|
+
declare function loadConfig(cwd: string): Promise<TendConfig>;
|
|
509
|
+
/** Overlay CLI flags onto a loaded config (flags win). */
|
|
510
|
+
declare function applyCliOverrides(config: TendConfig, overrides: CliOverrides): TendConfig;
|
|
511
|
+
|
|
512
|
+
//#endregion
|
|
513
|
+
//#region src/report/retry-id.d.ts
|
|
514
|
+
type RetryIdGenerator = () => string;
|
|
515
|
+
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/report/schema.d.ts
|
|
518
|
+
/** Ensure every finding in a persisted report has a human-facing id unique to that report. */
|
|
519
|
+
/** Per-scanner outcome for a run: did it run clean, get skipped, or fail (with a reason). */
|
|
520
|
+
declare const ScannerStatusSchema: z.ZodObject<{
|
|
521
|
+
tool: z.ZodEnum<["sonarjs", "knip", "jscpd", "semgrep", "osv", "gitleaks"]>;
|
|
522
|
+
status: z.ZodEnum<["ran", "skipped", "failed"]>;
|
|
523
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
524
|
+
}, "strip", z.ZodTypeAny, {
|
|
525
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
526
|
+
status: "skipped" | "ran" | "failed";
|
|
527
|
+
reason?: string | undefined;
|
|
528
|
+
}, {
|
|
529
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
530
|
+
status: "skipped" | "ran" | "failed";
|
|
531
|
+
reason?: string | undefined;
|
|
532
|
+
}>;
|
|
533
|
+
declare const BehaviorChangeSchema: z.ZodObject<{
|
|
534
|
+
findingId: z.ZodString;
|
|
535
|
+
file: z.ZodString;
|
|
536
|
+
note: z.ZodString;
|
|
537
|
+
}, "strip", z.ZodTypeAny, {
|
|
538
|
+
file: string;
|
|
539
|
+
findingId: string;
|
|
540
|
+
note: string;
|
|
541
|
+
}, {
|
|
542
|
+
file: string;
|
|
543
|
+
findingId: string;
|
|
544
|
+
note: string;
|
|
545
|
+
}>;
|
|
546
|
+
/**
|
|
547
|
+
* Estimated AI cost/usage for a run. `estimatedCostUsd` is Claude's client-side
|
|
548
|
+
* `total_cost_usd` estimate — never authoritative billing.
|
|
549
|
+
*/
|
|
550
|
+
declare const AiUsageSchema: z.ZodObject<{
|
|
551
|
+
estimatedCostUsd: z.ZodNumber;
|
|
552
|
+
inputTokens: z.ZodNumber;
|
|
553
|
+
outputTokens: z.ZodNumber;
|
|
554
|
+
cacheCreationInputTokens: z.ZodNumber;
|
|
555
|
+
cacheReadInputTokens: z.ZodNumber;
|
|
556
|
+
sessions: z.ZodNumber;
|
|
557
|
+
}, "strip", z.ZodTypeAny, {
|
|
558
|
+
estimatedCostUsd: number;
|
|
559
|
+
inputTokens: number;
|
|
560
|
+
outputTokens: number;
|
|
561
|
+
cacheCreationInputTokens: number;
|
|
562
|
+
cacheReadInputTokens: number;
|
|
563
|
+
sessions: number;
|
|
564
|
+
}, {
|
|
565
|
+
estimatedCostUsd: number;
|
|
566
|
+
inputTokens: number;
|
|
567
|
+
outputTokens: number;
|
|
568
|
+
cacheCreationInputTokens: number;
|
|
569
|
+
cacheReadInputTokens: number;
|
|
570
|
+
sessions: number;
|
|
571
|
+
}>;
|
|
572
|
+
declare const ReportSchema: z.ZodObject<{
|
|
573
|
+
findings: z.ZodArray<z.ZodObject<{
|
|
574
|
+
id: z.ZodString;
|
|
575
|
+
retryId: z.ZodOptional<z.ZodString>;
|
|
576
|
+
tool: z.ZodEnum<["sonarjs", "knip", "jscpd", "semgrep", "osv", "gitleaks"]>;
|
|
577
|
+
rule: z.ZodString;
|
|
578
|
+
category: z.ZodEnum<["bug", "smell", "dead-code", "duplication", "security", "secret", "vuln-dep"]>;
|
|
579
|
+
severity: z.ZodEnum<["error", "warning", "info"]>;
|
|
580
|
+
file: z.ZodString;
|
|
581
|
+
range: z.ZodObject<{
|
|
582
|
+
startLine: z.ZodNumber;
|
|
583
|
+
startCol: z.ZodNumber;
|
|
584
|
+
endLine: z.ZodNumber;
|
|
585
|
+
endCol: z.ZodNumber;
|
|
586
|
+
}, "strip", z.ZodTypeAny, {
|
|
587
|
+
startLine: number;
|
|
588
|
+
startCol: number;
|
|
589
|
+
endLine: number;
|
|
590
|
+
endCol: number;
|
|
591
|
+
}, {
|
|
592
|
+
startLine: number;
|
|
593
|
+
startCol: number;
|
|
594
|
+
endLine: number;
|
|
595
|
+
endCol: number;
|
|
596
|
+
}>;
|
|
597
|
+
message: z.ZodString;
|
|
598
|
+
helpUri: z.ZodOptional<z.ZodString>;
|
|
599
|
+
flowPath: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
600
|
+
file: z.ZodString;
|
|
601
|
+
line: z.ZodNumber;
|
|
602
|
+
}, "strip", z.ZodTypeAny, {
|
|
603
|
+
file: string;
|
|
604
|
+
line: number;
|
|
605
|
+
}, {
|
|
606
|
+
file: string;
|
|
607
|
+
line: number;
|
|
608
|
+
}>, "many">>;
|
|
609
|
+
remediation: z.ZodOptional<z.ZodString>;
|
|
610
|
+
track: z.ZodEnum<["ai-fix", "deterministic", "report-only"]>;
|
|
611
|
+
status: z.ZodEnum<["pending", "fixing", "fixed", "reverted", "unfixable", "skipped"]>;
|
|
612
|
+
attempts: z.ZodNumber;
|
|
613
|
+
revertReason: z.ZodOptional<z.ZodEnum<["broke-test", "suppression", "regression", "typecheck", "session-error"]>>;
|
|
614
|
+
firstSeenLoop: z.ZodNumber;
|
|
615
|
+
lastSeenLoop: z.ZodNumber;
|
|
616
|
+
inScope: z.ZodOptional<z.ZodBoolean>;
|
|
617
|
+
}, "strip", z.ZodTypeAny, {
|
|
618
|
+
id: string;
|
|
619
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
620
|
+
status: "pending" | "fixing" | "fixed" | "reverted" | "unfixable" | "skipped";
|
|
621
|
+
message: string;
|
|
622
|
+
rule: string;
|
|
623
|
+
category: "bug" | "smell" | "dead-code" | "duplication" | "security" | "secret" | "vuln-dep";
|
|
624
|
+
severity: "error" | "warning" | "info";
|
|
625
|
+
file: string;
|
|
626
|
+
range: {
|
|
627
|
+
startLine: number;
|
|
628
|
+
startCol: number;
|
|
629
|
+
endLine: number;
|
|
630
|
+
endCol: number;
|
|
631
|
+
};
|
|
632
|
+
track: "ai-fix" | "deterministic" | "report-only";
|
|
633
|
+
attempts: number;
|
|
634
|
+
firstSeenLoop: number;
|
|
635
|
+
lastSeenLoop: number;
|
|
636
|
+
retryId?: string | undefined;
|
|
637
|
+
helpUri?: string | undefined;
|
|
638
|
+
flowPath?: {
|
|
639
|
+
file: string;
|
|
640
|
+
line: number;
|
|
641
|
+
}[] | undefined;
|
|
642
|
+
remediation?: string | undefined;
|
|
643
|
+
revertReason?: "broke-test" | "suppression" | "regression" | "typecheck" | "session-error" | undefined;
|
|
644
|
+
inScope?: boolean | undefined;
|
|
645
|
+
}, {
|
|
646
|
+
id: string;
|
|
647
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
648
|
+
status: "pending" | "fixing" | "fixed" | "reverted" | "unfixable" | "skipped";
|
|
649
|
+
message: string;
|
|
650
|
+
rule: string;
|
|
651
|
+
category: "bug" | "smell" | "dead-code" | "duplication" | "security" | "secret" | "vuln-dep";
|
|
652
|
+
severity: "error" | "warning" | "info";
|
|
653
|
+
file: string;
|
|
654
|
+
range: {
|
|
655
|
+
startLine: number;
|
|
656
|
+
startCol: number;
|
|
657
|
+
endLine: number;
|
|
658
|
+
endCol: number;
|
|
659
|
+
};
|
|
660
|
+
track: "ai-fix" | "deterministic" | "report-only";
|
|
661
|
+
attempts: number;
|
|
662
|
+
firstSeenLoop: number;
|
|
663
|
+
lastSeenLoop: number;
|
|
664
|
+
retryId?: string | undefined;
|
|
665
|
+
helpUri?: string | undefined;
|
|
666
|
+
flowPath?: {
|
|
667
|
+
file: string;
|
|
668
|
+
line: number;
|
|
669
|
+
}[] | undefined;
|
|
670
|
+
remediation?: string | undefined;
|
|
671
|
+
revertReason?: "broke-test" | "suppression" | "regression" | "typecheck" | "session-error" | undefined;
|
|
672
|
+
inScope?: boolean | undefined;
|
|
673
|
+
}>, "many">;
|
|
674
|
+
secrets: z.ZodArray<z.ZodObject<{
|
|
675
|
+
id: z.ZodString;
|
|
676
|
+
retryId: z.ZodOptional<z.ZodString>;
|
|
677
|
+
tool: z.ZodEnum<["sonarjs", "knip", "jscpd", "semgrep", "osv", "gitleaks"]>;
|
|
678
|
+
rule: z.ZodString;
|
|
679
|
+
category: z.ZodEnum<["bug", "smell", "dead-code", "duplication", "security", "secret", "vuln-dep"]>;
|
|
680
|
+
severity: z.ZodEnum<["error", "warning", "info"]>;
|
|
681
|
+
file: z.ZodString;
|
|
682
|
+
range: z.ZodObject<{
|
|
683
|
+
startLine: z.ZodNumber;
|
|
684
|
+
startCol: z.ZodNumber;
|
|
685
|
+
endLine: z.ZodNumber;
|
|
686
|
+
endCol: z.ZodNumber;
|
|
687
|
+
}, "strip", z.ZodTypeAny, {
|
|
688
|
+
startLine: number;
|
|
689
|
+
startCol: number;
|
|
690
|
+
endLine: number;
|
|
691
|
+
endCol: number;
|
|
692
|
+
}, {
|
|
693
|
+
startLine: number;
|
|
694
|
+
startCol: number;
|
|
695
|
+
endLine: number;
|
|
696
|
+
endCol: number;
|
|
697
|
+
}>;
|
|
698
|
+
message: z.ZodString;
|
|
699
|
+
helpUri: z.ZodOptional<z.ZodString>;
|
|
700
|
+
flowPath: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
701
|
+
file: z.ZodString;
|
|
702
|
+
line: z.ZodNumber;
|
|
703
|
+
}, "strip", z.ZodTypeAny, {
|
|
704
|
+
file: string;
|
|
705
|
+
line: number;
|
|
706
|
+
}, {
|
|
707
|
+
file: string;
|
|
708
|
+
line: number;
|
|
709
|
+
}>, "many">>;
|
|
710
|
+
remediation: z.ZodOptional<z.ZodString>;
|
|
711
|
+
track: z.ZodEnum<["ai-fix", "deterministic", "report-only"]>;
|
|
712
|
+
status: z.ZodEnum<["pending", "fixing", "fixed", "reverted", "unfixable", "skipped"]>;
|
|
713
|
+
attempts: z.ZodNumber;
|
|
714
|
+
revertReason: z.ZodOptional<z.ZodEnum<["broke-test", "suppression", "regression", "typecheck", "session-error"]>>;
|
|
715
|
+
firstSeenLoop: z.ZodNumber;
|
|
716
|
+
lastSeenLoop: z.ZodNumber;
|
|
717
|
+
inScope: z.ZodOptional<z.ZodBoolean>;
|
|
718
|
+
}, "strip", z.ZodTypeAny, {
|
|
719
|
+
id: string;
|
|
720
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
721
|
+
status: "pending" | "fixing" | "fixed" | "reverted" | "unfixable" | "skipped";
|
|
722
|
+
message: string;
|
|
723
|
+
rule: string;
|
|
724
|
+
category: "bug" | "smell" | "dead-code" | "duplication" | "security" | "secret" | "vuln-dep";
|
|
725
|
+
severity: "error" | "warning" | "info";
|
|
726
|
+
file: string;
|
|
727
|
+
range: {
|
|
728
|
+
startLine: number;
|
|
729
|
+
startCol: number;
|
|
730
|
+
endLine: number;
|
|
731
|
+
endCol: number;
|
|
732
|
+
};
|
|
733
|
+
track: "ai-fix" | "deterministic" | "report-only";
|
|
734
|
+
attempts: number;
|
|
735
|
+
firstSeenLoop: number;
|
|
736
|
+
lastSeenLoop: number;
|
|
737
|
+
retryId?: string | undefined;
|
|
738
|
+
helpUri?: string | undefined;
|
|
739
|
+
flowPath?: {
|
|
740
|
+
file: string;
|
|
741
|
+
line: number;
|
|
742
|
+
}[] | undefined;
|
|
743
|
+
remediation?: string | undefined;
|
|
744
|
+
revertReason?: "broke-test" | "suppression" | "regression" | "typecheck" | "session-error" | undefined;
|
|
745
|
+
inScope?: boolean | undefined;
|
|
746
|
+
}, {
|
|
747
|
+
id: string;
|
|
748
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
749
|
+
status: "pending" | "fixing" | "fixed" | "reverted" | "unfixable" | "skipped";
|
|
750
|
+
message: string;
|
|
751
|
+
rule: string;
|
|
752
|
+
category: "bug" | "smell" | "dead-code" | "duplication" | "security" | "secret" | "vuln-dep";
|
|
753
|
+
severity: "error" | "warning" | "info";
|
|
754
|
+
file: string;
|
|
755
|
+
range: {
|
|
756
|
+
startLine: number;
|
|
757
|
+
startCol: number;
|
|
758
|
+
endLine: number;
|
|
759
|
+
endCol: number;
|
|
760
|
+
};
|
|
761
|
+
track: "ai-fix" | "deterministic" | "report-only";
|
|
762
|
+
attempts: number;
|
|
763
|
+
firstSeenLoop: number;
|
|
764
|
+
lastSeenLoop: number;
|
|
765
|
+
retryId?: string | undefined;
|
|
766
|
+
helpUri?: string | undefined;
|
|
767
|
+
flowPath?: {
|
|
768
|
+
file: string;
|
|
769
|
+
line: number;
|
|
770
|
+
}[] | undefined;
|
|
771
|
+
remediation?: string | undefined;
|
|
772
|
+
revertReason?: "broke-test" | "suppression" | "regression" | "typecheck" | "session-error" | undefined;
|
|
773
|
+
inScope?: boolean | undefined;
|
|
774
|
+
}>, "many">;
|
|
775
|
+
depBumps: z.ZodArray<z.ZodObject<{
|
|
776
|
+
findingId: z.ZodString;
|
|
777
|
+
remediation: z.ZodString;
|
|
778
|
+
}, "strip", z.ZodTypeAny, {
|
|
779
|
+
remediation: string;
|
|
780
|
+
findingId: string;
|
|
781
|
+
}, {
|
|
782
|
+
remediation: string;
|
|
783
|
+
findingId: string;
|
|
784
|
+
}>, "many">;
|
|
785
|
+
flaggedBehaviorChanges: z.ZodArray<z.ZodObject<{
|
|
786
|
+
findingId: z.ZodString;
|
|
787
|
+
file: z.ZodString;
|
|
788
|
+
note: z.ZodString;
|
|
789
|
+
}, "strip", z.ZodTypeAny, {
|
|
790
|
+
file: string;
|
|
791
|
+
findingId: string;
|
|
792
|
+
note: string;
|
|
793
|
+
}, {
|
|
794
|
+
file: string;
|
|
795
|
+
findingId: string;
|
|
796
|
+
note: string;
|
|
797
|
+
}>, "many">;
|
|
798
|
+
scannerStatuses: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
799
|
+
tool: z.ZodEnum<["sonarjs", "knip", "jscpd", "semgrep", "osv", "gitleaks"]>;
|
|
800
|
+
status: z.ZodEnum<["ran", "skipped", "failed"]>;
|
|
801
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
802
|
+
}, "strip", z.ZodTypeAny, {
|
|
803
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
804
|
+
status: "skipped" | "ran" | "failed";
|
|
805
|
+
reason?: string | undefined;
|
|
806
|
+
}, {
|
|
807
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
808
|
+
status: "skipped" | "ran" | "failed";
|
|
809
|
+
reason?: string | undefined;
|
|
810
|
+
}>, "many">>;
|
|
811
|
+
aiUsage: z.ZodDefault<z.ZodObject<{
|
|
812
|
+
estimatedCostUsd: z.ZodNumber;
|
|
813
|
+
inputTokens: z.ZodNumber;
|
|
814
|
+
outputTokens: z.ZodNumber;
|
|
815
|
+
cacheCreationInputTokens: z.ZodNumber;
|
|
816
|
+
cacheReadInputTokens: z.ZodNumber;
|
|
817
|
+
sessions: z.ZodNumber;
|
|
818
|
+
}, "strip", z.ZodTypeAny, {
|
|
819
|
+
estimatedCostUsd: number;
|
|
820
|
+
inputTokens: number;
|
|
821
|
+
outputTokens: number;
|
|
822
|
+
cacheCreationInputTokens: number;
|
|
823
|
+
cacheReadInputTokens: number;
|
|
824
|
+
sessions: number;
|
|
825
|
+
}, {
|
|
826
|
+
estimatedCostUsd: number;
|
|
827
|
+
inputTokens: number;
|
|
828
|
+
outputTokens: number;
|
|
829
|
+
cacheCreationInputTokens: number;
|
|
830
|
+
cacheReadInputTokens: number;
|
|
831
|
+
sessions: number;
|
|
832
|
+
}>>;
|
|
833
|
+
loops: z.ZodNumber;
|
|
834
|
+
durationMs: z.ZodNumber;
|
|
835
|
+
exitStatus: z.ZodNumber;
|
|
836
|
+
}, "strip", z.ZodTypeAny, {
|
|
837
|
+
findings: {
|
|
838
|
+
id: string;
|
|
839
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
840
|
+
status: "pending" | "fixing" | "fixed" | "reverted" | "unfixable" | "skipped";
|
|
841
|
+
message: string;
|
|
842
|
+
rule: string;
|
|
843
|
+
category: "bug" | "smell" | "dead-code" | "duplication" | "security" | "secret" | "vuln-dep";
|
|
844
|
+
severity: "error" | "warning" | "info";
|
|
845
|
+
file: string;
|
|
846
|
+
range: {
|
|
847
|
+
startLine: number;
|
|
848
|
+
startCol: number;
|
|
849
|
+
endLine: number;
|
|
850
|
+
endCol: number;
|
|
851
|
+
};
|
|
852
|
+
track: "ai-fix" | "deterministic" | "report-only";
|
|
853
|
+
attempts: number;
|
|
854
|
+
firstSeenLoop: number;
|
|
855
|
+
lastSeenLoop: number;
|
|
856
|
+
retryId?: string | undefined;
|
|
857
|
+
helpUri?: string | undefined;
|
|
858
|
+
flowPath?: {
|
|
859
|
+
file: string;
|
|
860
|
+
line: number;
|
|
861
|
+
}[] | undefined;
|
|
862
|
+
remediation?: string | undefined;
|
|
863
|
+
revertReason?: "broke-test" | "suppression" | "regression" | "typecheck" | "session-error" | undefined;
|
|
864
|
+
inScope?: boolean | undefined;
|
|
865
|
+
}[];
|
|
866
|
+
secrets: {
|
|
867
|
+
id: string;
|
|
868
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
869
|
+
status: "pending" | "fixing" | "fixed" | "reverted" | "unfixable" | "skipped";
|
|
870
|
+
message: string;
|
|
871
|
+
rule: string;
|
|
872
|
+
category: "bug" | "smell" | "dead-code" | "duplication" | "security" | "secret" | "vuln-dep";
|
|
873
|
+
severity: "error" | "warning" | "info";
|
|
874
|
+
file: string;
|
|
875
|
+
range: {
|
|
876
|
+
startLine: number;
|
|
877
|
+
startCol: number;
|
|
878
|
+
endLine: number;
|
|
879
|
+
endCol: number;
|
|
880
|
+
};
|
|
881
|
+
track: "ai-fix" | "deterministic" | "report-only";
|
|
882
|
+
attempts: number;
|
|
883
|
+
firstSeenLoop: number;
|
|
884
|
+
lastSeenLoop: number;
|
|
885
|
+
retryId?: string | undefined;
|
|
886
|
+
helpUri?: string | undefined;
|
|
887
|
+
flowPath?: {
|
|
888
|
+
file: string;
|
|
889
|
+
line: number;
|
|
890
|
+
}[] | undefined;
|
|
891
|
+
remediation?: string | undefined;
|
|
892
|
+
revertReason?: "broke-test" | "suppression" | "regression" | "typecheck" | "session-error" | undefined;
|
|
893
|
+
inScope?: boolean | undefined;
|
|
894
|
+
}[];
|
|
895
|
+
depBumps: {
|
|
896
|
+
remediation: string;
|
|
897
|
+
findingId: string;
|
|
898
|
+
}[];
|
|
899
|
+
flaggedBehaviorChanges: {
|
|
900
|
+
file: string;
|
|
901
|
+
findingId: string;
|
|
902
|
+
note: string;
|
|
903
|
+
}[];
|
|
904
|
+
scannerStatuses: {
|
|
905
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
906
|
+
status: "skipped" | "ran" | "failed";
|
|
907
|
+
reason?: string | undefined;
|
|
908
|
+
}[];
|
|
909
|
+
aiUsage: {
|
|
910
|
+
estimatedCostUsd: number;
|
|
911
|
+
inputTokens: number;
|
|
912
|
+
outputTokens: number;
|
|
913
|
+
cacheCreationInputTokens: number;
|
|
914
|
+
cacheReadInputTokens: number;
|
|
915
|
+
sessions: number;
|
|
916
|
+
};
|
|
917
|
+
loops: number;
|
|
918
|
+
durationMs: number;
|
|
919
|
+
exitStatus: number;
|
|
920
|
+
}, {
|
|
921
|
+
findings: {
|
|
922
|
+
id: string;
|
|
923
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
924
|
+
status: "pending" | "fixing" | "fixed" | "reverted" | "unfixable" | "skipped";
|
|
925
|
+
message: string;
|
|
926
|
+
rule: string;
|
|
927
|
+
category: "bug" | "smell" | "dead-code" | "duplication" | "security" | "secret" | "vuln-dep";
|
|
928
|
+
severity: "error" | "warning" | "info";
|
|
929
|
+
file: string;
|
|
930
|
+
range: {
|
|
931
|
+
startLine: number;
|
|
932
|
+
startCol: number;
|
|
933
|
+
endLine: number;
|
|
934
|
+
endCol: number;
|
|
935
|
+
};
|
|
936
|
+
track: "ai-fix" | "deterministic" | "report-only";
|
|
937
|
+
attempts: number;
|
|
938
|
+
firstSeenLoop: number;
|
|
939
|
+
lastSeenLoop: number;
|
|
940
|
+
retryId?: string | undefined;
|
|
941
|
+
helpUri?: string | undefined;
|
|
942
|
+
flowPath?: {
|
|
943
|
+
file: string;
|
|
944
|
+
line: number;
|
|
945
|
+
}[] | undefined;
|
|
946
|
+
remediation?: string | undefined;
|
|
947
|
+
revertReason?: "broke-test" | "suppression" | "regression" | "typecheck" | "session-error" | undefined;
|
|
948
|
+
inScope?: boolean | undefined;
|
|
949
|
+
}[];
|
|
950
|
+
secrets: {
|
|
951
|
+
id: string;
|
|
952
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
953
|
+
status: "pending" | "fixing" | "fixed" | "reverted" | "unfixable" | "skipped";
|
|
954
|
+
message: string;
|
|
955
|
+
rule: string;
|
|
956
|
+
category: "bug" | "smell" | "dead-code" | "duplication" | "security" | "secret" | "vuln-dep";
|
|
957
|
+
severity: "error" | "warning" | "info";
|
|
958
|
+
file: string;
|
|
959
|
+
range: {
|
|
960
|
+
startLine: number;
|
|
961
|
+
startCol: number;
|
|
962
|
+
endLine: number;
|
|
963
|
+
endCol: number;
|
|
964
|
+
};
|
|
965
|
+
track: "ai-fix" | "deterministic" | "report-only";
|
|
966
|
+
attempts: number;
|
|
967
|
+
firstSeenLoop: number;
|
|
968
|
+
lastSeenLoop: number;
|
|
969
|
+
retryId?: string | undefined;
|
|
970
|
+
helpUri?: string | undefined;
|
|
971
|
+
flowPath?: {
|
|
972
|
+
file: string;
|
|
973
|
+
line: number;
|
|
974
|
+
}[] | undefined;
|
|
975
|
+
remediation?: string | undefined;
|
|
976
|
+
revertReason?: "broke-test" | "suppression" | "regression" | "typecheck" | "session-error" | undefined;
|
|
977
|
+
inScope?: boolean | undefined;
|
|
978
|
+
}[];
|
|
979
|
+
depBumps: {
|
|
980
|
+
remediation: string;
|
|
981
|
+
findingId: string;
|
|
982
|
+
}[];
|
|
983
|
+
flaggedBehaviorChanges: {
|
|
984
|
+
file: string;
|
|
985
|
+
findingId: string;
|
|
986
|
+
note: string;
|
|
987
|
+
}[];
|
|
988
|
+
loops: number;
|
|
989
|
+
durationMs: number;
|
|
990
|
+
exitStatus: number;
|
|
991
|
+
scannerStatuses?: {
|
|
992
|
+
tool: "sonarjs" | "knip" | "jscpd" | "semgrep" | "osv" | "gitleaks";
|
|
993
|
+
status: "skipped" | "ran" | "failed";
|
|
994
|
+
reason?: string | undefined;
|
|
995
|
+
}[] | undefined;
|
|
996
|
+
aiUsage?: {
|
|
997
|
+
estimatedCostUsd: number;
|
|
998
|
+
inputTokens: number;
|
|
999
|
+
outputTokens: number;
|
|
1000
|
+
cacheCreationInputTokens: number;
|
|
1001
|
+
cacheReadInputTokens: number;
|
|
1002
|
+
sessions: number;
|
|
1003
|
+
} | undefined;
|
|
1004
|
+
}>;
|
|
1005
|
+
type Report = z.infer<typeof ReportSchema>;
|
|
1006
|
+
type BehaviorChange = z.infer<typeof BehaviorChangeSchema>;
|
|
1007
|
+
type ScannerStatus = z.infer<typeof ScannerStatusSchema>;
|
|
1008
|
+
type AiUsage$1 = z.infer<typeof AiUsageSchema>;
|
|
1009
|
+
|
|
1010
|
+
//#endregion
|
|
1011
|
+
//#region src/report/builder.d.ts
|
|
1012
|
+
/** Accumulates per-finding outcomes and run metadata into a validated report.json. */
|
|
1013
|
+
declare class ReportBuilder {
|
|
1014
|
+
private readonly generateRetryId?;
|
|
1015
|
+
private readonly outcomes;
|
|
1016
|
+
private readonly flagged;
|
|
1017
|
+
private scannerStatuses;
|
|
1018
|
+
constructor(generateRetryId?: RetryIdGenerator | undefined);
|
|
1019
|
+
/** Record (or update) a finding's final outcome by fingerprint. */
|
|
1020
|
+
recordOutcome(finding: Finding): void;
|
|
1021
|
+
recordOutcomes(findings: Finding[]): void;
|
|
1022
|
+
/** Flag a semantic test change for human review. */
|
|
1023
|
+
flagBehaviorChange(entry: BehaviorChange): void;
|
|
1024
|
+
/** Record per-scanner run outcomes (ran / skipped / failed) for the scanner-status line. */
|
|
1025
|
+
recordScannerStatuses(statuses: ScannerStatus[]): void;
|
|
1026
|
+
build(meta: {
|
|
1027
|
+
loops: number;
|
|
1028
|
+
durationMs: number;
|
|
1029
|
+
exitStatus: number;
|
|
1030
|
+
aiUsage?: AiUsage$1;
|
|
1031
|
+
}): Report;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
//#endregion
|
|
1035
|
+
//#region src/output/events.d.ts
|
|
1036
|
+
/** What happened to a file's fix attempt. "left" = in scope but never dispatched. */
|
|
1037
|
+
type FileOutcome = "fixed" | "reverted" | "left";
|
|
1038
|
+
type TendEvent = {
|
|
1039
|
+
type: "snapshot";
|
|
1040
|
+
} | {
|
|
1041
|
+
type: "detected";
|
|
1042
|
+
packageManager: string;
|
|
1043
|
+
typescript: boolean;
|
|
1044
|
+
testRunner?: string;
|
|
1045
|
+
} | {
|
|
1046
|
+
type: "scan-start";
|
|
1047
|
+
loop: number;
|
|
1048
|
+
} | {
|
|
1049
|
+
type: "audit";
|
|
1050
|
+
loop: number;
|
|
1051
|
+
findings: number;
|
|
1052
|
+
files: number;
|
|
1053
|
+
scanned?: number;
|
|
1054
|
+
} | {
|
|
1055
|
+
type: "loop-start";
|
|
1056
|
+
loop: number;
|
|
1057
|
+
files: string[];
|
|
1058
|
+
concurrency: number;
|
|
1059
|
+
} | {
|
|
1060
|
+
type: "file-start";
|
|
1061
|
+
loop: number;
|
|
1062
|
+
file: string;
|
|
1063
|
+
rule?: string;
|
|
1064
|
+
} | {
|
|
1065
|
+
type: "file-result";
|
|
1066
|
+
loop: number;
|
|
1067
|
+
file: string;
|
|
1068
|
+
outcome: FileOutcome;
|
|
1069
|
+
reason?: string;
|
|
1070
|
+
} | {
|
|
1071
|
+
type: "loop-complete";
|
|
1072
|
+
loop: number;
|
|
1073
|
+
fixed: number;
|
|
1074
|
+
} | {
|
|
1075
|
+
type: "done";
|
|
1076
|
+
exitStatus: number;
|
|
1077
|
+
};
|
|
1078
|
+
type Listener = (event: TendEvent) => void;
|
|
1079
|
+
/** Minimal synchronous event bus. With no listener, emit is a no-op (silent mode). */
|
|
1080
|
+
declare class EventBus {
|
|
1081
|
+
private readonly listeners;
|
|
1082
|
+
on(listener: Listener): () => void;
|
|
1083
|
+
emit(event: TendEvent): void;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
//#endregion
|
|
1087
|
+
//#region src/output/theme.d.ts
|
|
1088
|
+
/**
|
|
1089
|
+
* The visual language. Restraint is the point: one accent color used for ~2 things (the
|
|
1090
|
+
* wordmark + the active spinner), muted semantic colors for outcomes, everything else
|
|
1091
|
+
* achromatic. Hierarchy comes from indentation + dim metadata, not from color.
|
|
1092
|
+
*/
|
|
1093
|
+
type Theme = {
|
|
1094
|
+
/** The single accent — wordmark + active spinner only. */
|
|
1095
|
+
accent: Style;
|
|
1096
|
+
/** Soft green — a fix that landed. */
|
|
1097
|
+
fixed: Style;
|
|
1098
|
+
/** Soft amber — a fix that was reverted. */
|
|
1099
|
+
reverted: Style;
|
|
1100
|
+
/** Soft red — an error / something that needs the human. */
|
|
1101
|
+
error: Style;
|
|
1102
|
+
/** Dim grey — metadata, queued, rules, hints. */
|
|
1103
|
+
dim: Style;
|
|
1104
|
+
bold: Style;
|
|
1105
|
+
plain: Style;
|
|
1106
|
+
/** The "t e n d" wordmark: a subtle gradient when truecolor is available, else flat. */
|
|
1107
|
+
wordmark: () => string;
|
|
1108
|
+
glyph: Glyphs;
|
|
1109
|
+
};
|
|
1110
|
+
type Style = (s: string) => string;
|
|
1111
|
+
type Glyphs = {
|
|
1112
|
+
fixed: string;
|
|
1113
|
+
reverted: string;
|
|
1114
|
+
left: string;
|
|
1115
|
+
scanned: string;
|
|
1116
|
+
bullet: string;
|
|
1117
|
+
rule: string;
|
|
1118
|
+
arrow: string;
|
|
1119
|
+
spinner: string[];
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
//#endregion
|
|
1123
|
+
//#region src/output/summary.d.ts
|
|
1124
|
+
type SummaryOptions = {
|
|
1125
|
+
theme?: Theme;
|
|
1126
|
+
verbose?: boolean;
|
|
1127
|
+
plain?: boolean;
|
|
1128
|
+
};
|
|
1129
|
+
/**
|
|
1130
|
+
* The final summary: a real headline (fixed / couldn't-fix / left / secrets + elapsed),
|
|
1131
|
+
* grouped by what the user must do, with revert reasons surfaced per file, and ending in
|
|
1132
|
+
* next-step affordances. Brief by default; `--verbose` adds the full per-finding listing.
|
|
1133
|
+
*/
|
|
1134
|
+
declare function renderSummary(report: Report, opts?: SummaryOptions): string;
|
|
1135
|
+
type RemainingKey = "secrets" | "security" | "couldnt-fix" | "review";
|
|
1136
|
+
type RemainingGroup = {
|
|
1137
|
+
key: RemainingKey;
|
|
1138
|
+
title: string;
|
|
1139
|
+
count: number;
|
|
1140
|
+
};
|
|
1141
|
+
/**
|
|
1142
|
+
* Group the issues that still need a human, ordered by urgency:
|
|
1143
|
+
* secrets → security → couldn't-fix → needs-review. Empty groups are omitted.
|
|
1144
|
+
*/
|
|
1145
|
+
declare function groupRemaining(report: Report): RemainingGroup[];
|
|
1146
|
+
|
|
1147
|
+
//#endregion
|
|
1148
|
+
//#region src/orchestrator.d.ts
|
|
1149
|
+
type AuditResult = {
|
|
1150
|
+
findings: Finding[];
|
|
1151
|
+
allScannersMissing?: boolean;
|
|
1152
|
+
scanned?: number;
|
|
1153
|
+
scannerStatuses?: ScannerStatus$1[];
|
|
1154
|
+
};
|
|
1155
|
+
type FixOutcome = {
|
|
1156
|
+
kept: boolean;
|
|
1157
|
+
reason?: RevertReason;
|
|
1158
|
+
usage?: AiUsage;
|
|
1159
|
+
};
|
|
1160
|
+
type OrchestrateDeps = {
|
|
1161
|
+
/** Run the scanners for a loop and return normalized findings. */
|
|
1162
|
+
audit: (loop: number) => Promise<AuditResult>;
|
|
1163
|
+
/** Fix one work unit (session + gate); returns whether the fix was kept. */
|
|
1164
|
+
fixUnit: (unit: WorkUnit, loop: number) => Promise<FixOutcome>;
|
|
1165
|
+
config: {
|
|
1166
|
+
maxLoops: number;
|
|
1167
|
+
perIssueBudget: number;
|
|
1168
|
+
maxSessions: number;
|
|
1169
|
+
includeTests?: boolean;
|
|
1170
|
+
};
|
|
1171
|
+
/** Restrict findings to the fix scope (changed files); defaults to all. */
|
|
1172
|
+
inScope?: (findings: Finding[]) => Finding[];
|
|
1173
|
+
bus?: EventBus;
|
|
1174
|
+
};
|
|
1175
|
+
type Termination = "converged" | "max-loops" | "no-progress" | "no-scanners";
|
|
1176
|
+
type OrchestrateResult = {
|
|
1177
|
+
termination: Termination;
|
|
1178
|
+
loops: number;
|
|
1179
|
+
exitStatus: number;
|
|
1180
|
+
findings: Finding[];
|
|
1181
|
+
secrets: Finding[];
|
|
1182
|
+
depBumps: Finding[];
|
|
1183
|
+
scannerStatuses: ScannerStatus$1[];
|
|
1184
|
+
/** Estimated AI cost/usage summed across every fix attempt (including reverted ones). */
|
|
1185
|
+
usage: AiUsage;
|
|
1186
|
+
};
|
|
1187
|
+
/**
|
|
1188
|
+
* The scan → fix → re-audit loop. Terminates on the first of: converged (0 fixable),
|
|
1189
|
+
* no-progress (no dispatchable units or an attempted loop changed no attempt/status
|
|
1190
|
+
* state), per-issue budget exhaustion (mark unfixable, keep going), or max-loops.
|
|
1191
|
+
*/
|
|
1192
|
+
declare function orchestrate(deps: OrchestrateDeps): Promise<OrchestrateResult>;
|
|
1193
|
+
|
|
1194
|
+
//#endregion
|
|
1195
|
+
//#region src/cli.d.ts
|
|
1196
|
+
type CliHandlers = {
|
|
1197
|
+
run: (opts: {
|
|
1198
|
+
paths?: string[];
|
|
1199
|
+
all?: boolean;
|
|
1200
|
+
maxLoops?: number;
|
|
1201
|
+
maxSessions?: number;
|
|
1202
|
+
model?: string;
|
|
1203
|
+
effort?: string;
|
|
1204
|
+
includeTests?: boolean;
|
|
1205
|
+
plain?: boolean;
|
|
1206
|
+
color?: boolean;
|
|
1207
|
+
verbose?: boolean;
|
|
1208
|
+
}) => Promise<void> | void;
|
|
1209
|
+
diff: () => Promise<void> | void;
|
|
1210
|
+
undo: () => Promise<void> | void;
|
|
1211
|
+
show: (id: string) => Promise<void> | void;
|
|
1212
|
+
retry: (id: string) => Promise<void> | void;
|
|
1213
|
+
};
|
|
1214
|
+
/** Build the commander program wiring each subcommand to a handler. */
|
|
1215
|
+
declare function buildProgram(handlers: CliHandlers): Command;
|
|
1216
|
+
|
|
1217
|
+
//#endregion
|
|
1218
|
+
//#region src/commands/run.d.ts
|
|
1219
|
+
type RunDeps = OrchestrateDeps & {
|
|
1220
|
+
now?: () => number;
|
|
1221
|
+
};
|
|
1222
|
+
/** `tend run` — wire audit → fix loop → report. */
|
|
1223
|
+
declare function runCommand(deps: RunDeps): Promise<{
|
|
1224
|
+
report: Report;
|
|
1225
|
+
exitStatus: number;
|
|
1226
|
+
}>;
|
|
1227
|
+
|
|
1228
|
+
//#endregion
|
|
1229
|
+
//#region src/commands/diff.d.ts
|
|
1230
|
+
/** `tend diff` — the files the tool edited (snapshot vs now), the dev's own changes filtered out. */
|
|
1231
|
+
declare function diffCommand(deps: {
|
|
1232
|
+
snapshot: Snapshot;
|
|
1233
|
+
git: SimpleGit;
|
|
1234
|
+
}): Promise<string[]>;
|
|
1235
|
+
|
|
1236
|
+
//#endregion
|
|
1237
|
+
//#region src/commands/undo.d.ts
|
|
1238
|
+
/** `tend undo` — restore the pre-run snapshot exactly. */
|
|
1239
|
+
declare function undoCommand(deps: {
|
|
1240
|
+
snapshot: Snapshot;
|
|
1241
|
+
git: SimpleGit;
|
|
1242
|
+
}): Promise<void>;
|
|
1243
|
+
|
|
1244
|
+
//#endregion
|
|
1245
|
+
//#region src/commands/show.d.ts
|
|
1246
|
+
/** `tend show <id>` — full detail on one finding: attempts, revert reason, taint flow path. */
|
|
1247
|
+
declare function showCommand(id: string, findings: Finding[]): string;
|
|
1248
|
+
|
|
1249
|
+
//#endregion
|
|
1250
|
+
//#region src/commands/retry.d.ts
|
|
1251
|
+
type RetryDeps = {
|
|
1252
|
+
report?: Report;
|
|
1253
|
+
findings?: Finding[];
|
|
1254
|
+
baseBudget: number;
|
|
1255
|
+
/** Re-run the fix for a finding with the given (larger) attempt budget. */
|
|
1256
|
+
runFix: (finding: Finding, budget: number) => Promise<FixOutcome>;
|
|
1257
|
+
};
|
|
1258
|
+
type RetryResult = {
|
|
1259
|
+
outcome: "fixed";
|
|
1260
|
+
finding: Finding;
|
|
1261
|
+
budget: number;
|
|
1262
|
+
} | {
|
|
1263
|
+
outcome: "reverted";
|
|
1264
|
+
finding: Finding;
|
|
1265
|
+
budget: number;
|
|
1266
|
+
reason: RevertReason;
|
|
1267
|
+
} | {
|
|
1268
|
+
error: string;
|
|
1269
|
+
};
|
|
1270
|
+
/** `tend retry <id>` — re-attempt a stubborn finding with a larger attempt budget. */
|
|
1271
|
+
declare function retryCommand(id: string, deps: RetryDeps): Promise<RetryResult>;
|
|
1272
|
+
|
|
1273
|
+
//#endregion
|
|
1274
|
+
export { AiUsage, AuditResult, ChangeSet, Check, ClaudeSession, CliHandlers, ConfigSchema, EventBus, FileEdit, Finding, FindingSchema, FindingStore, FixOutcome, GateOutcome, OrchestrateDeps, OrchestrateResult, PackageManager, RawFinding, Report, ReportBuilder, ReportSchema, RetryDeps, RetryResult, RouteResult, RunDeps, ScanContext, ScanResult, Scanner, SessionRequest, SessionResult, SessionRunner, Snapshot, Spawn, TendConfig, TendEvent, Termination, Tool, Track, Which, WorkUnit, addUsage, applyCliOverrides, assertGitRepo, buildProgram, changedFiles, changedVsHead, detectPackageManager, diffCommand, dispatch, filterToChanged, fingerprint, groupRemaining, isAvailable, loadConfig, normalize, orchestrate, planWork, renderSummary, retryCommand, revertFile, route, runCommand, runGate, runScanner, scopeFindings, showCommand, trackForTool, undoCommand, zeroUsage };
|