@vibe-commit/capture 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 +66 -0
- package/dist/agents/claude_code.d.ts +11 -0
- package/dist/agents/claude_code.js +162 -0
- package/dist/agents/claude_code.js.map +1 -0
- package/dist/agents/codex_cli.d.ts +15 -0
- package/dist/agents/codex_cli.js +67 -0
- package/dist/agents/codex_cli.js.map +1 -0
- package/dist/agents/cursor.d.ts +21 -0
- package/dist/agents/cursor.js +89 -0
- package/dist/agents/cursor.js.map +1 -0
- package/dist/agents/registry.d.ts +184 -0
- package/dist/agents/registry.js +212 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/agents/types.d.ts +279 -0
- package/dist/agents/types.js +67 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/commands/connect.d.ts +36 -0
- package/dist/commands/connect.js +499 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/context.d.ts +45 -0
- package/dist/commands/context.js +14 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/report.d.ts +105 -0
- package/dist/commands/report.js +430 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/status.d.ts +29 -0
- package/dist/commands/status.js +236 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/why.d.ts +349 -0
- package/dist/commands/why.js +937 -0
- package/dist/commands/why.js.map +1 -0
- package/dist/consent.d.ts +59 -0
- package/dist/consent.js +111 -0
- package/dist/consent.js.map +1 -0
- package/dist/copy/claims.d.ts +79 -0
- package/dist/copy/claims.js +343 -0
- package/dist/copy/claims.js.map +1 -0
- package/dist/copy/grades.d.ts +117 -0
- package/dist/copy/grades.js +96 -0
- package/dist/copy/grades.js.map +1 -0
- package/dist/copy/index.d.ts +12 -0
- package/dist/copy/index.js +13 -0
- package/dist/copy/index.js.map +1 -0
- package/dist/copy/strings.d.ts +1328 -0
- package/dist/copy/strings.js +1243 -0
- package/dist/copy/strings.js.map +1 -0
- package/dist/copy/time.d.ts +27 -0
- package/dist/copy/time.js +43 -0
- package/dist/copy/time.js.map +1 -0
- package/dist/credential.d.ts +211 -0
- package/dist/credential.js +298 -0
- package/dist/credential.js.map +1 -0
- package/dist/exit.d.ts +59 -0
- package/dist/exit.js +60 -0
- package/dist/exit.js.map +1 -0
- package/dist/git.d.ts +299 -0
- package/dist/git.js +653 -0
- package/dist/git.js.map +1 -0
- package/dist/hooks/entry.d.ts +277 -0
- package/dist/hooks/entry.js +1092 -0
- package/dist/hooks/entry.js.map +1 -0
- package/dist/hooks/post_commit.d.ts +59 -0
- package/dist/hooks/post_commit.js +160 -0
- package/dist/hooks/post_commit.js.map +1 -0
- package/dist/hooks/post_rewrite.d.ts +59 -0
- package/dist/hooks/post_rewrite.js +161 -0
- package/dist/hooks/post_rewrite.js.map +1 -0
- package/dist/hooks/session_end.d.ts +101 -0
- package/dist/hooks/session_end.js +120 -0
- package/dist/hooks/session_end.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +360 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +153 -0
- package/dist/install.js +284 -0
- package/dist/install.js.map +1 -0
- package/dist/json.d.ts +100 -0
- package/dist/json.js +94 -0
- package/dist/json.js.map +1 -0
- package/dist/oauth/discovery.d.ts +96 -0
- package/dist/oauth/discovery.js +163 -0
- package/dist/oauth/discovery.js.map +1 -0
- package/dist/oauth/lock.d.ts +100 -0
- package/dist/oauth/lock.js +287 -0
- package/dist/oauth/lock.js.map +1 -0
- package/dist/oauth/loopback.d.ts +85 -0
- package/dist/oauth/loopback.js +124 -0
- package/dist/oauth/loopback.js.map +1 -0
- package/dist/oauth/pkce.d.ts +58 -0
- package/dist/oauth/pkce.js +77 -0
- package/dist/oauth/pkce.js.map +1 -0
- package/dist/oauth/read.d.ts +120 -0
- package/dist/oauth/read.js +144 -0
- package/dist/oauth/read.js.map +1 -0
- package/dist/oauth/session.d.ts +80 -0
- package/dist/oauth/session.js +112 -0
- package/dist/oauth/session.js.map +1 -0
- package/dist/oauth/signin.d.ts +178 -0
- package/dist/oauth/signin.js +225 -0
- package/dist/oauth/signin.js.map +1 -0
- package/dist/oauth/token.d.ts +139 -0
- package/dist/oauth/token.js +183 -0
- package/dist/oauth/token.js.map +1 -0
- package/dist/paths.d.ts +366 -0
- package/dist/paths.js +460 -0
- package/dist/paths.js.map +1 -0
- package/dist/policy.d.ts +195 -0
- package/dist/policy.js +244 -0
- package/dist/policy.js.map +1 -0
- package/dist/post.d.ts +302 -0
- package/dist/post.js +384 -0
- package/dist/post.js.map +1 -0
- package/dist/project.d.ts +99 -0
- package/dist/project.js +192 -0
- package/dist/project.js.map +1 -0
- package/dist/redact.d.ts +175 -0
- package/dist/redact.js +700 -0
- package/dist/redact.js.map +1 -0
- package/dist/runtime.d.ts +30 -0
- package/dist/runtime.js +37 -0
- package/dist/runtime.js.map +1 -0
- package/dist/spawn_budget.d.ts +101 -0
- package/dist/spawn_budget.js +124 -0
- package/dist/spawn_budget.js.map +1 -0
- package/dist/spool.d.ts +390 -0
- package/dist/spool.js +556 -0
- package/dist/spool.js.map +1 -0
- package/dist/state.d.ts +109 -0
- package/dist/state.js +217 -0
- package/dist/state.js.map +1 -0
- package/dist/system_message.d.ts +101 -0
- package/dist/system_message.js +141 -0
- package/dist/system_message.js.map +1 -0
- package/dist/term.d.ts +112 -0
- package/dist/term.js +134 -0
- package/dist/term.js.map +1 -0
- package/dist/version.d.ts +16 -0
- package/dist/version.js +17 -0
- package/dist/version.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vibecommit status` and `vibecommit off` — the screen, `CR-021`.
|
|
3
|
+
*
|
|
4
|
+
* Style guide §10.3 draws it and D65 makes that drawing the ruling, so this
|
|
5
|
+
* module renders it rather than designing it: four questions in fixed order
|
|
6
|
+
* (on for *this* repo? last successful send? where do I look? what do I type to
|
|
7
|
+
* fix it?), then the revoke link, on §13.5's `LABEL_GUTTER`.
|
|
8
|
+
*
|
|
9
|
+
* ## What is deliberately NOT on the screen
|
|
10
|
+
*
|
|
11
|
+
* §10.3's `last sent` row reads `4 minutes ago · 3 turns · seq 41`. **The turn
|
|
12
|
+
* count is not rendered and must not be.** Counting turns means parsing the
|
|
13
|
+
* transcript's NDJSON and deciding what a turn is, and that is the analysis the
|
|
14
|
+
* client is barred from holding — D60 §D6: *the client may render; it may not
|
|
15
|
+
* analyze, hash, or canonicalize*. The one recorded deviation is `CR-024d`'s
|
|
16
|
+
* redaction parse, authorised by name; there is no such authorisation here, and
|
|
17
|
+
* the server is the only thing holding sealed turns to count. The age and the
|
|
18
|
+
* `seq` are both facts this client already wrote down, so both render.
|
|
19
|
+
*
|
|
20
|
+
* ## Absence is not a blank
|
|
21
|
+
*
|
|
22
|
+
* When nothing has been delivered for this repo the row is OMITTED and
|
|
23
|
+
* `STATUS.neverSent` says so in a sentence. No sixth absence phrasing is
|
|
24
|
+
* invented here: `CR-112` (W8) owns the five-absence grammar as one set, and
|
|
25
|
+
* adding a variant now is what that task exists to prevent.
|
|
26
|
+
*/
|
|
27
|
+
import { COMMANDS, CREDENTIAL, ERRORS, OFF, STATUS, URLS, relativeAge } from "../copy/index.js";
|
|
28
|
+
import { isProjectAllowed, revokeProject } from "../consent.js";
|
|
29
|
+
import { loadCredential } from "../credential.js";
|
|
30
|
+
import { EXIT } from "../exit.js";
|
|
31
|
+
import { resolveRepoSlug } from "../git.js";
|
|
32
|
+
import { emitJson } from "../json.js";
|
|
33
|
+
import { resolveProjectKeys } from "../project.js";
|
|
34
|
+
import { lastSendForRepo } from "../state.js";
|
|
35
|
+
import { LABEL_GUTTER, WRAP_COLUMNS, glyph, labelled, paint, renderErrorBlock, truncatePath, wrap, } from "../term.js";
|
|
36
|
+
import { writeLines } from "./context.js";
|
|
37
|
+
/** §10.3 indents the gutter rows four columns under the state line. */
|
|
38
|
+
const ROW_INDENT = 4;
|
|
39
|
+
export function status(ctx, argv = []) {
|
|
40
|
+
// ⛔ THIS SCREEN USES BOTH ROLES AT ONCE (`D184 §1`), which is why the whole
|
|
41
|
+
// function is not flipped: `on` below is CONSENT STATE and keys on the git
|
|
42
|
+
// common dir, while the repo row, the send scan and the `--json` document are
|
|
43
|
+
// REPO IDENTITY and stay on the worktree toplevel. The `--json` field is
|
|
44
|
+
// literally named `toplevel`, so moving it would make the document lie.
|
|
45
|
+
const keys = resolveProjectKeys(ctx.cwd);
|
|
46
|
+
if (keys === null) {
|
|
47
|
+
writeLines(ctx.stderr, renderErrorBlock({ kind: "bad", what: ERRORS.notAGitRepo, why: [] }, ctx.colour));
|
|
48
|
+
return EXIT.failure;
|
|
49
|
+
}
|
|
50
|
+
const projectKey = keys.worktree;
|
|
51
|
+
// ⛔ CONSENT STATE, on the common dir. A worktree of a consented clone must
|
|
52
|
+
// report ON here, because the hook that runs there is now admitted — a
|
|
53
|
+
// `status` still keyed on the toplevel would tell that user they are
|
|
54
|
+
// disconnected while their session was being captured.
|
|
55
|
+
const on = isProjectAllowed(ctx.home, keys.consent);
|
|
56
|
+
const kind = on ? "ok" : "warn";
|
|
57
|
+
const lines = [
|
|
58
|
+
` ${paint(ctx.colour, kind, glyph(ctx.colour, kind))} ${paint(ctx.colour, "strong", on ? STATUS.onForRepo : STATUS.offForRepo)}`,
|
|
59
|
+
labelled(ROW_INDENT, STATUS.repoLabel, repoValue(ctx, projectKey)),
|
|
60
|
+
];
|
|
61
|
+
// Question 2: the last successful send FOR THIS REPO. `status` has no
|
|
62
|
+
// session_id — it is interactive — so this is a scan of the repo's session
|
|
63
|
+
// files rather than a lookup (`lastSendForRepo`).
|
|
64
|
+
const lastSend = lastSendForRepo(ctx.home, projectKey);
|
|
65
|
+
if (lastSend !== null) {
|
|
66
|
+
lines.push(labelled(ROW_INDENT, STATUS.lastSuccessLabel, STATUS.lastSuccessValue(relativeAge(lastSend.at, ctx.now().getTime()), lastSend.seq)));
|
|
67
|
+
}
|
|
68
|
+
lines.push(labelled(ROW_INDENT, STATUS.dashboardLabel, paint(ctx.colour, "accent", URLS.dashboard)));
|
|
69
|
+
const load = loadCredential({ env: ctx.env, home: ctx.home });
|
|
70
|
+
// §13.3 — `--json` emits NOTHING BUT the JSON document on stdout, so the
|
|
71
|
+
// screen assembled above is DISCARDED rather than printed beside it.
|
|
72
|
+
//
|
|
73
|
+
// The credential fault below still refuses, and it refuses the same way it
|
|
74
|
+
// does in prose: §13.6's block to stderr, nothing on stdout, exit 1. A failure
|
|
75
|
+
// is not an answer, so it gets no document — `src/json.ts`'s docblock states
|
|
76
|
+
// that split and why it is a builder's reading of §13.3 rather than a ruling.
|
|
77
|
+
//
|
|
78
|
+
// ⚠ `last_send` is `null` when nothing has been delivered, never a zeroed
|
|
79
|
+
// object: absence here is *nothing has been sent*, and a `{ at_ms: 0 }` would
|
|
80
|
+
// be a timestamp claim. §13.2's glyph rule has no glyph on this surface, so
|
|
81
|
+
// `outcome` carries the on/off distinction the glyph carries on screen.
|
|
82
|
+
if (argv.includes("--json")) {
|
|
83
|
+
if (load.kind !== "ok" && load.kind !== "absent") {
|
|
84
|
+
writeLines(ctx.stderr, credentialProblem(load, ctx.colour));
|
|
85
|
+
return EXIT.failure;
|
|
86
|
+
}
|
|
87
|
+
emitJson(ctx.stdout, "status", on ? "on" : "off", {
|
|
88
|
+
repo: resolveRepoSlug(projectKey),
|
|
89
|
+
toplevel: projectKey,
|
|
90
|
+
last_send: lastSend === null ? null : { at_ms: lastSend.at, seq: lastSend.seq },
|
|
91
|
+
dashboard: URLS.dashboard,
|
|
92
|
+
});
|
|
93
|
+
return on ? EXIT.ok : EXIT.notConnected;
|
|
94
|
+
}
|
|
95
|
+
if (load.kind !== "ok" && load.kind !== "absent") {
|
|
96
|
+
writeLines(ctx.stdout, lines);
|
|
97
|
+
writeLines(ctx.stderr, credentialProblem(load, ctx.colour));
|
|
98
|
+
return EXIT.failure;
|
|
99
|
+
}
|
|
100
|
+
// Absence is a SENTENCE, not a blank row. Triggered on the send record rather
|
|
101
|
+
// than on the credential: a repo that has delivered and then lost its
|
|
102
|
+
// credential has still recorded a session, and saying otherwise would be false.
|
|
103
|
+
if (on && lastSend === null)
|
|
104
|
+
lines.push("", ...wrap(STATUS.neverSent, 2));
|
|
105
|
+
lines.push("", ...actionLines(ctx, on));
|
|
106
|
+
writeLines(ctx.stdout, lines);
|
|
107
|
+
return on ? EXIT.ok : EXIT.notConnected;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* §10.3's repo row: `owner/name (git toplevel ~/path)`.
|
|
111
|
+
*
|
|
112
|
+
* The slug is `CR-019d`'s — the same value that goes on the wire as
|
|
113
|
+
* `X-Repo-Slug` — so what `status` shows is what the server will resolve, which
|
|
114
|
+
* is the point of `status` being the debugging tool (D57 §DX1). A repo with no
|
|
115
|
+
* GitHub remote shows its `local:` identity rather than hiding it: that IS the
|
|
116
|
+
* answer to "why does the dashboard not show my repo under its name?".
|
|
117
|
+
*
|
|
118
|
+
* The path is truncated from the LEFT (§13.4) against what the line actually has
|
|
119
|
+
* left, not a fixed 60, so the row cannot exceed 80 columns for a long slug.
|
|
120
|
+
*/
|
|
121
|
+
function repoValue(ctx, toplevel) {
|
|
122
|
+
const slug = resolveRepoSlug(toplevel);
|
|
123
|
+
const shown = tildify(toplevel, ctx.home);
|
|
124
|
+
const overhead = ROW_INDENT + LABEL_GUTTER + STATUS.repoToplevel(slug, "").length;
|
|
125
|
+
let budget = Math.max(12, WRAP_COLUMNS - overhead);
|
|
126
|
+
let value = STATUS.repoToplevel(slug, truncatePath(shown, budget));
|
|
127
|
+
// Fit the row by BYTES as well as by columns, and pay the difference only on
|
|
128
|
+
// rows that actually truncate. `truncatePath`'s `…` is one column but three
|
|
129
|
+
// UTF-8 bytes, so a row filled to exactly 80 columns is 82 bytes — which
|
|
130
|
+
// passes §13.4 (a terminal renders one column) and fails the byte-counting
|
|
131
|
+
// `awk` check the verify recipe uses. BSD awk counts bytes whatever the
|
|
132
|
+
// locale, so satisfying the stricter measure is cheaper than arguing about
|
|
133
|
+
// which one is right: it costs at most two columns, and only here.
|
|
134
|
+
while (ROW_INDENT + LABEL_GUTTER + Buffer.byteLength(value) > WRAP_COLUMNS && budget > 12) {
|
|
135
|
+
budget -= 1;
|
|
136
|
+
value = STATUS.repoToplevel(slug, truncatePath(shown, budget));
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
/** `/Users/dev/code/x` -> `~/code/x`, as §10.3 draws it. */
|
|
141
|
+
function tildify(path, home) {
|
|
142
|
+
if (home !== "" && (path === home || path.startsWith(`${home}/`))) {
|
|
143
|
+
return `~${path.slice(home.length)}`;
|
|
144
|
+
}
|
|
145
|
+
return path;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The two trailing actions, aligned as one pair — §10.3 draws their values in a
|
|
149
|
+
* shared column, which only holds if both labels pad to the longer of the two.
|
|
150
|
+
*
|
|
151
|
+
* When capture is OFF the first line is the reconnect command instead: there is
|
|
152
|
+
* nothing to turn off, and §10.3 only draws the on-state.
|
|
153
|
+
*/
|
|
154
|
+
function actionLines(ctx, on) {
|
|
155
|
+
const pairs = on
|
|
156
|
+
? [
|
|
157
|
+
[STATUS.turnOffLabel, COMMANDS.off],
|
|
158
|
+
[STATUS.revokeLabel, URLS.settings],
|
|
159
|
+
]
|
|
160
|
+
: [
|
|
161
|
+
[STATUS.fixCommandLabel, COMMANDS.connect],
|
|
162
|
+
[STATUS.revokeLabel, URLS.settings],
|
|
163
|
+
];
|
|
164
|
+
const width = Math.max(...pairs.map(([label]) => label.length));
|
|
165
|
+
return pairs.map(([label, value]) => {
|
|
166
|
+
const painted = value.startsWith("https://") ? paint(ctx.colour, "accent", value) : value;
|
|
167
|
+
return ` ${`${label}:`.padEnd(width + 2)}${painted}`;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
export function off(ctx) {
|
|
171
|
+
const keys = resolveProjectKeys(ctx.cwd);
|
|
172
|
+
if (keys === null) {
|
|
173
|
+
writeLines(ctx.stderr, renderErrorBlock({ kind: "bad", what: ERRORS.notAGitRepo, why: [] }, ctx.colour));
|
|
174
|
+
return EXIT.failure;
|
|
175
|
+
}
|
|
176
|
+
// ⛔ THE REVOKE SIDE, AND IT MUST DELETE THE KEY `connect` WROTE (`D184 §9`).
|
|
177
|
+
// `revokeProject` deletes by exact string and reports `false` when the key is
|
|
178
|
+
// absent — so a revoke still keyed on the toplevel would find nothing, print
|
|
179
|
+
// "already off", and LEAVE THE GRANT IN PLACE. The user believes they
|
|
180
|
+
// withdrew consent; capture continues. That is the worse direction of this
|
|
181
|
+
// same bug, and it is the second mutation the cells below pin.
|
|
182
|
+
const changed = revokeProject(ctx.home, keys.consent);
|
|
183
|
+
writeLines(ctx.stdout, [
|
|
184
|
+
...wrap(changed ? OFF.done : OFF.alreadyOff, 2),
|
|
185
|
+
...wrap(OFF.note, 2),
|
|
186
|
+
]);
|
|
187
|
+
return EXIT.ok;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The three credential problems that are worth four different fixes on an
|
|
191
|
+
* interactive surface. The hook collapses them to one `systemMessage`; here the
|
|
192
|
+
* user can act, so §13.6 gets its full what / why / fix.
|
|
193
|
+
*
|
|
194
|
+
* None of these lines can carry credential bytes: the plaintext leaves
|
|
195
|
+
* `IngestCredential` only through `expose()`, and no copy path calls it.
|
|
196
|
+
*/
|
|
197
|
+
function credentialProblem(load, colour) {
|
|
198
|
+
switch (load.kind) {
|
|
199
|
+
case "wrong-class":
|
|
200
|
+
return renderErrorBlock({
|
|
201
|
+
kind: "bad",
|
|
202
|
+
what: CREDENTIAL.wrongClassWhat,
|
|
203
|
+
why: [
|
|
204
|
+
load.source === "env"
|
|
205
|
+
? CREDENTIAL.wrongClassEnvWhy
|
|
206
|
+
: CREDENTIAL.wrongClassFileWhy,
|
|
207
|
+
],
|
|
208
|
+
fixLabel: CREDENTIAL.wrongClassFix,
|
|
209
|
+
fixes: [COMMANDS.connect],
|
|
210
|
+
}, colour);
|
|
211
|
+
case "insecure-file":
|
|
212
|
+
return renderErrorBlock({
|
|
213
|
+
kind: "bad",
|
|
214
|
+
what: CREDENTIAL.insecureFileWhat,
|
|
215
|
+
why: [
|
|
216
|
+
`${CREDENTIAL.insecureFileWhyLabel} ${truncatePath(load.path, 60)}`,
|
|
217
|
+
`${CREDENTIAL.insecureFileModeLabel} ${load.mode}`,
|
|
218
|
+
CREDENTIAL.insecureFileWhy,
|
|
219
|
+
],
|
|
220
|
+
fixLabel: CREDENTIAL.insecureFileFix,
|
|
221
|
+
fixes: [COMMANDS.chmodCredentials],
|
|
222
|
+
}, colour);
|
|
223
|
+
case "unreadable":
|
|
224
|
+
return renderErrorBlock({
|
|
225
|
+
kind: "bad",
|
|
226
|
+
what: CREDENTIAL.unreadableWhat,
|
|
227
|
+
why: [
|
|
228
|
+
`${CREDENTIAL.insecureFileWhyLabel} ${truncatePath(load.path, 60)}`,
|
|
229
|
+
CREDENTIAL.unreadableWhy,
|
|
230
|
+
],
|
|
231
|
+
fixLabel: CREDENTIAL.unreadableFix,
|
|
232
|
+
fixes: [COMMANDS.connect],
|
|
233
|
+
}, colour);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vibecommit why` — the screenshotted surface. `CR-086`, W9.
|
|
3
|
+
*
|
|
4
|
+
* *"Show the conversation turns recorded against the commit that last changed
|
|
5
|
+
* this line."* That sentence is VERBATIM-APPROVED (D61 §PS6), already in
|
|
6
|
+
* `strings.ts` and already pinned by `test/help.golden.txt`. This module is what
|
|
7
|
+
* makes it true.
|
|
8
|
+
*
|
|
9
|
+
* The screen is style guide §10.4; the copy is §R3's decision table in
|
|
10
|
+
* `docs/designs/claims-audit-why.md`, which §10.4 says must not be paraphrased.
|
|
11
|
+
* Layout is `DESIGN.md` §13 — six SGR roles, base-16 only, 80-column HARD WRAP,
|
|
12
|
+
* §13.6's error shape, §13.7's exit contract.
|
|
13
|
+
*
|
|
14
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
* ## ⚠ WHAT THIS VERB SHIPS WITHOUT, AND WHY IT SHIPS ANYWAY
|
|
16
|
+
*
|
|
17
|
+
* `why` is *blame → commit → attribution*. The third arrow has no producer, and
|
|
18
|
+
* two more of this screen's inputs do not exist either. All four were
|
|
19
|
+
* re-measured for this task rather than taken on trust (every tracked blob at
|
|
20
|
+
* `vibecommit-capture` `4dcf47d`, `vibecommit-mcp` `eeda878`,
|
|
21
|
+
* and `vibecommit-schema` `2c77400`, read as utf8 in
|
|
22
|
+
* Python — never `grep`, because `src/redact.ts` carries literal NUL bytes and
|
|
23
|
+
* `grep -I` skips it silently):
|
|
24
|
+
*
|
|
25
|
+
* 1. **`observed` has no producer in any repo** — 0 client git probes on the
|
|
26
|
+
* write path, and `vibecommit-mcp/src/conversation/edge_derivation.ts:36-47`
|
|
27
|
+
* states it outright. This module does not build one and must not be read
|
|
28
|
+
* as building one: `git blame` here is an INTERACTIVE READ-PATH probe whose
|
|
29
|
+
* output never crosses the ingest wire. `src/hooks/`, `src/post.ts` and
|
|
30
|
+
* `src/state.ts` are untouched.
|
|
31
|
+
* 2. **Nothing writes `commit_file_attribution`** — the table `agent_id` lives
|
|
32
|
+
* on. `CR-070` created it; `CR-072` shipped the edge only and said so.
|
|
33
|
+
* 3. **`capture_turns.subagent_count` EXISTS AND STILL HAS NO PRODUCER.**
|
|
34
|
+
* `CR-129a` added it (`vibecommit-schema` `f3022c5`), `integer`, nullable,
|
|
35
|
+
* no default. Nothing can carry it: mcp's `insertCaptureTurns` is an
|
|
36
|
+
* explicit seven-key literal and `blame_commit` selects
|
|
37
|
+
* `position, turn_hash`, so the column never reaches this client. The
|
|
38
|
+
* measurement, with the file and line for each answer, lives in
|
|
39
|
+
* `test/subagent-count.test.ts`.
|
|
40
|
+
*
|
|
41
|
+
* ⚠ **A COLUMN LIST STOOD HERE AND IT WAS A TALLY — D101, WRITE THE
|
|
42
|
+
* PREDICATE, NEVER THE COUNT.** It read `capture_id, position,
|
|
43
|
+
* workspace_id, conversation_id, turn_hash, is_human_prompt`, and it was
|
|
44
|
+
* an exact transcription of the original `create table` when it was
|
|
45
|
+
* written. **Three later migrations emptied it, none of them wrong:**
|
|
46
|
+
* `20260610140000` added `human_prompt_hash`; `20260620180000` renamed
|
|
47
|
+
* `workspace_id` to `org_id` across fourteen tables; `20260820000000`
|
|
48
|
+
* added `subagent_count`. So the list decayed to naming a column that no
|
|
49
|
+
* longer exists — D97's species, a statement that was genuine when
|
|
50
|
+
* authored and was emptied later by someone else's correct change. **The
|
|
51
|
+
* live set is not restated here**, because restating it would start the
|
|
52
|
+
* same decay again; ask the catalog.
|
|
53
|
+
* 4. **`blame_commit` does not exist** — it is `CR-085`, being built in
|
|
54
|
+
* another repo in this same wave, and D106 §1 measured production mcp still
|
|
55
|
+
* serving release v4, which has no ingest route at all. **There is no
|
|
56
|
+
* deployed server to test this against.**
|
|
57
|
+
*
|
|
58
|
+
* (1) and (2) are recorded in D104 §2 as owed their own `CR-###` and are
|
|
59
|
+
* explicitly not this task's. (3) was `CR-129`: `CR-129a` shipped the column in
|
|
60
|
+
* W10 and `CR-129d` verified the consumer against it — **the gap is narrower
|
|
61
|
+
* than it was and it is not closed**, because a producer needs an mcp write
|
|
62
|
+
* path AND an mcp read path, and no row owns either.
|
|
63
|
+
*
|
|
64
|
+
* D81 governs the shape of the answer: *"A task can be un-shippable because of
|
|
65
|
+
* what it CLAIMS, not because of what it lacks… making the claim honest is
|
|
66
|
+
* usually cheaper than building the thing, and it is always the correct
|
|
67
|
+
* order."* So every one of those holes renders as an absence in this project's
|
|
68
|
+
* grammar — **state the absence, state the cause, state what would change it** —
|
|
69
|
+
* and none of them renders as a zero, a placeholder, or a silence.
|
|
70
|
+
*
|
|
71
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
* ## THE THREE RULES THIS FILE IS BUILT AROUND
|
|
73
|
+
*
|
|
74
|
+
* **The payload is UNTRUSTED JSON.** `blame_commit` has no implementation to
|
|
75
|
+
* agree with, so `BlameCommitPayload` below is a contract this repo DECLARES and
|
|
76
|
+
* `CR-085` owns the other side of. Every field is validated; anything
|
|
77
|
+
* unrecognised is `malformed` and renders as such rather than being partly
|
|
78
|
+
* guessed at.
|
|
79
|
+
*
|
|
80
|
+
* **The client may render; it may not analyze** (D60 §D1a). The one authorised
|
|
81
|
+
* deviation in this package is `CR-024d`'s redaction parse, by name; there is no
|
|
82
|
+
* authorisation here. So the truncation totals come from the payload and this
|
|
83
|
+
* module never counts the array it was handed, never ranks it, and never
|
|
84
|
+
* reorders it.
|
|
85
|
+
*
|
|
86
|
+
* **`null` and ABSENT are different facts.** The schema comment on `agent_id`
|
|
87
|
+
* says *"NULL = the main thread"* — so a null we were SENT is knowledge, and a
|
|
88
|
+
* field that never arrived is not. An unmarked turn rendered from an absent
|
|
89
|
+
* field is the overclaim, so a turn is rendered unmarked only when the field was
|
|
90
|
+
* actually present.
|
|
91
|
+
*
|
|
92
|
+
* @provenance vibecommit-mcp src/read/envelope.ts — the one-text-part wire shape, retyped
|
|
93
|
+
* @provenance vibecommit-schema capture_turns + commit_file_attribution — MEASURED ABSENCE, cited
|
|
94
|
+
*/
|
|
95
|
+
import { type BlamedCommit, type EdgeGrade } from "../copy/index.js";
|
|
96
|
+
import { readWithSession } from "../oauth/signin.js";
|
|
97
|
+
import { type CommandContext } from "./context.js";
|
|
98
|
+
/**
|
|
99
|
+
* THE SHA-LENGTH RULE — read this before "fixing" the asymmetry.
|
|
100
|
+
*
|
|
101
|
+
* ⚠⚠ **THE TWO SHAS ON THE SQUASH SCREEN ARE ABBREVIATED DIFFERENTLY, ON
|
|
102
|
+
* PURPOSE, AND THE SCREEN SAYS SO** (D120 §1 as revised).
|
|
103
|
+
*
|
|
104
|
+
* The BLAMED sha renders at `ABBREV`, like every other sha this product prints:
|
|
105
|
+
* it is on the reader's ref, so seven hex resolves under `git show` — which is
|
|
106
|
+
* §10.6 E's stated purpose — and seven is also git's own dynamic `--short`
|
|
107
|
+
* length for repositories this size.
|
|
108
|
+
*
|
|
109
|
+
* **The RECORDED sha renders IN FULL, and the first ruling to abbreviate it was
|
|
110
|
+
* reversed.** This state exists for the squash case, and after a squash-merge
|
|
111
|
+
* WITH BRANCH DELETE the original commit is **not in the reader's clone at
|
|
112
|
+
* all** — measured, not assumed: cloned over the git transport, the original
|
|
113
|
+
* object is ABSENT and `git show` fails while the squashed commit resolves.
|
|
114
|
+
*
|
|
115
|
+
* ⚠ **AND THE MECHANISM IS NOT THE ONE THE RULING ASSUMED.** The full sha does
|
|
116
|
+
* NOT resolve locally either — if the object is gone, both forms fail. What the
|
|
117
|
+
* full value buys is that it stays a COMPLETE IDENTIFIER, resolvable off this
|
|
118
|
+
* machine: it can be looked up on the host, quoted in a record, or matched
|
|
119
|
+
* against another system. **A seven-character prefix is not addressable that
|
|
120
|
+
* way** — prefix resolution needs a store that already holds the object, and
|
|
121
|
+
* nothing here does. Truncating it would make the value unrecoverable
|
|
122
|
+
* anywhere, which is a claims problem rather than a formatting one.
|
|
123
|
+
*/
|
|
124
|
+
/**
|
|
125
|
+
* What `why` requires of `blame_commit`.
|
|
126
|
+
*
|
|
127
|
+
* ⚠ **UNRECONCILED AS OF `vibecommit-mcp` `eeda878`.** No tool of this name
|
|
128
|
+
* exists there: `src/read/envelope.ts:18` says *"`blame_commit` DOES NOT EXIST
|
|
129
|
+
* until W9"*, and `CR-085` — the last link of W9's serial mcp chain — is being
|
|
130
|
+
* written in parallel with this file, by a different builder, in a repo this
|
|
131
|
+
* task may not touch. This declaration was reported to the orchestrator so the
|
|
132
|
+
* two sides could be AGREED rather than discovered at merge.
|
|
133
|
+
*
|
|
134
|
+
* The transport contract it rides on IS settled and golden-tested on both sides
|
|
135
|
+
* (`vibecommit-mcp/src/read/envelope.ts`, `src/oauth/read.ts` here): exactly one
|
|
136
|
+
* `text` part whose text is `JSON.stringify(payload)`, consumed by a single
|
|
137
|
+
* `JSON.parse`, with an optional `isError: true` on the same shape.
|
|
138
|
+
*
|
|
139
|
+
* ### Request
|
|
140
|
+
*
|
|
141
|
+
* ```jsonc
|
|
142
|
+
* { "repo": "acohan/blog", // resolveRepoSlug — the X-Repo-Slug value
|
|
143
|
+
* "commit_sha": "<40 hex>", // what `git blame` returned, in full
|
|
144
|
+
* "file_path": "src/auth/session.ts" // repo-relative, as blame reported it
|
|
145
|
+
* }
|
|
146
|
+
* ```
|
|
147
|
+
*
|
|
148
|
+
* ### Response — a discriminated union on `state`, and every arm must render
|
|
149
|
+
*
|
|
150
|
+
* ```jsonc
|
|
151
|
+
* { "state": "cold_start" } // no capture for this repo, ever
|
|
152
|
+
* { "state": "no_edge" } // repo captured; this commit has none
|
|
153
|
+
* { "state": "squash_resolved", "recorded_sha": "<40 hex>" }
|
|
154
|
+
* { "state": "turns",
|
|
155
|
+
* "grades": ["derived"], // EVERY edge grade held; the floor governs
|
|
156
|
+
* "turns": [ { "position": 12, "tool": "Edit", // ⚠ string | null | ABSENT — see below
|
|
157
|
+
* "files": ["src/auth/session.ts"],
|
|
158
|
+
* "excerpt": ["…verbatim scrubbed…"],
|
|
159
|
+
* "agent_id": null, // ⚠ REQUIRED — see below
|
|
160
|
+
* "subagent_count": 2 } ], // ⚠ optional; no producer (CR-129)
|
|
161
|
+
* "window": { "first": 12, "last": 16, "total": 23 }, // server-computed; never counted here
|
|
162
|
+
* "files_without_turn": 2 }
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* ### ⚠ `tool` IS `string | null | ABSENT`, AND THE SERVER MUST NOT NAME THE HOLE
|
|
166
|
+
*
|
|
167
|
+
* **The wire carries the FACT; the client owns the WORDS** (D112 §3). A turn that
|
|
168
|
+
* ran no tool sends `null` or omits the key, and this module renders **no
|
|
169
|
+
* bracket and no space where the bracket was**. All three inputs are accepted
|
|
170
|
+
* and the two absence forms render identical bytes.
|
|
171
|
+
*
|
|
172
|
+
* ⚠ **A placeholder here is refused even when it is TRUE.** `"(none)"` is
|
|
173
|
+
* derivable from a turn actually fetched, so it is a claim about content rather
|
|
174
|
+
* than an invention — and it is still rejected, because a user-facing string
|
|
175
|
+
* minted in `vibecommit-mcp` bypasses **every copy gate this repo has**:
|
|
176
|
+
* `test/copy-claims.test.ts` maps exports of `strings.ts` and a wire string is
|
|
177
|
+
* in no export; `test/copy-width.test.ts` never measures it at 80 columns;
|
|
178
|
+
* `test/copy-inline-literals.test.ts` cannot see a literal that is not in this
|
|
179
|
+
* tree. `CR-090d` centralised this package's copy for exactly one reason — **the
|
|
180
|
+
* claims gate can only check copy it can find.**
|
|
181
|
+
*
|
|
182
|
+
* ⚠ **AND PRESENCE IS DELIBERATELY NOT REQUIRED.** Requiring a key the server
|
|
183
|
+
* could not satisfy is what broke this screen once already: `typeof value.tool
|
|
184
|
+
* !== "string"` rejected one turn, one rejected turn made the WHOLE document
|
|
185
|
+
* malformed, and `why` exited 1 on every payload (D110 §1). A required-but-
|
|
186
|
+
* nullable `tool` reproduces that defect one field over, so `parseTurn` accepts
|
|
187
|
+
* a turn object with no `tool` key at all.
|
|
188
|
+
*
|
|
189
|
+
* ### ⚠ THE ONE REQUIREMENT THIS FILE MAKES OF `CR-085`, AND WHY
|
|
190
|
+
*
|
|
191
|
+
* **`agent_id` must be PRESENT on every turn object — `null` for the main
|
|
192
|
+
* thread, a string for a delegated agent — and its absence must never be used to
|
|
193
|
+
* mean "main thread".** The schema comment on the column is explicit that *"NULL
|
|
194
|
+
* = the main thread"* and that the empty string is refused because it *"would
|
|
195
|
+
* render as an unnamed sub-agent — a plausible wrong answer rather than an
|
|
196
|
+
* error"*. The same reasoning one level up makes an ABSENT field and a null
|
|
197
|
+
* field different facts, so this module marks every turn `actor not recorded`
|
|
198
|
+
* unless the key was actually there.
|
|
199
|
+
*
|
|
200
|
+
* **Today `CR-085` cannot satisfy that**, because `commit_file_attribution` has
|
|
201
|
+
* no writer — which is why the absence branch is the one that ships, named on
|
|
202
|
+
* screen rather than hidden. `subagent_count` is worse: the column does not
|
|
203
|
+
* exist in any schema, so the second attribution row is implemented, exercised
|
|
204
|
+
* against a synthetic payload, and unreachable on the wire. Both are `CR-129`.
|
|
205
|
+
*/
|
|
206
|
+
export interface BlameCommitPayload {
|
|
207
|
+
readonly state: "cold_start" | "no_edge" | "edge_unreadable" | "squash_resolved" | "turns";
|
|
208
|
+
/**
|
|
209
|
+
* The sha the session was actually recorded against, when a rewrite was
|
|
210
|
+
* resolved. ⚠ **Legal on the `turns` branch too** (D112 §4) — that is the
|
|
211
|
+
* whole of `CR-138`: `squash_resolved` was TERMINAL, and `parseTarget`
|
|
212
|
+
* accepts only `<file>:<line>`, so the state handed the reader a sha and no
|
|
213
|
+
* verb that takes it. A dead end by construction. Carried on `turns`, both
|
|
214
|
+
* shas render AND the session is readable underneath them.
|
|
215
|
+
*/
|
|
216
|
+
readonly recordedSha: string | null;
|
|
217
|
+
/**
|
|
218
|
+
* HOW the pair was established, and it RENDERS rather than being carried.
|
|
219
|
+
* `null` when no rewrite was resolved — the ordinary case.
|
|
220
|
+
*/
|
|
221
|
+
readonly matchKind: MatchKind | null;
|
|
222
|
+
readonly grades: readonly EdgeGrade[];
|
|
223
|
+
readonly turns: readonly WhyTurn[];
|
|
224
|
+
readonly window: TurnWindow | null;
|
|
225
|
+
readonly filesWithoutTurn: number | null;
|
|
226
|
+
}
|
|
227
|
+
/** One turn, exactly as §10.4 draws its row. */
|
|
228
|
+
export interface WhyTurn {
|
|
229
|
+
readonly position: number;
|
|
230
|
+
/**
|
|
231
|
+
* The tool this turn ran, or `null` when none is recorded — and on this field
|
|
232
|
+
* ALONE, `null` and ABSENT are the same fact (D112 §3). Both render no
|
|
233
|
+
* bracket, which is the point: **no bracket makes no claim**, where an
|
|
234
|
+
* `[(none)]` would be words this repo's copy gates cannot see. The opposite
|
|
235
|
+
* of the `agent_id` rule below, deliberately, because `tool` has no reading
|
|
236
|
+
* under which absence could mean "the main thread".
|
|
237
|
+
*/
|
|
238
|
+
readonly tool: string | null;
|
|
239
|
+
readonly files: readonly string[];
|
|
240
|
+
readonly excerpt: readonly string[];
|
|
241
|
+
/**
|
|
242
|
+
* `null` = the main thread, a string = that sub-agent. ⚠ Meaningful ONLY when
|
|
243
|
+
* `agentIdPresent` — otherwise it is this module's default, not the server's
|
|
244
|
+
* answer, and rendering an unmarked turn from it would be the overclaim.
|
|
245
|
+
*/
|
|
246
|
+
readonly agentId: string | null;
|
|
247
|
+
readonly agentIdPresent: boolean;
|
|
248
|
+
/** §R3 row 2. NO PRODUCER EXISTS — `capture_turns.subagent_count` is not a column. */
|
|
249
|
+
readonly subagentCount: number | null;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* §R3's truncation notice. Every number is the server's (D60 §D1a).
|
|
253
|
+
*
|
|
254
|
+
* ⚠ `first` / `last`, NOT `from` / `to`, and the reason is a known scanner shape
|
|
255
|
+
* rather than taste. `test/provenance.test.ts`'s linkage wall is a REGEX over
|
|
256
|
+
* raw text — `/(?:from|import)\s*\(?\s*"([^"]+)"/` — so the token `from`
|
|
257
|
+
* immediately followed by a double quote reads as an import specifier escaping
|
|
258
|
+
* the package, ANYWHERE in the file including a JSON example in a docblock.
|
|
259
|
+
* `src/oauth/signin.ts` already carries the same warning for prose. A false RED
|
|
260
|
+
* is the more dangerous direction (it justifies "fixing" correct content and
|
|
261
|
+
* nothing later flags it), so the field is named around it and the reason is
|
|
262
|
+
* written down. `first`/`last` also says INCLUSIVE, which `from`/`to` does not.
|
|
263
|
+
*/
|
|
264
|
+
export interface TurnWindow {
|
|
265
|
+
readonly first: number;
|
|
266
|
+
readonly last: number;
|
|
267
|
+
readonly total: number;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* How a squash rewrite was established — `commit_sha_successors.match_kind`.
|
|
271
|
+
*
|
|
272
|
+
* ⚠ **THE SET IS EXACTLY TWO AND AN UNRECOGNISED VALUE IS MALFORMED, NEVER
|
|
273
|
+
* DEFAULTED.** Defaulting would silently downgrade a claim: `exact` came from a
|
|
274
|
+
* recorded successor row, `probable` from a patch-id match, and D67 forbids
|
|
275
|
+
* folding them. The far side freezes the same pair
|
|
276
|
+
* (`vibecommit-mcp/src/db/commit_sha_successors.ts`, `SUCCESSOR_MATCH_KINDS`),
|
|
277
|
+
* and its own comment says a third value is *"a `DECISIONS.md` question, not a
|
|
278
|
+
* widening"* — so a value we do not recognise means the two repos disagree,
|
|
279
|
+
* which is precisely the thing to refuse rather than paper over.
|
|
280
|
+
*/
|
|
281
|
+
export type MatchKind = "exact" | "probable";
|
|
282
|
+
/**
|
|
283
|
+
* ⛔ THE SKEW SENTINEL. Returned when the document is WELL-FORMED but names a
|
|
284
|
+
* `state` this build does not know — which means the server is newer than this
|
|
285
|
+
* client, not that the answer is broken.
|
|
286
|
+
*
|
|
287
|
+
* ⚠ A UNIQUE OBJECT, compared by identity. A string sentinel could collide with
|
|
288
|
+
* a future legitimate return, and `null` is already taken by "malformed" — the
|
|
289
|
+
* two must stay distinguishable, because they send the user to opposite
|
|
290
|
+
* remedies: "report this" versus "update the plugin".
|
|
291
|
+
*/
|
|
292
|
+
export declare const PAYLOAD_VERSION_SKEW: Readonly<{
|
|
293
|
+
skew: true;
|
|
294
|
+
}>;
|
|
295
|
+
export type PayloadVersionSkew = typeof PAYLOAD_VERSION_SKEW;
|
|
296
|
+
/**
|
|
297
|
+
* ⚠ A TYPE GUARD RATHER THAN A BARE `===`. TypeScript does not narrow a union
|
|
298
|
+
* on object identity — the two members share no discriminant literal — so
|
|
299
|
+
* `payload === PAYLOAD_VERSION_SKEW` compiles but leaves `payload` wide, and
|
|
300
|
+
* the next line passing it to a renderer fails to typecheck. The guard makes
|
|
301
|
+
* the narrowing explicit and keeps the identity comparison as the actual test.
|
|
302
|
+
*/
|
|
303
|
+
export declare function isVersionSkew(p: BlameCommitPayload | PayloadVersionSkew): p is PayloadVersionSkew;
|
|
304
|
+
/**
|
|
305
|
+
* The whole document.
|
|
306
|
+
*
|
|
307
|
+
* - `null` — MALFORMED. The server sent something that is not a valid document
|
|
308
|
+
* in any version, and the remedy is to report it.
|
|
309
|
+
* - `PAYLOAD_VERSION_SKEW` — WELL-FORMED, UNKNOWN STATE. The server is newer
|
|
310
|
+
* than this client and the remedy is to upgrade. ⛔ Added by `U1`
|
|
311
|
+
* (2026-08-30): before it, this case returned `null` and told the user a
|
|
312
|
+
* perfectly good answer was broken. See `WHY.skewWhat` for why that mattered
|
|
313
|
+
* more here than in most protocols.
|
|
314
|
+
* - a payload — parsed.
|
|
315
|
+
*/
|
|
316
|
+
export declare function parseBlameCommitPayload(document: unknown): BlameCommitPayload | PayloadVersionSkew | null;
|
|
317
|
+
export interface BlameResult {
|
|
318
|
+
readonly sha: string;
|
|
319
|
+
readonly commit: BlamedCommit;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* `git blame` one line, via `--porcelain`.
|
|
323
|
+
*
|
|
324
|
+
* Porcelain rather than the human format: the human format embeds the author's
|
|
325
|
+
* NAME, which can contain spaces, parentheses and digits, so every field after
|
|
326
|
+
* it is positional guesswork. Porcelain puts the SHA first on its own line and
|
|
327
|
+
* the timestamp on a labelled one.
|
|
328
|
+
*/
|
|
329
|
+
export declare function blameLine(dir: string, file: string, line: number): BlameResult | null;
|
|
330
|
+
/** Exported so the parser can be driven against captured git output in the suite. */
|
|
331
|
+
export declare function parseBlamePorcelain(out: string): BlameResult | null;
|
|
332
|
+
export interface WhyTarget {
|
|
333
|
+
readonly file: string;
|
|
334
|
+
readonly line: number;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* `<file>:<line>`.
|
|
338
|
+
*
|
|
339
|
+
* `lastIndexOf` rather than a split, because a path may legitimately contain a
|
|
340
|
+
* colon and only the last one can be the line number. The tail must be all
|
|
341
|
+
* digits and at least 1 — `file:0` and `file:-3` are not lines, and `file:1e3`
|
|
342
|
+
* is the kind of input `parseInt` would quietly accept as 1.
|
|
343
|
+
*/
|
|
344
|
+
export declare function parseTarget(argv: readonly string[]): WhyTarget | null;
|
|
345
|
+
export interface WhyDeps {
|
|
346
|
+
/** Injected so the suite can drive every arm without a server — there is none. */
|
|
347
|
+
readonly read?: typeof readWithSession;
|
|
348
|
+
}
|
|
349
|
+
export declare function why(ctx: CommandContext, argv: readonly string[], deps?: WhyDeps): Promise<number>;
|