carrick 0.3.53
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.md +99 -0
- package/README.md +79 -0
- package/bin/carrick.mjs +168 -0
- package/dist/channel.d.ts +16 -0
- package/dist/channel.js +37 -0
- package/dist/channel.js.map +1 -0
- package/dist/cli.d.ts +29 -0
- package/dist/cli.js +60 -0
- package/dist/cli.js.map +1 -0
- package/dist/contract.d.ts +154 -0
- package/dist/contract.js +141 -0
- package/dist/contract.js.map +1 -0
- package/dist/diagnostics.d.ts +54 -0
- package/dist/diagnostics.js +199 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/hook/post-edit.d.ts +13 -0
- package/dist/hook/post-edit.js +76 -0
- package/dist/hook/post-edit.js.map +1 -0
- package/dist/hook/session-start.d.ts +2 -0
- package/dist/hook/session-start.js +44 -0
- package/dist/hook/session-start.js.map +1 -0
- package/dist/init/identity.d.ts +20 -0
- package/dist/init/identity.js +60 -0
- package/dist/init/identity.js.map +1 -0
- package/dist/init/repos.d.ts +19 -0
- package/dist/init/repos.js +46 -0
- package/dist/init/repos.js.map +1 -0
- package/dist/init/run.d.ts +11 -0
- package/dist/init/run.js +228 -0
- package/dist/init/run.js.map +1 -0
- package/dist/init/settings.d.ts +50 -0
- package/dist/init/settings.js +138 -0
- package/dist/init/settings.js.map +1 -0
- package/dist/log.d.ts +2 -0
- package/dist/log.js +35 -0
- package/dist/log.js.map +1 -0
- package/dist/native.d.ts +48 -0
- package/dist/native.js +127 -0
- package/dist/native.js.map +1 -0
- package/dist/render.d.ts +65 -0
- package/dist/render.js +334 -0
- package/dist/render.js.map +1 -0
- package/dist/root.d.ts +30 -0
- package/dist/root.js +82 -0
- package/dist/root.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +255 -0
- package/dist/server.js.map +1 -0
- package/dist/templates.d.ts +18 -0
- package/dist/templates.js +66 -0
- package/dist/templates.js.map +1 -0
- package/package.json +72 -0
- package/plugin/.claude-plugin/plugin.json +6 -0
- package/plugin/.lsp.json +14 -0
- package/plugin/hooks/hooks.json +27 -0
- package/sidecar/dist/src/bundler.d.ts +141 -0
- package/sidecar/dist/src/bundler.js +680 -0
- package/sidecar/dist/src/capture/anchors.d.ts +61 -0
- package/sidecar/dist/src/capture/anchors.js +1132 -0
- package/sidecar/dist/src/capture/api.d.ts +378 -0
- package/sidecar/dist/src/capture/api.js +10 -0
- package/sidecar/dist/src/capture/augmentations.d.ts +20 -0
- package/sidecar/dist/src/capture/augmentations.js +60 -0
- package/sidecar/dist/src/capture/check-classify.d.ts +58 -0
- package/sidecar/dist/src/capture/check-classify.js +189 -0
- package/sidecar/dist/src/capture/check-deep.d.ts +32 -0
- package/sidecar/dist/src/capture/check-deep.js +91 -0
- package/sidecar/dist/src/capture/check-poison.d.ts +40 -0
- package/sidecar/dist/src/capture/check-poison.js +155 -0
- package/sidecar/dist/src/capture/check-probe.d.ts +70 -0
- package/sidecar/dist/src/capture/check-probe.js +147 -0
- package/sidecar/dist/src/capture/check-scrub.d.ts +31 -0
- package/sidecar/dist/src/capture/check-scrub.js +71 -0
- package/sidecar/dist/src/capture/check-workspace.d.ts +52 -0
- package/sidecar/dist/src/capture/check-workspace.js +218 -0
- package/sidecar/dist/src/capture/check.d.ts +39 -0
- package/sidecar/dist/src/capture/check.js +450 -0
- package/sidecar/dist/src/capture/deep-walk.d.ts +62 -0
- package/sidecar/dist/src/capture/deep-walk.js +243 -0
- package/sidecar/dist/src/capture/index.d.ts +36 -0
- package/sidecar/dist/src/capture/index.js +477 -0
- package/sidecar/dist/src/capture/lockfile.d.ts +48 -0
- package/sidecar/dist/src/capture/lockfile.js +490 -0
- package/sidecar/dist/src/capture/machinery.d.ts +59 -0
- package/sidecar/dist/src/capture/machinery.js +160 -0
- package/sidecar/dist/src/capture/node-builder.d.ts +37 -0
- package/sidecar/dist/src/capture/node-builder.js +123 -0
- package/sidecar/dist/src/capture/paths-rewrite.d.ts +34 -0
- package/sidecar/dist/src/capture/paths-rewrite.js +100 -0
- package/sidecar/dist/src/capture/self-check.d.ts +38 -0
- package/sidecar/dist/src/capture/self-check.js +317 -0
- package/sidecar/dist/src/capture/specifiers.d.ts +39 -0
- package/sidecar/dist/src/capture/specifiers.js +56 -0
- package/sidecar/dist/src/definition-resolver.d.ts +57 -0
- package/sidecar/dist/src/definition-resolver.js +153 -0
- package/sidecar/dist/src/index.d.ts +14 -0
- package/sidecar/dist/src/index.js +564 -0
- package/sidecar/dist/src/monorepo-builder.d.ts +129 -0
- package/sidecar/dist/src/monorepo-builder.js +584 -0
- package/sidecar/dist/src/project-loader.d.ts +130 -0
- package/sidecar/dist/src/project-loader.js +399 -0
- package/sidecar/dist/src/type-inferrer.d.ts +940 -0
- package/sidecar/dist/src/type-inferrer.js +3540 -0
- package/sidecar/dist/src/type-structural-expander.d.ts +61 -0
- package/sidecar/dist/src/type-structural-expander.js +283 -0
- package/sidecar/dist/src/type-text-canonicalizer.d.ts +40 -0
- package/sidecar/dist/src/type-text-canonicalizer.js +297 -0
- package/sidecar/dist/src/types.d.ts +669 -0
- package/sidecar/dist/src/types.js +5 -0
- package/sidecar/dist/src/validators.d.ts +2214 -0
- package/sidecar/dist/src/validators.js +336 -0
- package/sidecar/package.json +6 -0
- package/templates/carrick.json +7 -0
- package/templates/carrick.yml +45 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/** A count with up to 200 of its reasons, mirroring `Counted` in src/boundary.rs. */
|
|
2
|
+
export type Counted = {
|
|
3
|
+
total: number;
|
|
4
|
+
reasons?: string[];
|
|
5
|
+
truncated?: boolean;
|
|
6
|
+
};
|
|
7
|
+
/** `ServiceBoundary` from src/boundary.rs, as it rides the check output. */
|
|
8
|
+
export type Boundary = {
|
|
9
|
+
commit_hash?: string;
|
|
10
|
+
files_attempted?: number;
|
|
11
|
+
files_lost?: Counted;
|
|
12
|
+
unemitted_literal_candidates?: number;
|
|
13
|
+
consumers_not_resolved?: Counted;
|
|
14
|
+
sdk_unresolved?: Counted;
|
|
15
|
+
unknown_call_paths?: Counted;
|
|
16
|
+
model_only_rows?: number;
|
|
17
|
+
model_rows_joined?: number;
|
|
18
|
+
model_contradictions_discarded?: number;
|
|
19
|
+
model_endpoints_discarded_in_claimed_modules?: number;
|
|
20
|
+
routes_without_response_type?: Counted;
|
|
21
|
+
calls_without_expected_type?: Counted;
|
|
22
|
+
types_degraded?: {
|
|
23
|
+
stage?: string;
|
|
24
|
+
detail?: string;
|
|
25
|
+
};
|
|
26
|
+
bare_checkout?: boolean;
|
|
27
|
+
};
|
|
28
|
+
export type Counterpart = {
|
|
29
|
+
/**
|
|
30
|
+
* `peer` is a shared external contract: both sides call the same third
|
|
31
|
+
* party and neither serves the other, so it gets no producer/consumer word.
|
|
32
|
+
*/
|
|
33
|
+
role: "producer" | "consumer" | "peer" | string;
|
|
34
|
+
service?: string;
|
|
35
|
+
/** Absolute path of the counterpart's repo; null when the index lost it. */
|
|
36
|
+
repo?: string | null;
|
|
37
|
+
/** Relative to the counterpart's own repo, which is not the queried file's. */
|
|
38
|
+
file?: string;
|
|
39
|
+
line?: number;
|
|
40
|
+
};
|
|
41
|
+
export type Verdict = {
|
|
42
|
+
state: "resolved" | "unresolved" | "not_checked" | string;
|
|
43
|
+
/** Null while `state` is `not_checked`: matched, never compared. */
|
|
44
|
+
result: null | "compatible" | "type_mismatch" | "method_mismatch" | "producer_removed" | string;
|
|
45
|
+
detail?: string;
|
|
46
|
+
};
|
|
47
|
+
export type CheckItem = {
|
|
48
|
+
kind: "route" | "call" | string;
|
|
49
|
+
method?: string;
|
|
50
|
+
path?: string;
|
|
51
|
+
line?: number;
|
|
52
|
+
col?: number;
|
|
53
|
+
/** R1: a row whose only source is the model is a candidate, never a fact. */
|
|
54
|
+
source?: "fact" | "candidate" | string;
|
|
55
|
+
resolution_source?: string | null;
|
|
56
|
+
/** One line naming what the row was read off. */
|
|
57
|
+
evidence?: string | null;
|
|
58
|
+
counterparts?: Counterpart[];
|
|
59
|
+
verdict?: Verdict | null;
|
|
60
|
+
};
|
|
61
|
+
export type CheckResult = {
|
|
62
|
+
schema: string;
|
|
63
|
+
/** Set instead of the payload, e.g. `not_indexed`. */
|
|
64
|
+
error?: string;
|
|
65
|
+
file?: string;
|
|
66
|
+
/** Absolute path of the repo owning `file`; `repo` + `file` is openable. */
|
|
67
|
+
repo?: string;
|
|
68
|
+
service?: string;
|
|
69
|
+
index_commit?: string;
|
|
70
|
+
/** RFC 3339 time the index or the last refresh of this service ran. */
|
|
71
|
+
indexed_at?: string;
|
|
72
|
+
/** The scanner release that wrote the index. */
|
|
73
|
+
scanner_version?: string;
|
|
74
|
+
changed_since_index?: number;
|
|
75
|
+
stale?: boolean;
|
|
76
|
+
/** The index holds this file and it is no longer on disk. */
|
|
77
|
+
deleted?: boolean;
|
|
78
|
+
items?: CheckItem[];
|
|
79
|
+
boundary?: Boundary;
|
|
80
|
+
/**
|
|
81
|
+
* One sentence about what a local index cannot hold at all. Always sent, so a
|
|
82
|
+
* thin index never reads as "there is no API here". It leads `boundary_lines`
|
|
83
|
+
* and, when those are absent, the counts rendered here.
|
|
84
|
+
*/
|
|
85
|
+
boundary_note?: string;
|
|
86
|
+
/**
|
|
87
|
+
* The boundary already rendered by the CLI, the same bytes it prints at the
|
|
88
|
+
* tail of its human output. When it is here it is printed as it stands; when
|
|
89
|
+
* it is not, the counts in `boundary` are rendered instead. One renderer owns
|
|
90
|
+
* the wording either way, and which one depends only on what the CLI sent.
|
|
91
|
+
*/
|
|
92
|
+
boundary_lines?: string[];
|
|
93
|
+
};
|
|
94
|
+
export declare const SCHEMA = "carrick.check/0";
|
|
95
|
+
export declare const STATUS_SCHEMA = "carrick.status/0";
|
|
96
|
+
/** One service in a `carrick status --json` answer. */
|
|
97
|
+
export type StatusService = {
|
|
98
|
+
service: string;
|
|
99
|
+
/** Absolute. Services of one repo share a commit and a changed-file count. */
|
|
100
|
+
repo: string;
|
|
101
|
+
index_commit: string;
|
|
102
|
+
indexed_at?: string;
|
|
103
|
+
routes: number;
|
|
104
|
+
calls: number;
|
|
105
|
+
changed_since_index: number;
|
|
106
|
+
/** Up to 50, repo-relative and sorted. */
|
|
107
|
+
stale_files?: string[];
|
|
108
|
+
/** Always exact, whatever the list length. */
|
|
109
|
+
stale_files_total?: number;
|
|
110
|
+
stale_files_truncated?: boolean;
|
|
111
|
+
boundary?: Boundary;
|
|
112
|
+
boundary_note?: string;
|
|
113
|
+
boundary_lines?: string[];
|
|
114
|
+
};
|
|
115
|
+
/** `carrick status --json`: the workspace, with no file in the question. */
|
|
116
|
+
export type StatusResult = {
|
|
117
|
+
schema: string;
|
|
118
|
+
error?: string;
|
|
119
|
+
workspace?: string;
|
|
120
|
+
indexed_at?: string;
|
|
121
|
+
scanner_version?: string;
|
|
122
|
+
services: StatusService[];
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Parse CLI stdout into a `CheckResult`, or `null` when it is not one.
|
|
126
|
+
*
|
|
127
|
+
* Only the schema tag is required. A payload whose `items` is missing reads as
|
|
128
|
+
* "no locations", which is what an unindexed file returns, and is not an error.
|
|
129
|
+
*/
|
|
130
|
+
export declare function parseCheckResult(stdout: string): CheckResult | null;
|
|
131
|
+
/**
|
|
132
|
+
* Parse `carrick status --json` output, or `null` when it is not that shape.
|
|
133
|
+
*
|
|
134
|
+
* Its own schema rather than a fileless `check`, so every `carrick.check/0`
|
|
135
|
+
* response stays about one file. A service row missing a required field is
|
|
136
|
+
* dropped rather than rendered half-formed.
|
|
137
|
+
*/
|
|
138
|
+
export declare function parseStatusResult(stdout: string): StatusResult | null;
|
|
139
|
+
/** True when the row is the model's reading alone (R1). */
|
|
140
|
+
export declare function isCandidate(item: CheckItem): boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Items worth reporting: a verdict that states a result other than
|
|
143
|
+
* `compatible`.
|
|
144
|
+
*
|
|
145
|
+
* The result decides, not the state. `state` is the type layer's word, and
|
|
146
|
+
* `not_checked` covers the routing findings (`method_mismatch`,
|
|
147
|
+
* `producer_removed`) as well as the rows nothing bears on, so filtering on it
|
|
148
|
+
* would drop real findings. A null result is the "nothing is claimed" case in
|
|
149
|
+
* every state. Both channels report this same set, so a diagnostic and a hook
|
|
150
|
+
* line never disagree about what is wrong.
|
|
151
|
+
*/
|
|
152
|
+
export declare function problemItems(result: CheckResult): CheckItem[];
|
|
153
|
+
/** Items that name a counterpart in another service, problem or not. */
|
|
154
|
+
export declare function connectedItems(result: CheckResult): CheckItem[];
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// The `carrick check|touch <file> --json` output, schema `carrick.check/0`.
|
|
2
|
+
//
|
|
3
|
+
// The CLI owns this shape: docs/local-mode-output.md and
|
|
4
|
+
// docs/schemas/carrick-check-0.json (carrick#708). It may ADD fields; it never
|
|
5
|
+
// renames or removes one. Everything here is read-only: the
|
|
6
|
+
// plugin parses what it is given, keeps the fields it renders, and drops the
|
|
7
|
+
// rest. An unknown `kind`, `result` or `state` value is carried through as a
|
|
8
|
+
// string rather than rejected, so a CLI that learns a new verdict result still
|
|
9
|
+
// renders a line here instead of nothing.
|
|
10
|
+
export const SCHEMA = "carrick.check/0";
|
|
11
|
+
export const STATUS_SCHEMA = "carrick.status/0";
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse CLI stdout into a `CheckResult`, or `null` when it is not one.
|
|
17
|
+
*
|
|
18
|
+
* Only the schema tag is required. A payload whose `items` is missing reads as
|
|
19
|
+
* "no locations", which is what an unindexed file returns, and is not an error.
|
|
20
|
+
*/
|
|
21
|
+
export function parseCheckResult(stdout) {
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = JSON.parse(stdout);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if (!isRecord(parsed))
|
|
30
|
+
return null;
|
|
31
|
+
if (typeof parsed["schema"] !== "string")
|
|
32
|
+
return null;
|
|
33
|
+
if (parsed["schema"] !== SCHEMA)
|
|
34
|
+
return null;
|
|
35
|
+
const items = Array.isArray(parsed["items"]) ? parsed["items"] : [];
|
|
36
|
+
const result = {
|
|
37
|
+
schema: parsed["schema"],
|
|
38
|
+
items,
|
|
39
|
+
};
|
|
40
|
+
if (typeof parsed["error"] === "string")
|
|
41
|
+
result.error = parsed["error"];
|
|
42
|
+
if (typeof parsed["file"] === "string")
|
|
43
|
+
result.file = parsed["file"];
|
|
44
|
+
if (typeof parsed["repo"] === "string")
|
|
45
|
+
result.repo = parsed["repo"];
|
|
46
|
+
if (typeof parsed["service"] === "string")
|
|
47
|
+
result.service = parsed["service"];
|
|
48
|
+
if (typeof parsed["index_commit"] === "string")
|
|
49
|
+
result.index_commit = parsed["index_commit"];
|
|
50
|
+
if (typeof parsed["indexed_at"] === "string")
|
|
51
|
+
result.indexed_at = parsed["indexed_at"];
|
|
52
|
+
if (typeof parsed["scanner_version"] === "string") {
|
|
53
|
+
result.scanner_version = parsed["scanner_version"];
|
|
54
|
+
}
|
|
55
|
+
if (typeof parsed["deleted"] === "boolean")
|
|
56
|
+
result.deleted = parsed["deleted"];
|
|
57
|
+
if (typeof parsed["changed_since_index"] === "number") {
|
|
58
|
+
result.changed_since_index = parsed["changed_since_index"];
|
|
59
|
+
}
|
|
60
|
+
if (typeof parsed["stale"] === "boolean")
|
|
61
|
+
result.stale = parsed["stale"];
|
|
62
|
+
if (isRecord(parsed["boundary"]))
|
|
63
|
+
result.boundary = parsed["boundary"];
|
|
64
|
+
if (typeof parsed["boundary_note"] === "string")
|
|
65
|
+
result.boundary_note = parsed["boundary_note"];
|
|
66
|
+
if (Array.isArray(parsed["boundary_lines"])) {
|
|
67
|
+
const lines = parsed["boundary_lines"].filter((line) => typeof line === "string");
|
|
68
|
+
if (lines.length)
|
|
69
|
+
result.boundary_lines = lines;
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Parse `carrick status --json` output, or `null` when it is not that shape.
|
|
75
|
+
*
|
|
76
|
+
* Its own schema rather than a fileless `check`, so every `carrick.check/0`
|
|
77
|
+
* response stays about one file. A service row missing a required field is
|
|
78
|
+
* dropped rather than rendered half-formed.
|
|
79
|
+
*/
|
|
80
|
+
export function parseStatusResult(stdout) {
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = JSON.parse(stdout);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
if (!isRecord(parsed))
|
|
89
|
+
return null;
|
|
90
|
+
if (parsed["schema"] !== STATUS_SCHEMA)
|
|
91
|
+
return null;
|
|
92
|
+
const result = { schema: STATUS_SCHEMA, services: [] };
|
|
93
|
+
if (typeof parsed["error"] === "string")
|
|
94
|
+
result.error = parsed["error"];
|
|
95
|
+
if (typeof parsed["workspace"] === "string")
|
|
96
|
+
result.workspace = parsed["workspace"];
|
|
97
|
+
if (typeof parsed["indexed_at"] === "string")
|
|
98
|
+
result.indexed_at = parsed["indexed_at"];
|
|
99
|
+
if (typeof parsed["scanner_version"] === "string") {
|
|
100
|
+
result.scanner_version = parsed["scanner_version"];
|
|
101
|
+
}
|
|
102
|
+
const services = Array.isArray(parsed["services"]) ? parsed["services"] : [];
|
|
103
|
+
for (const entry of services) {
|
|
104
|
+
if (!isRecord(entry))
|
|
105
|
+
continue;
|
|
106
|
+
if (typeof entry["service"] !== "string" || typeof entry["repo"] !== "string")
|
|
107
|
+
continue;
|
|
108
|
+
if (typeof entry["index_commit"] !== "string")
|
|
109
|
+
continue;
|
|
110
|
+
result.services.push(entry);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
/** True when the row is the model's reading alone (R1). */
|
|
115
|
+
export function isCandidate(item) {
|
|
116
|
+
return item.source === "candidate";
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Items worth reporting: a verdict that states a result other than
|
|
120
|
+
* `compatible`.
|
|
121
|
+
*
|
|
122
|
+
* The result decides, not the state. `state` is the type layer's word, and
|
|
123
|
+
* `not_checked` covers the routing findings (`method_mismatch`,
|
|
124
|
+
* `producer_removed`) as well as the rows nothing bears on, so filtering on it
|
|
125
|
+
* would drop real findings. A null result is the "nothing is claimed" case in
|
|
126
|
+
* every state. Both channels report this same set, so a diagnostic and a hook
|
|
127
|
+
* line never disagree about what is wrong.
|
|
128
|
+
*/
|
|
129
|
+
export function problemItems(result) {
|
|
130
|
+
return (result.items ?? []).filter((item) => {
|
|
131
|
+
const verdict = item.verdict;
|
|
132
|
+
if (!verdict)
|
|
133
|
+
return false;
|
|
134
|
+
return verdict.result != null && verdict.result !== "compatible";
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/** Items that name a counterpart in another service, problem or not. */
|
|
138
|
+
export function connectedItems(result) {
|
|
139
|
+
return (result.items ?? []).filter((item) => (item.counterparts ?? []).length > 0);
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type CheckItem, type CheckResult, type Counterpart } from "./contract.ts";
|
|
2
|
+
export declare const SOURCE = "carrick";
|
|
3
|
+
export declare const SEVERITY: {
|
|
4
|
+
readonly error: 1;
|
|
5
|
+
readonly warning: 2;
|
|
6
|
+
readonly information: 3;
|
|
7
|
+
readonly hint: 4;
|
|
8
|
+
};
|
|
9
|
+
export type Position = {
|
|
10
|
+
line: number;
|
|
11
|
+
character: number;
|
|
12
|
+
};
|
|
13
|
+
export type Range = {
|
|
14
|
+
start: Position;
|
|
15
|
+
end: Position;
|
|
16
|
+
};
|
|
17
|
+
export type Diagnostic = {
|
|
18
|
+
range: Range;
|
|
19
|
+
severity: number;
|
|
20
|
+
code?: string;
|
|
21
|
+
source: string;
|
|
22
|
+
message: string;
|
|
23
|
+
relatedInformation?: Array<{
|
|
24
|
+
location: {
|
|
25
|
+
uri: string;
|
|
26
|
+
range: Range;
|
|
27
|
+
};
|
|
28
|
+
message: string;
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
/** An error only for a finding on a fact row that claims something (R1c). */
|
|
32
|
+
export declare function severityOf(item: CheckItem): number;
|
|
33
|
+
export declare function messageOf(item: CheckItem): string;
|
|
34
|
+
/**
|
|
35
|
+
* Where a counterpart lands on this disk, or null when it cannot be pointed at.
|
|
36
|
+
*
|
|
37
|
+
* `repo` + `file`, and nothing else: the payload states the repo absolutely, so
|
|
38
|
+
* a reader that also tried the workspace root or a directory named after the
|
|
39
|
+
* service would be guessing at paths the producer already knows.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveCounterpart(counterpart: Counterpart, exists?: (target: string) => boolean): string | null;
|
|
42
|
+
export type DiagnosticOptions = {
|
|
43
|
+
/** Injectable for tests. */
|
|
44
|
+
exists?: (target: string) => boolean;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Diagnostics for one check payload, keyed by the absolute file they belong to.
|
|
48
|
+
*
|
|
49
|
+
* The checked file gets one per problem item, one for the boundary, and one
|
|
50
|
+
* more when the index holds a file that is no longer on disk. Each counterpart
|
|
51
|
+
* site whose path resolves gets the same finding at its own line, so an agent
|
|
52
|
+
* that opens the consumer reads it there too.
|
|
53
|
+
*/
|
|
54
|
+
export declare function toDiagnostics(result: CheckResult, root: string, checkedFile: string, options?: DiagnosticOptions): Map<string, Diagnostic[]>;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Check verdicts as LSP diagnostics.
|
|
2
|
+
//
|
|
3
|
+
// Four rules decide what a diagnostic looks like here.
|
|
4
|
+
//
|
|
5
|
+
// R1: a finding on a fact row is an error; the same finding on a candidate row
|
|
6
|
+
// is a warning that says it is the model's reading. `verdict.state` is the type
|
|
7
|
+
// layer's word and not a confidence in the row: `method_mismatch` and
|
|
8
|
+
// `producer_removed` are routing facts and carry `not_checked` because no type
|
|
9
|
+
// verdict bears on them, so keying severity off the state alone would demote
|
|
10
|
+
// every routing finding. A state of `unresolved` is the exception: nothing was
|
|
11
|
+
// claimed there, so nothing is asserted here either.
|
|
12
|
+
//
|
|
13
|
+
// E18: Claude Code drops `relatedInformation` from the attachment it gives the
|
|
14
|
+
// model, and editors render it as clickable locations. So every counterpart
|
|
15
|
+
// site goes in BOTH: in the message text for the agent, and in
|
|
16
|
+
// `relatedInformation` for the human.
|
|
17
|
+
//
|
|
18
|
+
// The boundary is never dropped. The local index holds no bare-receiver route
|
|
19
|
+
// and no `fetch` call, because both need a model, so a file with no findings is
|
|
20
|
+
// not a file with no contracts. One information diagnostic carries the boundary
|
|
21
|
+
// on every checked file that has one.
|
|
22
|
+
//
|
|
23
|
+
// A counterpart's `file` is relative to its OWN repo, and the payload names
|
|
24
|
+
// that repo absolutely, so `repo` + `file` is the path and nothing is guessed.
|
|
25
|
+
// `repo` is null when the index no longer holds the counterpart's repo: then
|
|
26
|
+
// there is no path to give, the site stays in the message text with no URI, and
|
|
27
|
+
// no diagnostic is mirrored onto a file this machine cannot point at.
|
|
28
|
+
import fs from "node:fs";
|
|
29
|
+
import path from "node:path";
|
|
30
|
+
import { pathToFileURL } from "node:url";
|
|
31
|
+
import { isCandidate, problemItems, } from "./contract.js";
|
|
32
|
+
import { boundaryFor, stateWord } from "./render.js";
|
|
33
|
+
export const SOURCE = "carrick";
|
|
34
|
+
export const SEVERITY = { error: 1, warning: 2, information: 3, hint: 4 };
|
|
35
|
+
function rangeAt(line, col) {
|
|
36
|
+
const zeroLine = Math.max(0, (line ?? 1) - 1);
|
|
37
|
+
const zeroCol = Math.max(0, (col ?? 1) - 1);
|
|
38
|
+
return {
|
|
39
|
+
start: { line: zeroLine, character: zeroCol },
|
|
40
|
+
end: { line: zeroLine, character: zeroCol + 1 },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** An error only for a finding on a fact row that claims something (R1c). */
|
|
44
|
+
export function severityOf(item) {
|
|
45
|
+
if (isCandidate(item))
|
|
46
|
+
return SEVERITY.warning;
|
|
47
|
+
if (item.source !== "fact")
|
|
48
|
+
return SEVERITY.warning;
|
|
49
|
+
if (item.verdict?.state === "unresolved")
|
|
50
|
+
return SEVERITY.warning;
|
|
51
|
+
return SEVERITY.error;
|
|
52
|
+
}
|
|
53
|
+
/** `consumer in admin-ui`, and for a peer the role word is left out. */
|
|
54
|
+
function counterpartPhrase(counterpart) {
|
|
55
|
+
const service = counterpart.service ?? "an unnamed service";
|
|
56
|
+
return counterpart.role === "peer" ? `the same contract in ${service}` : `${counterpart.role} in ${service}`;
|
|
57
|
+
}
|
|
58
|
+
function counterpartWhere(counterpart) {
|
|
59
|
+
return `${counterpart.file ?? "an unnamed file"}${counterpart.line ? `:${counterpart.line}` : ""}`;
|
|
60
|
+
}
|
|
61
|
+
export function messageOf(item) {
|
|
62
|
+
const operation = [item.method, item.path].filter(Boolean).join(" ");
|
|
63
|
+
const verdict = item.verdict;
|
|
64
|
+
// `result` is null wherever the state is the whole statement, and a result
|
|
65
|
+
// whose state is not `resolved` has no compiler verdict behind it.
|
|
66
|
+
const verdictWords = verdict
|
|
67
|
+
? verdict.result == null
|
|
68
|
+
? stateWord(verdict.state)
|
|
69
|
+
: verdict.state === "resolved"
|
|
70
|
+
? verdict.result
|
|
71
|
+
: `${verdict.result} (${stateWord(verdict.state)})`
|
|
72
|
+
: "";
|
|
73
|
+
const head = [operation, verdictWords].filter(Boolean).join(" ");
|
|
74
|
+
const parts = [verdict?.detail ? `${head}: ${verdict.detail}` : head];
|
|
75
|
+
if (isCandidate(item)) {
|
|
76
|
+
const from = item.resolution_source ? ` (${item.resolution_source})` : "";
|
|
77
|
+
parts.push(`Candidate row${from}, so this is a reading of the code and not a fact about it.`);
|
|
78
|
+
}
|
|
79
|
+
if (item.evidence)
|
|
80
|
+
parts.push(`Read off ${item.evidence}.`);
|
|
81
|
+
const counterparts = item.counterparts ?? [];
|
|
82
|
+
if (counterparts.length) {
|
|
83
|
+
parts.push(`Counterparts: ${counterparts
|
|
84
|
+
.map((counterpart) => `${counterpartPhrase(counterpart)}, ${counterpartWhere(counterpart)}`)
|
|
85
|
+
.join("; ")}`);
|
|
86
|
+
}
|
|
87
|
+
return parts.join("\n");
|
|
88
|
+
}
|
|
89
|
+
function defaultExists(target) {
|
|
90
|
+
try {
|
|
91
|
+
return fs.existsSync(target);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Where a counterpart lands on this disk, or null when it cannot be pointed at.
|
|
99
|
+
*
|
|
100
|
+
* `repo` + `file`, and nothing else: the payload states the repo absolutely, so
|
|
101
|
+
* a reader that also tried the workspace root or a directory named after the
|
|
102
|
+
* service would be guessing at paths the producer already knows.
|
|
103
|
+
*/
|
|
104
|
+
export function resolveCounterpart(counterpart, exists = defaultExists) {
|
|
105
|
+
if (!counterpart.file)
|
|
106
|
+
return null;
|
|
107
|
+
if (path.isAbsolute(counterpart.file))
|
|
108
|
+
return exists(counterpart.file) ? counterpart.file : null;
|
|
109
|
+
if (!counterpart.repo)
|
|
110
|
+
return null;
|
|
111
|
+
const target = path.resolve(counterpart.repo, counterpart.file);
|
|
112
|
+
return exists(target) ? target : null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Diagnostics for one check payload, keyed by the absolute file they belong to.
|
|
116
|
+
*
|
|
117
|
+
* The checked file gets one per problem item, one for the boundary, and one
|
|
118
|
+
* more when the index holds a file that is no longer on disk. Each counterpart
|
|
119
|
+
* site whose path resolves gets the same finding at its own line, so an agent
|
|
120
|
+
* that opens the consumer reads it there too.
|
|
121
|
+
*/
|
|
122
|
+
export function toDiagnostics(result, root, checkedFile, options = {}) {
|
|
123
|
+
const exists = options.exists ?? defaultExists;
|
|
124
|
+
const byFile = new Map();
|
|
125
|
+
if (result.error)
|
|
126
|
+
return byFile;
|
|
127
|
+
// The path the caller asked about, always: that is the document the editor
|
|
128
|
+
// has open, and a diagnostic published to any other URI is invisible. The
|
|
129
|
+
// payload's `repo` + `file` names the same file by another route and is used
|
|
130
|
+
// for counterparts, where the caller has no path of its own.
|
|
131
|
+
const checkedAbs = path.resolve(root, checkedFile);
|
|
132
|
+
const put = (file, diagnostic) => {
|
|
133
|
+
const existing = byFile.get(file);
|
|
134
|
+
if (existing)
|
|
135
|
+
existing.push(diagnostic);
|
|
136
|
+
else
|
|
137
|
+
byFile.set(file, [diagnostic]);
|
|
138
|
+
};
|
|
139
|
+
for (const item of problemItems(result)) {
|
|
140
|
+
const counterparts = item.counterparts ?? [];
|
|
141
|
+
const related = counterparts
|
|
142
|
+
.map((counterpart) => ({ counterpart, resolved: resolveCounterpart(counterpart, exists) }))
|
|
143
|
+
.filter((entry) => entry.resolved !== null)
|
|
144
|
+
.map((entry) => ({
|
|
145
|
+
location: {
|
|
146
|
+
uri: pathToFileURL(entry.resolved).toString(),
|
|
147
|
+
range: rangeAt(entry.counterpart.line, 1),
|
|
148
|
+
},
|
|
149
|
+
message: counterpartPhrase(entry.counterpart),
|
|
150
|
+
}));
|
|
151
|
+
const diagnostic = {
|
|
152
|
+
range: rangeAt(item.line, item.col),
|
|
153
|
+
severity: severityOf(item),
|
|
154
|
+
source: SOURCE,
|
|
155
|
+
message: messageOf(item),
|
|
156
|
+
};
|
|
157
|
+
if (item.verdict?.result)
|
|
158
|
+
diagnostic.code = item.verdict.result;
|
|
159
|
+
if (related.length)
|
|
160
|
+
diagnostic.relatedInformation = related;
|
|
161
|
+
put(checkedAbs, diagnostic);
|
|
162
|
+
for (const counterpart of counterparts) {
|
|
163
|
+
const resolved = resolveCounterpart(counterpart, exists);
|
|
164
|
+
if (!resolved)
|
|
165
|
+
continue;
|
|
166
|
+
const mirrored = {
|
|
167
|
+
range: rangeAt(counterpart.line, 1),
|
|
168
|
+
severity: severityOf(item),
|
|
169
|
+
source: SOURCE,
|
|
170
|
+
message: `${counterpartPhrase(counterpart)} of ${result.file ?? checkedFile}. ${messageOf(item)}`,
|
|
171
|
+
};
|
|
172
|
+
if (item.verdict?.result)
|
|
173
|
+
mirrored.code = item.verdict.result;
|
|
174
|
+
put(resolved, mirrored);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (result.deleted) {
|
|
178
|
+
const consumers = (result.items ?? []).reduce((total, item) => total + (item.counterparts ?? []).length, 0);
|
|
179
|
+
put(checkedAbs, {
|
|
180
|
+
range: rangeAt(1, 1),
|
|
181
|
+
severity: SEVERITY.warning,
|
|
182
|
+
code: "producer_removed",
|
|
183
|
+
source: SOURCE,
|
|
184
|
+
message: `producer_removed: the index holds ${(result.items ?? []).length} row(s) for this file and it is no longer on disk. ${consumers} counterpart(s) still name it.`,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const boundary = boundaryFor(result);
|
|
188
|
+
if (boundary.length) {
|
|
189
|
+
put(checkedAbs, {
|
|
190
|
+
range: rangeAt(1, 1),
|
|
191
|
+
severity: SEVERITY.information,
|
|
192
|
+
code: "boundary",
|
|
193
|
+
source: SOURCE,
|
|
194
|
+
message: `What this service's scan could not classify:\n${boundary.join("\n")}`,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return byFile;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
type Payload = {
|
|
3
|
+
cwd?: string;
|
|
4
|
+
tool_input?: {
|
|
5
|
+
file_path?: string;
|
|
6
|
+
filePath?: string;
|
|
7
|
+
edits?: Array<{
|
|
8
|
+
file_path?: string;
|
|
9
|
+
}>;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export declare function fileFromPayload(payload: Payload): string | null;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PostToolUse hook for Edit, Write and MultiEdit.
|
|
3
|
+
//
|
|
4
|
+
// Reads the tool payload on stdin, runs `carrick check <file> --json` from the
|
|
5
|
+
// workspace root and prints the verdicts as `additionalContext`, which Claude
|
|
6
|
+
// Code attaches to the tool result. Attaching to the result rather than to the
|
|
7
|
+
// next turn is why this channel has no "the run ended on the edit" hole.
|
|
8
|
+
//
|
|
9
|
+
// It exits 0 whatever happens. An edit never fails because Carrick had nothing
|
|
10
|
+
// to say, could not find its binary, or timed out.
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { check } from "../cli.js";
|
|
13
|
+
import { resolveChannel } from "../channel.js";
|
|
14
|
+
import { createLogger } from "../log.js";
|
|
15
|
+
import { renderPostToolUse } from "../render.js";
|
|
16
|
+
import { resolveRoot, rootNote } from "../root.js";
|
|
17
|
+
const log = createLogger("carrick-hook");
|
|
18
|
+
/** The files the index has rows for. */
|
|
19
|
+
const CHECKED = /\.(ts|tsx|mts|cts)$/;
|
|
20
|
+
export function fileFromPayload(payload) {
|
|
21
|
+
const input = payload.tool_input ?? {};
|
|
22
|
+
return input.file_path ?? input.filePath ?? input.edits?.[0]?.file_path ?? null;
|
|
23
|
+
}
|
|
24
|
+
function emit(context) {
|
|
25
|
+
process.stdout.write(JSON.stringify({
|
|
26
|
+
hookSpecificOutput: {
|
|
27
|
+
hookEventName: "PostToolUse",
|
|
28
|
+
additionalContext: context,
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
async function readStdin() {
|
|
33
|
+
const chunks = [];
|
|
34
|
+
for await (const chunk of process.stdin)
|
|
35
|
+
chunks.push(Buffer.from(chunk));
|
|
36
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
37
|
+
}
|
|
38
|
+
async function main() {
|
|
39
|
+
const channel = resolveChannel({ hooksInstalled: true });
|
|
40
|
+
if (channel.channel !== "hook") {
|
|
41
|
+
log(`the ${channel.channel} channel owns delivery in this install; printing nothing`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let payload;
|
|
45
|
+
try {
|
|
46
|
+
payload = JSON.parse((await readStdin()) || "{}");
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
log("unparseable payload", String(error));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const file = fileFromPayload(payload);
|
|
53
|
+
if (!file || !CHECKED.test(file))
|
|
54
|
+
return;
|
|
55
|
+
const choice = resolveRoot({
|
|
56
|
+
clientRoot: payload.cwd ?? null,
|
|
57
|
+
projectDir: process.env["CLAUDE_PROJECT_DIR"] ?? null,
|
|
58
|
+
filePath: file,
|
|
59
|
+
});
|
|
60
|
+
const note = rootNote(choice);
|
|
61
|
+
if (note)
|
|
62
|
+
log(note);
|
|
63
|
+
const relative = path.isAbsolute(file) ? path.relative(choice.root, file) : file;
|
|
64
|
+
const outcome = await check(relative, { cwd: choice.root });
|
|
65
|
+
if (!outcome.result) {
|
|
66
|
+
log("no answer for", relative, outcome.failure ?? "");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const context = renderPostToolUse(outcome.result, relative);
|
|
70
|
+
log(`check ${relative} -> ${context ? "context" : "nothing to say"} in ${outcome.ms}ms`);
|
|
71
|
+
if (context)
|
|
72
|
+
emit(context);
|
|
73
|
+
}
|
|
74
|
+
await main();
|
|
75
|
+
process.exit(0);
|
|
76
|
+
//# sourceMappingURL=post-edit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-edit.js","sourceRoot":"","sources":["../../src/hook/post-edit.ts"],"names":[],"mappings":"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"}
|