carrick 0.3.82 → 0.3.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -30
- package/bin/carrick.mjs +45 -2
- package/dist/contract.d.ts +17 -0
- package/dist/contract.js.map +1 -1
- package/dist/hook/apply-patch.d.ts +13 -0
- package/dist/hook/apply-patch.js +100 -0
- package/dist/hook/apply-patch.js.map +1 -0
- package/dist/hook/post-edit.d.ts +30 -1
- package/dist/hook/post-edit.js +95 -24
- package/dist/hook/post-edit.js.map +1 -1
- package/dist/hook/reuse.d.ts +97 -0
- package/dist/hook/reuse.js +245 -0
- package/dist/hook/reuse.js.map +1 -0
- package/dist/hook/stop.d.ts +3 -0
- package/dist/hook/stop.js +76 -0
- package/dist/hook/stop.js.map +1 -0
- package/dist/hook/user-prompt.d.ts +9 -0
- package/dist/hook/user-prompt.js +79 -0
- package/dist/hook/user-prompt.js.map +1 -0
- package/dist/init/codex.d.ts +51 -0
- package/dist/init/codex.js +167 -0
- package/dist/init/codex.js.map +1 -0
- package/dist/init/connect.d.ts +13 -0
- package/dist/init/connect.js +21 -15
- package/dist/init/connect.js.map +1 -1
- package/dist/init/doctor.d.ts +57 -0
- package/dist/init/doctor.js +132 -3
- package/dist/init/doctor.js.map +1 -1
- package/dist/init/files.d.ts +15 -0
- package/dist/init/files.js +20 -1
- package/dist/init/files.js.map +1 -1
- package/dist/init/hosted.d.ts +30 -5
- package/dist/init/hosted.js +75 -9
- package/dist/init/hosted.js.map +1 -1
- package/dist/init/mcp.d.ts +45 -30
- package/dist/init/mcp.js +47 -88
- package/dist/init/mcp.js.map +1 -1
- package/dist/init/outdated.d.ts +54 -0
- package/dist/init/outdated.js +175 -0
- package/dist/init/outdated.js.map +1 -0
- package/dist/init/output.d.ts +72 -3
- package/dist/init/output.js +187 -23
- package/dist/init/output.js.map +1 -1
- package/dist/init/projects.d.ts +27 -13
- package/dist/init/projects.js +48 -50
- package/dist/init/projects.js.map +1 -1
- package/dist/init/remove.d.ts +0 -2
- package/dist/init/remove.js +61 -15
- package/dist/init/remove.js.map +1 -1
- package/dist/init/repos.d.ts +34 -0
- package/dist/init/repos.js +77 -0
- package/dist/init/repos.js.map +1 -1
- package/dist/init/run.d.ts +148 -26
- package/dist/init/run.js +577 -125
- package/dist/init/run.js.map +1 -1
- package/dist/init/settings.d.ts +20 -0
- package/dist/init/settings.js +42 -4
- package/dist/init/settings.js.map +1 -1
- package/dist/init/task-skills.d.ts +27 -0
- package/dist/init/task-skills.js +64 -1
- package/dist/init/task-skills.js.map +1 -1
- package/dist/init/workspace-file.d.ts +73 -0
- package/dist/init/workspace-file.js +173 -0
- package/dist/init/workspace-file.js.map +1 -0
- package/dist/render.d.ts +16 -0
- package/dist/render.js +57 -6
- package/dist/render.js.map +1 -1
- package/package.json +7 -6
- package/plugin/hooks/hooks.json +11 -0
- package/sidecar/dist/src/capture/api.d.ts +23 -1
- package/sidecar/dist/src/capture/check-classify.d.ts +10 -1
- package/sidecar/dist/src/capture/check-classify.js +96 -9
- package/sidecar/dist/src/capture/check-deep.d.ts +16 -4
- package/sidecar/dist/src/capture/check-deep.js +21 -17
- package/sidecar/dist/src/capture/check-fields.d.ts +112 -0
- package/sidecar/dist/src/capture/check-fields.js +311 -0
- package/sidecar/dist/src/capture/check-probe.d.ts +21 -1
- package/sidecar/dist/src/capture/check-probe.js +39 -0
- package/sidecar/dist/src/capture/check.js +14 -2
- package/sidecar/dist/src/capture/index.js +6 -1
- package/sidecar/dist/src/capture/self-check.d.ts +6 -1
- package/sidecar/dist/src/capture/self-check.js +65 -11
- package/sidecar/dist/src/validators.d.ts +40 -40
- package/sidecar/dist/src/validators.js +6 -1
- package/templates/skills/carrick-census.md +18 -9
- package/templates/skills/carrick-drift.md +7 -1
- package/templates/skills/carrick-impact.md +3 -1
- package/templates/skills/carrick-reuse.md +43 -26
package/README.md
CHANGED
|
@@ -25,30 +25,51 @@ the hooks deliver, the CI check and a `carrick.json` written by hand, is in the
|
|
|
25
25
|
agent's shell, a pipe — the same lines are written as plain text, with no colour
|
|
26
26
|
and no spinner.
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
`init` runs in this order, and nothing is written — on this machine or in
|
|
29
|
+
Carrick — until you accept what it proposes:
|
|
30
|
+
|
|
31
|
+
1. **The repos this install covers.** In a folder of sibling repos it asks
|
|
32
|
+
which ones, with every repo chosen to begin with. A folder routinely holds a
|
|
33
|
+
repo that should not be indexed, and a repo you leave out gets no proposal
|
|
34
|
+
entry, no project assignment and no connection. Without a terminal, name
|
|
35
|
+
them: `--repo owner/repo`, repeated or comma-separated. A folder of repos
|
|
36
|
+
with neither a terminal nor `--repo` stops there, having written nothing;
|
|
37
|
+
add `--yes` to cover all of them. A single repository is not a choice and is
|
|
38
|
+
never asked about.
|
|
39
|
+
2. **The project.** Which project the selected repos belong in.
|
|
40
|
+
3. **The proposal**, which names the packages found, the repos covered, any
|
|
41
|
+
project to create, and any repo that would move out of the project it is in
|
|
42
|
+
now. Answering no, or ending any question with Ctrl-C, ends the run with
|
|
43
|
+
nothing written.
|
|
44
|
+
|
|
45
|
+
Existing users can name the project on the command line:
|
|
30
46
|
|
|
31
47
|
```
|
|
32
|
-
carrick init --project payments
|
|
48
|
+
carrick init --project payments --allow-move
|
|
33
49
|
```
|
|
34
50
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
`--project` puts the selected repos in that project, creating it from the
|
|
52
|
+
terminal where the API allows that and printing the project link where it does
|
|
53
|
+
not. A repo that is already in another project is **moved** out of it, which
|
|
54
|
+
changes what every agent querying either project can see, so the move is named
|
|
55
|
+
in the proposal with the project it comes out of and asked about separately.
|
|
56
|
+
`--yes` does not grant it: `--allow-move` does, and without either the run
|
|
57
|
+
stops before anything is moved.
|
|
58
|
+
|
|
59
|
+
Connected repos are placed from the terminal, so the GitHub App grant is the
|
|
60
|
+
only browser step; where the API has no assignment action, the command prints
|
|
61
|
+
the repo-assignment link and, in an interactive terminal, opens the page.
|
|
62
|
+
Either way the claim comes from a read. The CLI reads `resolve-repos` until
|
|
63
|
+
every covered repo reports `project_slug: "payments"`, and a repo connected to
|
|
44
64
|
another project remains pending. A target it cannot meet does not stop the run.
|
|
45
65
|
The hooks, the MCP connection and the proposal are written, and the command
|
|
46
66
|
says which browser steps are left.
|
|
47
67
|
|
|
48
68
|
Without `--project` the project step still runs: the command takes the project
|
|
49
69
|
the repos are already in, and otherwise lists the workspace's projects for you
|
|
50
|
-
to name one
|
|
51
|
-
|
|
70
|
+
to name one. An empty answer leaves the step to the browser. Every project is
|
|
71
|
+
printed as the dashboard shows it, display name and slug both — `Payments
|
|
72
|
+
(payments)`.
|
|
52
73
|
|
|
53
74
|
Each repository is identified by its `origin` remote. A remote written through
|
|
54
75
|
a per-account SSH host alias (`git@github.com-work:owner/repo.git`) is resolved
|
|
@@ -56,7 +77,8 @@ with `ssh -G`, so an alias whose `HostName` is `github.com` is an ordinary
|
|
|
56
77
|
GitHub repository here. When a repository still names none, `init` says which
|
|
57
78
|
one it was and what it read, and leaves that repository out of the project and
|
|
58
79
|
connection steps rather than dropping it quietly. `carrick init --repo
|
|
59
|
-
owner/repo` names the repository in that case
|
|
80
|
+
owner/repo` names the repository in that case: a `--repo` value that matches no
|
|
81
|
+
repo in the folder attaches to the one repo there that has no identity.
|
|
60
82
|
|
|
61
83
|
Credentials live in `$XDG_CONFIG_HOME/carrick/credentials.json`, falling back
|
|
62
84
|
to `~/.config/carrick/` on macOS and Linux or `%APPDATA%\carrick\` on Windows.
|
|
@@ -79,9 +101,14 @@ exists. It is the only scan a first run makes.
|
|
|
79
101
|
|
|
80
102
|
Workspace detection handles a repo root or immediate sibling repositories.
|
|
81
103
|
An optional `carrick-workspace.json` adds paths through `repos` and removes
|
|
82
|
-
directory names through `exclude
|
|
83
|
-
|
|
84
|
-
|
|
104
|
+
directory names through `exclude`. Where you leave a repo out of the selection,
|
|
105
|
+
init writes that name into `exclude` and records it under a `carrick` key
|
|
106
|
+
beside it: every later command reads the same answer, so the scans, the editor
|
|
107
|
+
hooks and the next `init` all leave that repo alone, and `carrick remove` takes
|
|
108
|
+
back the names init added and nothing you wrote yourself. `--repo` naming an
|
|
109
|
+
excluded repo is refused and says which file excludes it. `carrick derive
|
|
110
|
+
--workspace . --json` previews the Rust proposal without writing files or
|
|
111
|
+
scanning.
|
|
85
112
|
|
|
86
113
|
Deno projects use their existing `deno.json` or `deno.jsonc` and require Deno
|
|
87
114
|
2.9.4 or newer on PATH. Before local indexing, prepare their dependencies with
|
|
@@ -114,7 +141,7 @@ types in a Node process.
|
|
|
114
141
|
|---|---|
|
|
115
142
|
| `carrick login` | Authorise a Carrick workspace in the browser, or verify `CARRICK_TOKEN` |
|
|
116
143
|
| `carrick logout` | Remove the saved local credential |
|
|
117
|
-
| `carrick init [--project SLUG] [--
|
|
144
|
+
| `carrick init [--repo OWNER/REPO]... [--project SLUG] [--allow-move]` | The repos this install covers, the project, the service proposal in `.carrick/`, the agent hooks, the MCP connection, and the prompt that writes `carrick.json` |
|
|
118
145
|
| `carrick doctor` | Re-check that setup: the declared paths, the CI workflow against the current template, the hooks, the MCP connection and how far the index is behind |
|
|
119
146
|
| `carrick remove [--keep-login]` | Undo all of that on this machine, and list the files the scaffold added to the repository |
|
|
120
147
|
| `carrick index` | Derive the workspace, apply optional repo overrides and write `.carrick/`, with Carrick classifying what the deterministic passes could not |
|
|
@@ -180,17 +207,22 @@ runs on the local machine.
|
|
|
180
207
|
Hosted queries cover connected, indexed repositories in the selected Carrick
|
|
181
208
|
project, including repositories absent from this disk, using their most recent
|
|
182
209
|
default-branch indexes. They are available over MCP, and `carrick init`
|
|
183
|
-
connects
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
210
|
+
connects Claude Code through `claude mcp add`. Cursor, Windsurf and VS Code
|
|
211
|
+
keep their configuration outside the workspace, so each is asked about by the
|
|
212
|
+
path of its file: the terminal offers the ones whose configuration directory
|
|
213
|
+
exists, ticked where the editor itself is detected, and a run with no terminal
|
|
214
|
+
writes one only when `--mcp EDITOR` names it. `--yes` does not cover them. A
|
|
215
|
+
`carrick` server is added to the file and every other entry in it is left
|
|
216
|
+
alone.
|
|
217
|
+
An entry init writes carries an `X-Carrick-Install-Id` header: a UUID generated
|
|
218
|
+
once, kept in `~/.carrick/install-id`, and sent with every MCP call so a slow
|
|
219
|
+
session can be told apart from a busy one. It says nothing about the machine or
|
|
220
|
+
the person — `carrick remove` deletes it, and the next `carrick init` mints
|
|
221
|
+
another. A `carrick` entry that is already there is left exactly as it is, with
|
|
222
|
+
or without the header: the header is part of what a client keys its sign-in on,
|
|
223
|
+
and nothing a user sees depends on it, so neither `init` nor `doctor` mentions
|
|
224
|
+
an entry that has none.
|
|
225
|
+
A machine with no client on it is given the line to run, with the
|
|
194
226
|
id already in it:
|
|
195
227
|
|
|
196
228
|
```
|
package/bin/carrick.mjs
CHANGED
|
@@ -38,11 +38,50 @@ if (nodeMajor() < NODE_FLOOR) {
|
|
|
38
38
|
const HOOKS = {
|
|
39
39
|
"post-edit": "../dist/hook/post-edit.js",
|
|
40
40
|
"session-start": "../dist/hook/session-start.js",
|
|
41
|
+
stop: "../dist/hook/stop.js",
|
|
42
|
+
"user-prompt": "../dist/hook/user-prompt.js",
|
|
41
43
|
};
|
|
42
44
|
|
|
43
45
|
const argv = process.argv.slice(2);
|
|
44
46
|
const [command, ...rest] = argv;
|
|
45
47
|
|
|
48
|
+
/**
|
|
49
|
+
* The commands that say nothing about the state of the install (carrick#1333).
|
|
50
|
+
*
|
|
51
|
+
* `hook` and `lsp` are protocol channels — a line of prose on either is a line
|
|
52
|
+
* an agent reads as data — and `init`, `doctor` and `remove` are the commands
|
|
53
|
+
* that write, audit and undo the very files the notice would be about, so each
|
|
54
|
+
* of them says it better in its own words.
|
|
55
|
+
*/
|
|
56
|
+
const NO_NOTICE = new Set(["hook", "lsp", "init", "doctor", "remove"]);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* One line a day, on stderr, naming what an upgrade left behind here.
|
|
60
|
+
*
|
|
61
|
+
* `npm install -g carrick@latest` changes the binary and nothing else, so a
|
|
62
|
+
* repo keeps the hook entries and skill bodies of whichever version first ran
|
|
63
|
+
* `carrick init` in it. Nothing re-reads them, and nobody is told.
|
|
64
|
+
*
|
|
65
|
+
* It runs before the command, never instead of it, and cannot fail one: a
|
|
66
|
+
* throw, a workspace Carrick was never set up in, or an install that is
|
|
67
|
+
* current all leave it silent.
|
|
68
|
+
*/
|
|
69
|
+
async function refreshNoticeLine() {
|
|
70
|
+
if (command === undefined || NO_NOTICE.has(command)) return;
|
|
71
|
+
try {
|
|
72
|
+
const { initialisedRoot, refreshNotice } = await import("../dist/init/outdated.js");
|
|
73
|
+
const root = initialisedRoot(process.cwd());
|
|
74
|
+
if (root === null) return;
|
|
75
|
+
const line = refreshNotice(root);
|
|
76
|
+
if (line) process.stderr.write(`carrick: ${line}\n`);
|
|
77
|
+
} catch {
|
|
78
|
+
// A notice about the install must never be the reason a command did not
|
|
79
|
+
// run.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await refreshNoticeLine();
|
|
84
|
+
|
|
46
85
|
/**
|
|
47
86
|
* Run a build of the index and render it (carrick#1315).
|
|
48
87
|
*
|
|
@@ -162,9 +201,13 @@ function extraHelp() {
|
|
|
162
201
|
"INTEGRATION:",
|
|
163
202
|
" lsp --stdio the language server, for an editor or an agent",
|
|
164
203
|
" that speaks LSP",
|
|
165
|
-
" hook post-edit
|
|
166
|
-
" payload on stdin)",
|
|
204
|
+
" hook post-edit PostToolUse hook for either host (reads the",
|
|
205
|
+
" tool payload on stdin)",
|
|
167
206
|
" hook session-start Claude Code SessionStart hook",
|
|
207
|
+
" hook stop Claude Code Stop hook (one reuse nudge per",
|
|
208
|
+
" task, silent when the task added no function)",
|
|
209
|
+
" hook user-prompt Codex UserPromptSubmit hook (the same nudge,",
|
|
210
|
+
" delivered on the next prompt)",
|
|
168
211
|
" templates <name> print a file to add to a repo: workflow, or",
|
|
169
212
|
" carrick.json",
|
|
170
213
|
" --version the version of this package",
|
package/dist/contract.d.ts
CHANGED
|
@@ -144,6 +144,23 @@ export type Recheck = {
|
|
|
144
144
|
stale_since?: string;
|
|
145
145
|
/** Why the re-check did not run. Only on a `none`. */
|
|
146
146
|
reason?: string;
|
|
147
|
+
/**
|
|
148
|
+
* Functions the re-extracted file declares that the index does not hold
|
|
149
|
+
* under that name (carrick#1330). Absent on a `none`, which extracted
|
|
150
|
+
* nothing to compare, and absent when the edit added no function.
|
|
151
|
+
*
|
|
152
|
+
* A name comparison against `index_commit`, and both halves matter: the
|
|
153
|
+
* index holds no body hash, so a renamed function reads as new, and the
|
|
154
|
+
* commit is the one the last scan of that repo ran on.
|
|
155
|
+
*/
|
|
156
|
+
new_functions?: NewFunction[];
|
|
157
|
+
};
|
|
158
|
+
/** One function the working tree declares and the index does not. */
|
|
159
|
+
export type NewFunction = {
|
|
160
|
+
/** As the source spells it: `name`, or `Class.member`. */
|
|
161
|
+
name: string;
|
|
162
|
+
/** Where it starts, so a reader can open it without searching. */
|
|
163
|
+
line: number;
|
|
147
164
|
};
|
|
148
165
|
export declare const SCHEMA = "carrick.check/0";
|
|
149
166
|
export declare const STATUS_SCHEMA = "carrick.status/0";
|
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,yDAAyD;AACzD,+EAA+E;AAC/E,4DAA4D;AAC5D,6EAA6E;AAC7E,6EAA6E;AAC7E,+EAA+E;AAC/E,0CAA0C;
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Whether a string is an `apply_patch` envelope at all. */
|
|
2
|
+
export declare function looksLikePatch(text: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* The paths a patch leaves on disk, in the order it names them.
|
|
5
|
+
*
|
|
6
|
+
* Relative to the cwd the tool ran in, exactly as the patch spells them, and
|
|
7
|
+
* deduplicated: a caller turns them into workspace-relative paths because only
|
|
8
|
+
* it knows the root.
|
|
9
|
+
*
|
|
10
|
+
* Anything that is not a patch gives an empty list, so a caller can hand this
|
|
11
|
+
* whatever a `command` field held without asking first.
|
|
12
|
+
*/
|
|
13
|
+
export declare function patchedFiles(text: string): string[];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// The files one `apply_patch` call leaves on disk (carrick#1335).
|
|
2
|
+
//
|
|
3
|
+
// Codex has no `Edit`/`Write` tool with a `file_path`. It edits through
|
|
4
|
+
// `apply_patch`, whose PostToolUse payload is
|
|
5
|
+
// `tool_input: { "command": "<the patch text>" }`
|
|
6
|
+
// (`codex-rs/core/src/tools/handlers/apply_patch.rs`), and one patch can add,
|
|
7
|
+
// update, delete and rename several files at once. So the recording half of
|
|
8
|
+
// the reuse nudge needs this: the patch text in, the paths worth re-checking
|
|
9
|
+
// out.
|
|
10
|
+
//
|
|
11
|
+
// The grammar is four header lines, each at the start of its own line, inside
|
|
12
|
+
// a `*** Begin Patch` / `*** End Patch` envelope (`codex-rs/apply-patch`):
|
|
13
|
+
//
|
|
14
|
+
// *** Add File: <path>
|
|
15
|
+
// *** Update File: <path>
|
|
16
|
+
// *** Delete File: <path>
|
|
17
|
+
// *** Move to: <path> (only ever after an Update File)
|
|
18
|
+
//
|
|
19
|
+
// Body lines carry a `+`, `-` or space prefix, so a line beginning `*** ` is a
|
|
20
|
+
// header and nothing else. `*** End of File` is a marker, not a path, and is
|
|
21
|
+
// ignored with everything else that is not one of the four.
|
|
22
|
+
//
|
|
23
|
+
// Two of the four are not simply "a file that changed":
|
|
24
|
+
//
|
|
25
|
+
// * `Delete File` names a path that is GONE once the patch applies. Re-checking
|
|
26
|
+
// it asks the CLI about a file that is not there, so it is dropped here
|
|
27
|
+
// rather than filtered later.
|
|
28
|
+
// * `Move to` renames the `Update File` above it. The file the index should be
|
|
29
|
+
// compared against is the destination, so the destination replaces the source
|
|
30
|
+
// rather than joining it.
|
|
31
|
+
//
|
|
32
|
+
// Reference: `docs/reference/task-skills.md`, "The reuse nudge".
|
|
33
|
+
/** The header that opens a patch, used to tell a patch from any other string. */
|
|
34
|
+
const ENVELOPE = "*** Begin Patch";
|
|
35
|
+
const HEADERS = [
|
|
36
|
+
{ marker: "*** Add File: ", kind: "add" },
|
|
37
|
+
{ marker: "*** Update File: ", kind: "update" },
|
|
38
|
+
{ marker: "*** Delete File: ", kind: "delete" },
|
|
39
|
+
{ marker: "*** Move to: ", kind: "move" },
|
|
40
|
+
];
|
|
41
|
+
function header(line) {
|
|
42
|
+
for (const candidate of HEADERS) {
|
|
43
|
+
if (line.startsWith(candidate.marker)) {
|
|
44
|
+
const path = line.slice(candidate.marker.length).trim();
|
|
45
|
+
if (path !== "")
|
|
46
|
+
return { kind: candidate.kind, path };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/** Whether a string is an `apply_patch` envelope at all. */
|
|
52
|
+
export function looksLikePatch(text) {
|
|
53
|
+
return text.includes(ENVELOPE);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The paths a patch leaves on disk, in the order it names them.
|
|
57
|
+
*
|
|
58
|
+
* Relative to the cwd the tool ran in, exactly as the patch spells them, and
|
|
59
|
+
* deduplicated: a caller turns them into workspace-relative paths because only
|
|
60
|
+
* it knows the root.
|
|
61
|
+
*
|
|
62
|
+
* Anything that is not a patch gives an empty list, so a caller can hand this
|
|
63
|
+
* whatever a `command` field held without asking first.
|
|
64
|
+
*/
|
|
65
|
+
export function patchedFiles(text) {
|
|
66
|
+
if (!looksLikePatch(text))
|
|
67
|
+
return [];
|
|
68
|
+
const files = [];
|
|
69
|
+
// Where the last `Update File` put its path, so a `Move to` under it replaces
|
|
70
|
+
// that entry instead of adding a second one for a file that no longer exists.
|
|
71
|
+
let lastUpdate = null;
|
|
72
|
+
for (const line of text.split("\n")) {
|
|
73
|
+
const found = header(line.trimEnd());
|
|
74
|
+
if (found === null)
|
|
75
|
+
continue;
|
|
76
|
+
switch (found.kind) {
|
|
77
|
+
case "delete":
|
|
78
|
+
lastUpdate = null;
|
|
79
|
+
break;
|
|
80
|
+
case "move":
|
|
81
|
+
if (lastUpdate === null) {
|
|
82
|
+
files.push(found.path);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
files[lastUpdate] = found.path;
|
|
86
|
+
}
|
|
87
|
+
lastUpdate = null;
|
|
88
|
+
break;
|
|
89
|
+
case "update":
|
|
90
|
+
lastUpdate = files.push(found.path) - 1;
|
|
91
|
+
break;
|
|
92
|
+
case "add":
|
|
93
|
+
files.push(found.path);
|
|
94
|
+
lastUpdate = null;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return [...new Set(files)];
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=apply-patch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-patch.js","sourceRoot":"","sources":["../../src/hook/apply-patch.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,EAAE;AACF,wEAAwE;AACxE,8CAA8C;AAC9C,kDAAkD;AAClD,8EAA8E;AAC9E,4EAA4E;AAC5E,6EAA6E;AAC7E,OAAO;AACP,EAAE;AACF,8EAA8E;AAC9E,2EAA2E;AAC3E,EAAE;AACF,2BAA2B;AAC3B,8BAA8B;AAC9B,8BAA8B;AAC9B,kEAAkE;AAClE,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,4DAA4D;AAC5D,EAAE;AACF,wDAAwD;AACxD,EAAE;AACF,gFAAgF;AAChF,0EAA0E;AAC1E,gCAAgC;AAChC,+EAA+E;AAC/E,gFAAgF;AAChF,4BAA4B;AAC5B,EAAE;AACF,iEAAiE;AAEjE,iFAAiF;AACjF,MAAM,QAAQ,GAAG,iBAAiB,CAAC;AAEnC,MAAM,OAAO,GAAG;IACd,EAAE,MAAM,EAAE,gBAAgB,EAAE,IAAI,EAAE,KAAK,EAAE;IACzC,EAAE,MAAM,EAAE,mBAAmB,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC/C,EAAE,MAAM,EAAE,mBAAmB,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC/C,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE;CACjC,CAAC;AAIX,SAAS,MAAM,CAAC,IAAY;IAC1B,KAAK,MAAM,SAAS,IAAI,OAAO,EAAE,CAAC;QAChC,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YACxD,IAAI,IAAI,KAAK,EAAE;gBAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;QACzD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,8EAA8E;IAC9E,8EAA8E;IAC9E,IAAI,UAAU,GAAkB,IAAI,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QACrC,IAAI,KAAK,KAAK,IAAI;YAAE,SAAS;QAC7B,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,QAAQ;gBACX,UAAU,GAAG,IAAI,CAAC;gBAClB,MAAM;YACR,KAAK,MAAM;gBACT,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;oBACxB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACzB,CAAC;qBAAM,CAAC;oBACN,KAAK,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC;gBACjC,CAAC;gBACD,UAAU,GAAG,IAAI,CAAC;gBAClB,MAAM;YACR,KAAK,QAAQ;gBACX,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACxC,MAAM;YACR,KAAK,KAAK;gBACR,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACvB,UAAU,GAAG,IAAI,CAAC;gBAClB,MAAM;QACV,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;AAC7B,CAAC"}
|
package/dist/hook/post-edit.d.ts
CHANGED
|
@@ -1,13 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import type { CheckResult } from "../contract.ts";
|
|
3
|
+
import type { NewFunction } from "./reuse.ts";
|
|
2
4
|
type Payload = {
|
|
3
5
|
cwd?: string;
|
|
6
|
+
session_id?: string;
|
|
4
7
|
tool_input?: {
|
|
5
8
|
file_path?: string;
|
|
6
9
|
filePath?: string;
|
|
7
10
|
edits?: Array<{
|
|
8
11
|
file_path?: string;
|
|
9
12
|
}>;
|
|
13
|
+
/** Codex's `apply_patch`: the patch text itself. */
|
|
14
|
+
command?: unknown;
|
|
10
15
|
};
|
|
11
16
|
};
|
|
12
|
-
|
|
17
|
+
/**
|
|
18
|
+
* What the re-check said this file declares and the index does not
|
|
19
|
+
* (carrick#1330).
|
|
20
|
+
*
|
|
21
|
+
* Nothing is printed about it here. This hook fires on every edit and most
|
|
22
|
+
* edits add no function; the one line about the ones that did belongs at the
|
|
23
|
+
* end of the task, where it costs one model turn instead of one per edit. All
|
|
24
|
+
* this does is put the names somewhere the Stop hook can find them.
|
|
25
|
+
*/
|
|
26
|
+
export declare function newFunctions(result: CheckResult, file: string): NewFunction[];
|
|
27
|
+
/**
|
|
28
|
+
* Every file one tool call left on disk, whichever host made it.
|
|
29
|
+
*
|
|
30
|
+
* A named file wins where there is one: that is Claude Code, and its path is
|
|
31
|
+
* absolute. Otherwise the `command` field is read as a patch, which is Codex's
|
|
32
|
+
* `apply_patch` — its paths are relative to the cwd the tool ran in, and a
|
|
33
|
+
* string that is not a patch gives nothing, so nothing here has to ask which
|
|
34
|
+
* host is calling.
|
|
35
|
+
*
|
|
36
|
+
* A shell command that is not a patch reaches the same branch and yields an
|
|
37
|
+
* empty list, which is why this does not test `tool_name`: the patch text
|
|
38
|
+
* recognises itself, and an `apply_patch` run through a shell is recorded on
|
|
39
|
+
* the same terms as one run as a tool.
|
|
40
|
+
*/
|
|
41
|
+
export declare function filesFromPayload(payload: Payload): string[];
|
|
13
42
|
export {};
|
package/dist/hook/post-edit.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// PostToolUse hook for
|
|
2
|
+
// PostToolUse hook for a file edit, on either host.
|
|
3
3
|
//
|
|
4
4
|
// Reads the tool payload on stdin, runs `carrick check <file> --json` from the
|
|
5
|
-
// workspace root and prints the verdicts as `additionalContext`, which
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// workspace root and prints the verdicts as `additionalContext`, which the host
|
|
6
|
+
// attaches to the tool result. Attaching to the result rather than to the next
|
|
7
|
+
// turn is why this channel has no "the run ended on the edit" hole.
|
|
8
|
+
//
|
|
9
|
+
// Two payload shapes reach it, because the two hosts edit differently
|
|
10
|
+
// (carrick#1335). Claude Code's `Edit`, `Write` and `MultiEdit` name one file
|
|
11
|
+
// in `tool_input.file_path`. Codex's `apply_patch` carries a patch in
|
|
12
|
+
// `tool_input.command`, and one patch can touch several files — so the file is
|
|
13
|
+
// a list here, checked in the order the payload names them.
|
|
8
14
|
//
|
|
9
15
|
// It exits 0 whatever happens. An edit never fails because Carrick had nothing
|
|
10
16
|
// to say, could not find its binary, or timed out.
|
|
@@ -14,12 +20,47 @@ import { resolveChannel } from "../channel.js";
|
|
|
14
20
|
import { createLogger } from "../log.js";
|
|
15
21
|
import { renderPostToolUse } from "../render.js";
|
|
16
22
|
import { resolveRoot, rootNote } from "../root.js";
|
|
23
|
+
import { patchedFiles } from "./apply-patch.js";
|
|
24
|
+
import { record } from "./reuse.js";
|
|
17
25
|
const log = createLogger("carrick-hook");
|
|
18
26
|
/** The files the index has rows for. */
|
|
19
27
|
const CHECKED = /\.(ts|tsx|mts|cts)$/;
|
|
20
|
-
|
|
28
|
+
/**
|
|
29
|
+
* What the re-check said this file declares and the index does not
|
|
30
|
+
* (carrick#1330).
|
|
31
|
+
*
|
|
32
|
+
* Nothing is printed about it here. This hook fires on every edit and most
|
|
33
|
+
* edits add no function; the one line about the ones that did belongs at the
|
|
34
|
+
* end of the task, where it costs one model turn instead of one per edit. All
|
|
35
|
+
* this does is put the names somewhere the Stop hook can find them.
|
|
36
|
+
*/
|
|
37
|
+
export function newFunctions(result, file) {
|
|
38
|
+
return (result.recheck?.new_functions ?? []).map((entry) => ({
|
|
39
|
+
name: entry.name,
|
|
40
|
+
file: result.file ?? file,
|
|
41
|
+
indexCommit: result.index_commit ?? "",
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Every file one tool call left on disk, whichever host made it.
|
|
46
|
+
*
|
|
47
|
+
* A named file wins where there is one: that is Claude Code, and its path is
|
|
48
|
+
* absolute. Otherwise the `command` field is read as a patch, which is Codex's
|
|
49
|
+
* `apply_patch` — its paths are relative to the cwd the tool ran in, and a
|
|
50
|
+
* string that is not a patch gives nothing, so nothing here has to ask which
|
|
51
|
+
* host is calling.
|
|
52
|
+
*
|
|
53
|
+
* A shell command that is not a patch reaches the same branch and yields an
|
|
54
|
+
* empty list, which is why this does not test `tool_name`: the patch text
|
|
55
|
+
* recognises itself, and an `apply_patch` run through a shell is recorded on
|
|
56
|
+
* the same terms as one run as a tool.
|
|
57
|
+
*/
|
|
58
|
+
export function filesFromPayload(payload) {
|
|
21
59
|
const input = payload.tool_input ?? {};
|
|
22
|
-
|
|
60
|
+
const named = input.file_path ?? input.filePath ?? input.edits?.[0]?.file_path ?? null;
|
|
61
|
+
if (named)
|
|
62
|
+
return [named];
|
|
63
|
+
return typeof input.command === "string" ? patchedFiles(input.command) : [];
|
|
23
64
|
}
|
|
24
65
|
function emit(context) {
|
|
25
66
|
process.stdout.write(JSON.stringify({
|
|
@@ -36,9 +77,13 @@ async function readStdin() {
|
|
|
36
77
|
return Buffer.concat(chunks).toString("utf8");
|
|
37
78
|
}
|
|
38
79
|
async function main() {
|
|
80
|
+
// `off` stops the work as well as the printing. `lsp` stops only the
|
|
81
|
+
// printing: it decides which surface delivers a verdict, and the reuse
|
|
82
|
+
// record below is not a verdict — it is what the Stop hook speaks from, and
|
|
83
|
+
// the language server has no equivalent of that moment (carrick#1330).
|
|
39
84
|
const channel = resolveChannel({ hooksInstalled: true });
|
|
40
|
-
if (channel.channel
|
|
41
|
-
log(
|
|
85
|
+
if (channel.channel === "off") {
|
|
86
|
+
log("CARRICK_CHANNEL=off; doing nothing");
|
|
42
87
|
return;
|
|
43
88
|
}
|
|
44
89
|
let payload;
|
|
@@ -49,31 +94,57 @@ async function main() {
|
|
|
49
94
|
log("unparseable payload", String(error));
|
|
50
95
|
return;
|
|
51
96
|
}
|
|
52
|
-
|
|
53
|
-
|
|
97
|
+
// A patch can name a file this index has no rows for beside one it does, so
|
|
98
|
+
// the filter is per file rather than a reason to drop the call.
|
|
99
|
+
const cwd = payload.cwd ?? null;
|
|
100
|
+
const edited = filesFromPayload(payload)
|
|
101
|
+
.filter((file) => CHECKED.test(file))
|
|
102
|
+
.map((file) => (path.isAbsolute(file) ? file : path.resolve(cwd ?? process.cwd(), file)));
|
|
103
|
+
if (edited.length === 0)
|
|
54
104
|
return;
|
|
105
|
+
// One root for the call: every file in one patch is in the workspace the tool
|
|
106
|
+
// ran in, and resolving it per file would ask the same question repeatedly.
|
|
55
107
|
const choice = resolveRoot({
|
|
56
|
-
clientRoot:
|
|
108
|
+
clientRoot: cwd,
|
|
57
109
|
projectDir: process.env["CLAUDE_PROJECT_DIR"] ?? null,
|
|
58
|
-
filePath:
|
|
110
|
+
filePath: edited[0],
|
|
59
111
|
});
|
|
60
112
|
const note = rootNote(choice);
|
|
61
113
|
if (note)
|
|
62
114
|
log(note);
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
115
|
+
const found = [];
|
|
116
|
+
const contexts = [];
|
|
117
|
+
for (const file of edited) {
|
|
118
|
+
const relative = path.relative(choice.root, file);
|
|
119
|
+
// The edit just landed, so the index describes the file as it was before
|
|
120
|
+
// it. This is the one surface that asks for the file to be re-judged from
|
|
121
|
+
// the working tree; it fires once per completed edit, where the language
|
|
122
|
+
// server fires on every save (carrick#1036).
|
|
123
|
+
const outcome = await check(relative, { cwd: choice.root, recheck: true });
|
|
124
|
+
if (!outcome.result) {
|
|
125
|
+
// One unreadable file in a patch of four is not a reason to say nothing
|
|
126
|
+
// about the other three.
|
|
127
|
+
log("no answer for", relative, outcome.failure ?? "");
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
found.push(...newFunctions(outcome.result, relative));
|
|
131
|
+
const context = renderPostToolUse(outcome.result, relative);
|
|
132
|
+
log(`check ${relative} -> ${context ? "context" : "nothing to say"} in ${outcome.ms}ms`);
|
|
133
|
+
if (context)
|
|
134
|
+
contexts.push(context);
|
|
135
|
+
}
|
|
136
|
+
// Silent and local: the names go to a file under the user's home directory
|
|
137
|
+
// and nothing about them is printed here.
|
|
138
|
+
if (found.length > 0 && payload.session_id) {
|
|
139
|
+
record(payload.session_id, found);
|
|
140
|
+
log(`recorded ${found.length} new function(s) for ${payload.session_id}`);
|
|
141
|
+
}
|
|
142
|
+
if (channel.channel !== "hook") {
|
|
143
|
+
log(`the ${channel.channel} channel owns delivery in this install; printing nothing`);
|
|
71
144
|
return;
|
|
72
145
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (context)
|
|
76
|
-
emit(context);
|
|
146
|
+
if (contexts.length > 0)
|
|
147
|
+
emit(contexts.join("\n\n"));
|
|
77
148
|
}
|
|
78
149
|
await main();
|
|
79
150
|
process.exit(0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-edit.js","sourceRoot":"","sources":["../../src/hook/post-edit.ts"],"names":[],"mappings":";AACA,
|
|
1
|
+
{"version":3,"file":"post-edit.js","sourceRoot":"","sources":["../../src/hook/post-edit.ts"],"names":[],"mappings":"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"}
|