@credda/cli 0.1.5 → 1.0.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/CHANGELOG.md +71 -0
- package/README.md +223 -274
- package/dist/args.d.ts +99 -0
- package/dist/args.js +222 -0
- package/dist/commands.d.ts +153 -0
- package/dist/commands.js +1192 -0
- package/dist/index.d.ts +41 -4
- package/dist/index.js +41 -55
- package/package.json +25 -12
- package/dist/cli.d.ts +0 -108
- package/dist/cli.js +0 -1303
- package/dist/listener.d.ts +0 -17
- package/dist/listener.js +0 -73
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small argument parser for the `credda` command surface.
|
|
3
|
+
*
|
|
4
|
+
* Hand-rolled deliberately: a dependency here would be a supply-chain risk
|
|
5
|
+
* taken on for string parsing. The surface has outgrown the "six commands and a
|
|
6
|
+
* dozen flags" this line used to claim -- it is 13 commands, 3 aliases and 27
|
|
7
|
+
* flags -- and the argument holds better at that size, not worse.
|
|
8
|
+
* The parser is spec-driven so `--help` is generated from the same data the
|
|
9
|
+
* parser validates against and cannot drift from it.
|
|
10
|
+
*/
|
|
11
|
+
export type FlagKind = 'boolean' | 'string' | 'number';
|
|
12
|
+
export interface FlagSpec {
|
|
13
|
+
readonly kind: FlagKind;
|
|
14
|
+
readonly description: string;
|
|
15
|
+
/** Single-character alias, without the leading dash. */
|
|
16
|
+
readonly alias?: string;
|
|
17
|
+
/** Permitted values for a string flag. */
|
|
18
|
+
readonly choices?: readonly string[];
|
|
19
|
+
/** Placeholder shown in help, e.g. `<n>`. */
|
|
20
|
+
readonly valueName?: string;
|
|
21
|
+
/** Documented default, shown in help only. Never applied by the parser. */
|
|
22
|
+
readonly defaultNote?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface CommandSpec {
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly summary: string;
|
|
27
|
+
/**
|
|
28
|
+
* The argument portion of the usage line, without `credda ` or the command
|
|
29
|
+
* name. Both are composed from `name`, so an alias renders its own usage
|
|
30
|
+
* rather than the canonical command's.
|
|
31
|
+
*/
|
|
32
|
+
readonly args: string;
|
|
33
|
+
readonly flags: Readonly<Record<string, FlagSpec>>;
|
|
34
|
+
/** Longer explanation appended to `credda <command> --help`. */
|
|
35
|
+
readonly details?: readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* The command this one is a spelling of. Parsing and help are unchanged; only
|
|
38
|
+
* dispatch resolves through it, so an alias cannot drift from its target.
|
|
39
|
+
*/
|
|
40
|
+
readonly aliasOf?: string;
|
|
41
|
+
}
|
|
42
|
+
/** A user error: bad flag, missing value, unknown command. Never a bug. */
|
|
43
|
+
export declare class UsageError extends Error {
|
|
44
|
+
/** The command whose usage should be printed, if one was identified. */
|
|
45
|
+
readonly command: string | null;
|
|
46
|
+
constructor(message: string,
|
|
47
|
+
/** The command whose usage should be printed, if one was identified. */
|
|
48
|
+
command?: string | null);
|
|
49
|
+
}
|
|
50
|
+
export interface GlobalFlags {
|
|
51
|
+
readonly help: boolean;
|
|
52
|
+
readonly version: boolean;
|
|
53
|
+
readonly json: boolean;
|
|
54
|
+
readonly quiet: boolean;
|
|
55
|
+
readonly verbose: boolean;
|
|
56
|
+
readonly color: boolean;
|
|
57
|
+
}
|
|
58
|
+
export type FlagValue = string | number | boolean;
|
|
59
|
+
export interface ParsedCommand {
|
|
60
|
+
readonly command: string | null;
|
|
61
|
+
readonly globals: GlobalFlags;
|
|
62
|
+
readonly flags: ReadonlyMap<string, FlagValue>;
|
|
63
|
+
readonly positionals: readonly string[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A lookup that reads only what a table actually declares.
|
|
67
|
+
*
|
|
68
|
+
* `COMMANDS`, `GLOBAL_FLAGS` and each command's `flags` are plain object
|
|
69
|
+
* literals, so `table[name]` walks the prototype chain: every member of
|
|
70
|
+
* `Object.prototype` answers `!== undefined` and is accepted as a real command
|
|
71
|
+
* or flag.
|
|
72
|
+
*
|
|
73
|
+
* That is not theoretical. Against the shipped build:
|
|
74
|
+
*
|
|
75
|
+
* credda investigate --constructor ./repo
|
|
76
|
+
*
|
|
77
|
+
* was accepted, and the flag SWALLOWED the next token as its value — so the
|
|
78
|
+
* repository path silently disappeared and the run began without it. The same
|
|
79
|
+
* argv with `--nope` correctly threw "Unknown flag". `credda toString` resolved
|
|
80
|
+
* as a command instead of "Unknown command", and `commandUsage('toString')`
|
|
81
|
+
* then threw a TypeError rather than printing usage.
|
|
82
|
+
*
|
|
83
|
+
* A CLI's whole contract is that it refuses what it does not understand. This
|
|
84
|
+
* makes the lookup answer for declared keys only.
|
|
85
|
+
*/
|
|
86
|
+
export declare function own<T>(table: Readonly<Record<string, T>>, name: string): T | undefined;
|
|
87
|
+
export declare const GLOBAL_FLAGS: Readonly<Record<string, FlagSpec>>;
|
|
88
|
+
/**
|
|
89
|
+
* Parses argv against a command table.
|
|
90
|
+
*
|
|
91
|
+
* `--` ends flag parsing; everything after it is positional. A bare `-` is a
|
|
92
|
+
* positional (it means stdin to `credda investigate`), not a flag.
|
|
93
|
+
*/
|
|
94
|
+
export declare function parseArgs(argv: readonly string[], commands: Readonly<Record<string, CommandSpec>>): ParsedCommand;
|
|
95
|
+
/** Reads a string flag, or null when absent. */
|
|
96
|
+
export declare function stringFlag(parsed: ParsedCommand, name: string): string | null;
|
|
97
|
+
/** Reads a number flag, or null when absent. */
|
|
98
|
+
export declare function numberFlag(parsed: ParsedCommand, name: string): number | null;
|
|
99
|
+
export declare function boolFlag(parsed: ParsedCommand, name: string): boolean;
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small argument parser for the `credda` command surface.
|
|
3
|
+
*
|
|
4
|
+
* Hand-rolled deliberately: a dependency here would be a supply-chain risk
|
|
5
|
+
* taken on for string parsing. The surface has outgrown the "six commands and a
|
|
6
|
+
* dozen flags" this line used to claim -- it is 13 commands, 3 aliases and 27
|
|
7
|
+
* flags -- and the argument holds better at that size, not worse.
|
|
8
|
+
* The parser is spec-driven so `--help` is generated from the same data the
|
|
9
|
+
* parser validates against and cannot drift from it.
|
|
10
|
+
*/
|
|
11
|
+
/** A user error: bad flag, missing value, unknown command. Never a bug. */
|
|
12
|
+
export class UsageError extends Error {
|
|
13
|
+
command;
|
|
14
|
+
constructor(message,
|
|
15
|
+
/** The command whose usage should be printed, if one was identified. */
|
|
16
|
+
command = null) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.command = command;
|
|
19
|
+
this.name = 'UsageError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A lookup that reads only what a table actually declares.
|
|
24
|
+
*
|
|
25
|
+
* `COMMANDS`, `GLOBAL_FLAGS` and each command's `flags` are plain object
|
|
26
|
+
* literals, so `table[name]` walks the prototype chain: every member of
|
|
27
|
+
* `Object.prototype` answers `!== undefined` and is accepted as a real command
|
|
28
|
+
* or flag.
|
|
29
|
+
*
|
|
30
|
+
* That is not theoretical. Against the shipped build:
|
|
31
|
+
*
|
|
32
|
+
* credda investigate --constructor ./repo
|
|
33
|
+
*
|
|
34
|
+
* was accepted, and the flag SWALLOWED the next token as its value — so the
|
|
35
|
+
* repository path silently disappeared and the run began without it. The same
|
|
36
|
+
* argv with `--nope` correctly threw "Unknown flag". `credda toString` resolved
|
|
37
|
+
* as a command instead of "Unknown command", and `commandUsage('toString')`
|
|
38
|
+
* then threw a TypeError rather than printing usage.
|
|
39
|
+
*
|
|
40
|
+
* A CLI's whole contract is that it refuses what it does not understand. This
|
|
41
|
+
* makes the lookup answer for declared keys only.
|
|
42
|
+
*/
|
|
43
|
+
export function own(table, name) {
|
|
44
|
+
return Object.prototype.hasOwnProperty.call(table, name) ? table[name] : undefined;
|
|
45
|
+
}
|
|
46
|
+
export const GLOBAL_FLAGS = {
|
|
47
|
+
help: { kind: 'boolean', alias: 'h', description: 'Show help for the command and exit' },
|
|
48
|
+
version: { kind: 'boolean', description: 'Print the credda version and exit' },
|
|
49
|
+
json: { kind: 'boolean', description: 'Machine-readable JSONL on stdout; nothing else on stdout' },
|
|
50
|
+
quiet: { kind: 'boolean', description: 'Print only the final outcome line' },
|
|
51
|
+
verbose: { kind: 'boolean', description: 'Include debug-severity events' },
|
|
52
|
+
'no-color': { kind: 'boolean', description: 'Disable ANSI colour (also honoured: NO_COLOR)' },
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Parses argv against a command table.
|
|
56
|
+
*
|
|
57
|
+
* `--` ends flag parsing; everything after it is positional. A bare `-` is a
|
|
58
|
+
* positional (it means stdin to `credda investigate`), not a flag.
|
|
59
|
+
*/
|
|
60
|
+
export function parseArgs(argv, commands) {
|
|
61
|
+
const globals = {
|
|
62
|
+
help: false,
|
|
63
|
+
version: false,
|
|
64
|
+
json: false,
|
|
65
|
+
quiet: false,
|
|
66
|
+
verbose: false,
|
|
67
|
+
'no-color': false,
|
|
68
|
+
};
|
|
69
|
+
const flags = new Map();
|
|
70
|
+
const positionals = [];
|
|
71
|
+
let command = null;
|
|
72
|
+
let spec = null;
|
|
73
|
+
let endOfFlags = false;
|
|
74
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
75
|
+
const token = argv[i];
|
|
76
|
+
if (token === undefined)
|
|
77
|
+
continue;
|
|
78
|
+
if (endOfFlags) {
|
|
79
|
+
positionals.push(token);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (token === '--') {
|
|
83
|
+
endOfFlags = true;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (token.startsWith('-') && token !== '-') {
|
|
87
|
+
const { name, inlineValue } = splitFlag(token);
|
|
88
|
+
const resolved = resolveFlag(name, spec, command);
|
|
89
|
+
if (resolved.kind === 'boolean') {
|
|
90
|
+
if (inlineValue !== null) {
|
|
91
|
+
throw new UsageError(`The flag --${resolved.name} does not take a value.`, command);
|
|
92
|
+
}
|
|
93
|
+
if (resolved.global)
|
|
94
|
+
globals[resolved.name] = true;
|
|
95
|
+
else
|
|
96
|
+
flags.set(resolved.name, true);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
let raw = inlineValue;
|
|
100
|
+
if (raw === null) {
|
|
101
|
+
const next = argv[i + 1];
|
|
102
|
+
// A negative number is a value, not a flag: `--since -2` must reach the
|
|
103
|
+
// command's own validation and produce its message, not "unknown flag".
|
|
104
|
+
const looksLikeFlag = next !== undefined &&
|
|
105
|
+
next.startsWith('-') &&
|
|
106
|
+
next !== '-' &&
|
|
107
|
+
!(resolved.kind === 'number' && /^-\d/.test(next));
|
|
108
|
+
if (next === undefined || looksLikeFlag) {
|
|
109
|
+
const placeholder = resolved.spec.valueName ?? `<${resolved.kind}>`;
|
|
110
|
+
throw new UsageError(`The flag --${resolved.name} needs a value: --${resolved.name} ${placeholder}`, command);
|
|
111
|
+
}
|
|
112
|
+
raw = next;
|
|
113
|
+
i += 1;
|
|
114
|
+
}
|
|
115
|
+
flags.set(resolved.name, coerce(resolved.name, raw, resolved.spec, command));
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (command === null && positionals.length === 0) {
|
|
119
|
+
const found = own(commands, token);
|
|
120
|
+
if (found !== undefined) {
|
|
121
|
+
command = token;
|
|
122
|
+
spec = found;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// An unknown first word is an unknown command, not a positional.
|
|
126
|
+
throw new UsageError(`Unknown command '${token}'. Run 'credda --help' for the list of commands.`, null);
|
|
127
|
+
}
|
|
128
|
+
positionals.push(token);
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
command,
|
|
132
|
+
globals: {
|
|
133
|
+
help: globals['help'] === true,
|
|
134
|
+
version: globals['version'] === true,
|
|
135
|
+
json: globals['json'] === true,
|
|
136
|
+
quiet: globals['quiet'] === true,
|
|
137
|
+
verbose: globals['verbose'] === true,
|
|
138
|
+
color: globals['no-color'] !== true,
|
|
139
|
+
},
|
|
140
|
+
flags,
|
|
141
|
+
positionals,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function splitFlag(token) {
|
|
145
|
+
const body = token.startsWith('--') ? token.slice(2) : token.slice(1);
|
|
146
|
+
const eq = body.indexOf('=');
|
|
147
|
+
if (eq === -1)
|
|
148
|
+
return { name: body, inlineValue: null };
|
|
149
|
+
return { name: body.slice(0, eq), inlineValue: body.slice(eq + 1) };
|
|
150
|
+
}
|
|
151
|
+
function resolveFlag(name, spec, command) {
|
|
152
|
+
const commandFlags = spec?.flags ?? {};
|
|
153
|
+
const direct = own(commandFlags, name);
|
|
154
|
+
if (direct !== undefined)
|
|
155
|
+
return { name, kind: direct.kind, spec: direct, global: false };
|
|
156
|
+
const globalSpec = own(GLOBAL_FLAGS, name);
|
|
157
|
+
if (globalSpec !== undefined)
|
|
158
|
+
return { name, kind: globalSpec.kind, spec: globalSpec, global: true };
|
|
159
|
+
for (const [key, candidate] of Object.entries(commandFlags)) {
|
|
160
|
+
if (candidate.alias === name)
|
|
161
|
+
return { name: key, kind: candidate.kind, spec: candidate, global: false };
|
|
162
|
+
}
|
|
163
|
+
for (const [key, candidate] of Object.entries(GLOBAL_FLAGS)) {
|
|
164
|
+
if (candidate.alias === name)
|
|
165
|
+
return { name: key, kind: candidate.kind, spec: candidate, global: true };
|
|
166
|
+
}
|
|
167
|
+
const known = [...Object.keys(commandFlags), ...Object.keys(GLOBAL_FLAGS)];
|
|
168
|
+
const suggestion = closest(name, known);
|
|
169
|
+
const where = command === null ? '' : ` for 'credda ${command}'`;
|
|
170
|
+
throw new UsageError(`Unknown flag '--${name}'${where}.${suggestion === null ? '' : ` Did you mean '--${suggestion}'?`}`, command);
|
|
171
|
+
}
|
|
172
|
+
function coerce(name, raw, spec, command) {
|
|
173
|
+
if (spec.kind === 'number') {
|
|
174
|
+
const value = Number(raw);
|
|
175
|
+
if (!Number.isFinite(value)) {
|
|
176
|
+
throw new UsageError(`The flag --${name} needs a number, but got '${raw}'.`, command);
|
|
177
|
+
}
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
if (spec.choices !== undefined && !spec.choices.includes(raw)) {
|
|
181
|
+
throw new UsageError(`Invalid value '${raw}' for --${name}. Expected one of: ${spec.choices.join(', ')}.`, command);
|
|
182
|
+
}
|
|
183
|
+
return raw;
|
|
184
|
+
}
|
|
185
|
+
/** Levenshtein-based suggestion, only offered when the edit distance is small. */
|
|
186
|
+
function closest(input, candidates) {
|
|
187
|
+
let best = null;
|
|
188
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
189
|
+
for (const candidate of candidates) {
|
|
190
|
+
const distance = editDistance(input, candidate);
|
|
191
|
+
if (distance < bestDistance) {
|
|
192
|
+
bestDistance = distance;
|
|
193
|
+
best = candidate;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return bestDistance <= Math.max(1, Math.floor(input.length / 3)) ? best : null;
|
|
197
|
+
}
|
|
198
|
+
function editDistance(a, b) {
|
|
199
|
+
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
200
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
201
|
+
const current = [i, ...new Array(b.length).fill(0)];
|
|
202
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
203
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
204
|
+
current[j] = Math.min((current[j - 1] ?? 0) + 1, (previous[j] ?? 0) + 1, (previous[j - 1] ?? 0) + cost);
|
|
205
|
+
}
|
|
206
|
+
previous = current;
|
|
207
|
+
}
|
|
208
|
+
return previous[b.length] ?? 0;
|
|
209
|
+
}
|
|
210
|
+
/** Reads a string flag, or null when absent. */
|
|
211
|
+
export function stringFlag(parsed, name) {
|
|
212
|
+
const value = parsed.flags.get(name);
|
|
213
|
+
return typeof value === 'string' ? value : null;
|
|
214
|
+
}
|
|
215
|
+
/** Reads a number flag, or null when absent. */
|
|
216
|
+
export function numberFlag(parsed, name) {
|
|
217
|
+
const value = parsed.flags.get(name);
|
|
218
|
+
return typeof value === 'number' ? value : null;
|
|
219
|
+
}
|
|
220
|
+
export function boolFlag(parsed, name) {
|
|
221
|
+
return parsed.flags.get(name) === true;
|
|
222
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The command table. This is the single source of truth for what the CLI
|
|
3
|
+
* accepts: the parser validates against it and `--help` is rendered from it,
|
|
4
|
+
* so documented flags and accepted flags cannot drift apart.
|
|
5
|
+
*
|
|
6
|
+
* ## What this CLI claims, and what it does not
|
|
7
|
+
*
|
|
8
|
+
* A run prepares an environment, reproduces the reported failure, captures its
|
|
9
|
+
* signature as evidence, diagnoses a cause where the evidence supports one,
|
|
10
|
+
* writes the patch and proves it with a test that fails before and passes
|
|
11
|
+
* after. It ends at a diff for a person to review. **It proposes and never
|
|
12
|
+
* merges**, and nothing here takes write access to a repository to do it.
|
|
13
|
+
*
|
|
14
|
+
* **How far a run goes is decided by the provider, not by a flag.** The fix
|
|
15
|
+
* stage is on the investigation path (ADR 0019) and is entered only when the
|
|
16
|
+
* configured provider can author code. Under `CREDDA_PROVIDER=auto` with no
|
|
17
|
+
* key the engine degrades to rule-based reasoning and stops after the
|
|
18
|
+
* diagnosis, because a rule-based patch is worse than none. `PATCH_PATH_STATES`
|
|
19
|
+
* in `packages/shared/src/states.ts` carries that gate and the evidence behind
|
|
20
|
+
* it. There is deliberately no switch here that overrides it: a flag would put
|
|
21
|
+
* an unevidenced claim one environment variable away from a customer.
|
|
22
|
+
*
|
|
23
|
+
* Two consequences this file carries: the old command names keep working and
|
|
24
|
+
* stop describing an output (see {@link INVESTIGATE}), and exit code 3 is
|
|
25
|
+
* reserved rather than reused (see {@link RESERVED_EXIT_CODES}).
|
|
26
|
+
*/
|
|
27
|
+
import { type CommandSpec } from './args.js';
|
|
28
|
+
/**
|
|
29
|
+
* Exit codes. Documented here, in `--help`, and in docs/cli.md.
|
|
30
|
+
*
|
|
31
|
+
* An investigation that abstains is a success: NO_CHANGE_REQUIRED and
|
|
32
|
+
* INCONCLUSIVE both exit 0. Every non-zero code an investigation can return is
|
|
33
|
+
* a genuine failure.
|
|
34
|
+
*
|
|
35
|
+
* `credda triage` is the one command with two successful codes, and the second of
|
|
36
|
+
* them is non-zero. It is not an investigation and has no Outcome: it executes
|
|
37
|
+
* nothing, so "did it reach a verdict" is not a question about it. What a caller
|
|
38
|
+
* needs from it is which of two correct answers it gave, and 0 is the silent
|
|
39
|
+
* one. See {@link EXIT.COMMENT_READY} for why that way round.
|
|
40
|
+
*
|
|
41
|
+
* The report record (ADR 0012) adds no code, and the omission is a decision. Its
|
|
42
|
+
* confidence class is the obvious candidate -- something like "8:
|
|
43
|
+
* NOT_ESTABLISHED", the next free number -- and it is the wrong thing to encode.
|
|
44
|
+
* `NOT_ESTABLISHED` is the *correct* class for an abstention, which is the
|
|
45
|
+
* outcome this table already insists is a success: code 0 covers it for an
|
|
46
|
+
* investigation and for triage alike, and it is the only one of these eight
|
|
47
|
+
* codes that does. Giving it a non-zero code
|
|
48
|
+
* would make every CI that treats non-zero as failure fail on exactly the runs
|
|
49
|
+
* Credda gets right, and would create a second, contradictory answer to a
|
|
50
|
+
* question `outcome` already answers. The confidence class is a property of the
|
|
51
|
+
* record, readable with `credda report <id> --json`, and the exit code stays a
|
|
52
|
+
* statement about whether the run reached a verdict.
|
|
53
|
+
*
|
|
54
|
+
* ## Code 3 was held open, and is returned again
|
|
55
|
+
*
|
|
56
|
+
* `PATCH_REJECTED` is the exit code of a run that produced a change and then
|
|
57
|
+
* threw it away. ADR 0015 stopped anything from producing changes and held the
|
|
58
|
+
* code open rather than renumbering the table; ADR 0019 put the fix stage back
|
|
59
|
+
* on the path, so runs return 3 again and it means exactly what the old scripts
|
|
60
|
+
* were written against. {@link RESERVED_EXIT_CODES} is empty as a result, and
|
|
61
|
+
* kept, because a test reads it and a code moving between reserved and returned
|
|
62
|
+
* should move its reason with it.
|
|
63
|
+
*/
|
|
64
|
+
export declare const EXIT: {
|
|
65
|
+
/** Success, including NO_CHANGE_REQUIRED and INCONCLUSIVE. */
|
|
66
|
+
readonly SUCCESS: 0;
|
|
67
|
+
/** Credda itself failed: internal error, unreadable database, crash. */
|
|
68
|
+
readonly INTERNAL_ERROR: 1;
|
|
69
|
+
/** The command line or its inputs were wrong. Nothing was run. */
|
|
70
|
+
readonly USAGE_ERROR: 2;
|
|
71
|
+
/** A change was produced and independent verification rejected it. */
|
|
72
|
+
readonly PATCH_REJECTED: 3;
|
|
73
|
+
/** The run was cancelled (Ctrl-C). */
|
|
74
|
+
readonly CANCELLED: 4;
|
|
75
|
+
/**
|
|
76
|
+
* NO_RUNNABLE_CHECK: nothing runnable could be derived from the report, so
|
|
77
|
+
* nothing was executed against the repository. Not a success and not a crash.
|
|
78
|
+
* See `exitCodeFor` for why it is neither 0 nor 1.
|
|
79
|
+
*/
|
|
80
|
+
readonly NO_RUNNABLE_CHECK: 5;
|
|
81
|
+
/**
|
|
82
|
+
* `credda triage` produced a comment, and it is on stdout. Nothing failed.
|
|
83
|
+
*
|
|
84
|
+
* ## Why the comment is the non-zero side and silence is 0
|
|
85
|
+
*
|
|
86
|
+
* Silence is the common case, not the exceptional one: half of real inbound
|
|
87
|
+
* produces nothing worth saying (`bench/harvest`, 50.6% of 729 issues). A
|
|
88
|
+
* code that turned every second opened issue into a red job would be switched
|
|
89
|
+
* off inside a week, and this repository's standing rule is already that
|
|
90
|
+
* abstention is a success. So silence exits 0, and it is 0 for the same
|
|
91
|
+
* reason NO_CHANGE_REQUIRED is.
|
|
92
|
+
*
|
|
93
|
+
* That leaves the comment needing a code of its own, because "post this" and
|
|
94
|
+
* "post nothing" are the two answers a caller has to tell apart and stdout
|
|
95
|
+
* being empty is a weaker signal than a number. Giving it a non-zero one is
|
|
96
|
+
* deliberate rather than reluctant: **every way of misreading this code then
|
|
97
|
+
* fails towards not posting.** A shell under `set -e` stops before the
|
|
98
|
+
* posting step; a caller that ignores the code and pipes stdout gets an empty
|
|
99
|
+
* document on the silent path; a caller that tests for 0 posts only silence,
|
|
100
|
+
* which posts nothing. The failure this product cannot afford is a
|
|
101
|
+
* confidently wrong refusal on a stranger's issue -- the dominant rule is
|
|
102
|
+
* still wrong 8.7% of the times it fires (Credda-io/core#7) -- so the
|
|
103
|
+
* direction of every mistake here has to be silence.
|
|
104
|
+
*
|
|
105
|
+
* 6 rather than reusing 5: NO_RUNNABLE_CHECK is a statement that nothing was
|
|
106
|
+
* executed against the repository, which is true of *every* triage run by
|
|
107
|
+
* design, so the two would stop meaning different things.
|
|
108
|
+
*/
|
|
109
|
+
readonly COMMENT_READY: 6;
|
|
110
|
+
/**
|
|
111
|
+
* `credda cancel` reached a run that is still executing and asked it to stop.
|
|
112
|
+
* The request is delivered; the run has not stopped yet.
|
|
113
|
+
*
|
|
114
|
+
* ## Why this is not 0, and not 4
|
|
115
|
+
*
|
|
116
|
+
* `apps/api/src/routes/investigations.ts` answers the same question with two
|
|
117
|
+
* different HTTP statuses -- 200 CANCELLED when the run is genuinely over, 202
|
|
118
|
+
* CANCELLATION_REQUESTED when a process is still inside it holding a sandbox
|
|
119
|
+
* and a model budget. A shell has no status line to read. It has this number,
|
|
120
|
+
* and if both answers were 0 then `credda cancel $id && echo stopped` would
|
|
121
|
+
* print "stopped" over a container that is still running and still spending.
|
|
122
|
+
* That is the one false claim this whole route was written to avoid, so the
|
|
123
|
+
* two answers get two codes.
|
|
124
|
+
*
|
|
125
|
+
* 4 is the run's own code, returned by `credda investigate` when the run it
|
|
126
|
+
* was executing was cancelled. It is a statement that a run ended. This is a
|
|
127
|
+
* statement that one was asked to, made by a different process that cannot
|
|
128
|
+
* see whether it did. Reusing 4 would collapse exactly the distinction.
|
|
129
|
+
*
|
|
130
|
+
* Every way of misreading 7 fails towards waiting rather than towards
|
|
131
|
+
* assuming: `set -e` stops, a test for 0 does not proceed. `credda events
|
|
132
|
+
* <id> --follow` is how a caller learns the run actually ended.
|
|
133
|
+
*/
|
|
134
|
+
readonly CANCELLATION_REQUESTED: 7;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Codes no run of this version can return, and the reason each is held open.
|
|
138
|
+
*
|
|
139
|
+
* A test reads this, so a code cannot quietly move between "reserved" and
|
|
140
|
+
* "returned" without the reason moving with it.
|
|
141
|
+
*/
|
|
142
|
+
export declare const RESERVED_EXIT_CODES: Readonly<Record<number, string>>;
|
|
143
|
+
export declare const EXIT_CODE_HELP: readonly string[];
|
|
144
|
+
export declare const COMMANDS: Readonly<Record<string, CommandSpec>>;
|
|
145
|
+
/**
|
|
146
|
+
* The command an alias dispatches to. Unknown names are returned unchanged so
|
|
147
|
+
* the caller's own "unknown command" path still owns that message.
|
|
148
|
+
*/
|
|
149
|
+
export declare function canonicalCommand(name: string): string;
|
|
150
|
+
/** Alias name to the command it stands for, for the root usage. */
|
|
151
|
+
export declare function aliases(): readonly (readonly [string, string])[];
|
|
152
|
+
export declare function rootUsage(): string;
|
|
153
|
+
export declare function commandUsage(name: string): string;
|