carrick 0.3.81 → 0.3.83
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 +46 -19
- 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 +36 -0
- package/dist/init/doctor.js +97 -2
- package/dist/init/doctor.js.map +1 -1
- package/dist/init/files.d.ts +16 -0
- package/dist/init/files.js +35 -0
- package/dist/init/files.js.map +1 -0
- 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 +35 -3
- package/dist/init/output.js +115 -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 +91 -23
- 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 +73 -6
- package/dist/init/run.js +382 -101
- 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 +97 -0
- package/dist/init/task-skills.js +267 -0
- package/dist/init/task-skills.js.map +1 -0
- 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/scan.d.ts +13 -5
- package/dist/scan.js +21 -10
- package/dist/scan.js.map +1 -1
- package/dist/templates.d.ts +9 -0
- package/dist/templates.js +9 -1
- package/dist/templates.js.map +1 -1
- package/package.json +6 -6
- package/plugin/hooks/hooks.json +11 -0
- package/sidecar/dist/src/capture/check-classify.d.ts +10 -1
- package/sidecar/dist/src/capture/check-classify.js +66 -8
- 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 +83 -0
- package/sidecar/dist/src/capture/check-fields.js +259 -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 +9 -2
- package/templates/skills/carrick-census.md +89 -0
- package/templates/skills/carrick-drift.md +107 -0
- package/templates/skills/carrick-impact.md +108 -0
- package/templates/skills/carrick-reuse.md +106 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: carrick-drift
|
|
3
|
+
description: Use when the ask is whether a consumer and a producer still agree on a type, before changing a request or response shape, and when a compatibility verdict names a problem you cannot place. Puts the producer's type, each consumer call site's expected type and the stored verdict side by side.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Where two services disagree about a shape
|
|
7
|
+
|
|
8
|
+
{{SCOPE_NOTE}}
|
|
9
|
+
|
|
10
|
+
`get_contract_pair` holds both sides and the verdict. It reports what the scan
|
|
11
|
+
stored and computes no verdict of its own, and neither do you.
|
|
12
|
+
|
|
13
|
+
## 1. The pairs
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
get_service_graph({{SCOPE}})
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Each edge is a consumer and a producer. An edge carrying `via_sdk` reaches its
|
|
20
|
+
producer through a published package and has no call site of its own. Page with
|
|
21
|
+
`offset: <next_offset>` while the rows keep coming. To start from one service,
|
|
22
|
+
pass `service: "<name>"`.
|
|
23
|
+
|
|
24
|
+
## 2. Each pair, operation by operation
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
get_contract_pair({{SCOPE}}, consumer_service: "<consumer>", producer_service: "<producer>")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Per operation the answer carries `producer.request` and `producer.response` as
|
|
31
|
+
type ids, `consumer.call_sites` with each site's `file_location`,
|
|
32
|
+
`expected_request` and `expected_response`, and `verdicts`. The `types` array
|
|
33
|
+
holds each distinct type text once, keyed by the id those rows reference.
|
|
34
|
+
Operations are ranked by call-site count then by untyped sides. Page with
|
|
35
|
+
`offset: <next_offset>`, or narrow with `path` and `method`.
|
|
36
|
+
|
|
37
|
+
A verdict's `state` is `compatible`, `incompatible` or `unresolved`, and it
|
|
38
|
+
carries `scanner_version` with `mismatch_reason` or `unresolved_reason` where
|
|
39
|
+
the scan wrote one. One verdict covers the whole consumer and producer pair, so
|
|
40
|
+
every call site on that operation shares it. An operation with `verdicts: []`
|
|
41
|
+
was never judged, and `verdict_note` says so.
|
|
42
|
+
|
|
43
|
+
Honour the wire note. Where `wire_notes` is present, one side declares a field
|
|
44
|
+
as `Date` and the other as `string`. JSON serialises a Date to a string, so
|
|
45
|
+
those two texts can describe the same bytes, and that is not drift.
|
|
46
|
+
|
|
47
|
+
## 3. Class each operation
|
|
48
|
+
|
|
49
|
+
- **MATCH**: the stored verdict is `compatible`.
|
|
50
|
+
- **DRIFT**: the stored verdict is `incompatible`. Name the field and the
|
|
51
|
+
direction, request or response, from `mismatch_reason` and the two type texts.
|
|
52
|
+
- **UNRESOLVED**: the stored verdict is `unresolved`. Report `unresolved_reason`
|
|
53
|
+
as it is written. Then read the two type texts this answer already returned in
|
|
54
|
+
`types`, the producer's side against the call site's expected type, and where
|
|
55
|
+
they differ name the field and say whether it is missing on one side, optional
|
|
56
|
+
on one side and required on the other, or of a different type. Report that as
|
|
57
|
+
"type texts differ", never as a verdict.
|
|
58
|
+
- **NOT JUDGED**: `verdicts` is empty. Relay `verdict_note`, then read the same
|
|
59
|
+
two type texts the same way and report any difference as "type texts differ".
|
|
60
|
+
- **CONSUMER UNTYPED**: a call site's `expected_request` or `expected_response`
|
|
61
|
+
is null on a side that carries one.
|
|
62
|
+
- **PRODUCER UNTYPED**: `producer.request` is null on a method that carries a
|
|
63
|
+
body, or `producer.response` is null.
|
|
64
|
+
|
|
65
|
+
A GET declares no request type by design, and `untyped_sides` already counts it
|
|
66
|
+
that way. The wire note covers both readings above, so a `Date` on one side
|
|
67
|
+
against a `string` on the other is not a difference.
|
|
68
|
+
|
|
69
|
+
## 4. Report
|
|
70
|
+
|
|
71
|
+
| operation | class | producer type | consumer type | call site | verdict |
|
|
72
|
+
|---|---|---|---|---|---|
|
|
73
|
+
| GET /api/orders/:id | DRIFT | Order | OrderSummary | web/src/orders.ts:31 | incompatible |
|
|
74
|
+
| POST /api/orders | UNRESOLVED | NewOrder | OrderDraft | web/src/orders.ts:52 | unresolved; type texts differ on `note` |
|
|
75
|
+
|
|
76
|
+
Class words, and only these: MATCH, DRIFT, UNRESOLVED, NOT JUDGED, CONSUMER
|
|
77
|
+
UNTYPED, PRODUCER UNTYPED. Every operation the answer returned carries one of
|
|
78
|
+
them, the class column is never empty, and the report states operations returned
|
|
79
|
+
against operations classed. Where more than one word fits an operation, the row
|
|
80
|
+
takes the first that applies of DRIFT, PRODUCER UNTYPED, CONSUMER UNTYPED,
|
|
81
|
+
UNRESOLVED, NOT JUDGED, MATCH, because a stored incompatible verdict is the
|
|
82
|
+
finding and a side carrying no type is why nothing past it could be judged.
|
|
83
|
+
A reading of the two type texts goes in the verdict
|
|
84
|
+
column beside the stored state, in the words "type texts differ", so nothing in
|
|
85
|
+
the table reads as a verdict the index did not give you.
|
|
86
|
+
|
|
87
|
+
State alongside it: `operations_total` against `operations_shown`,
|
|
88
|
+
`matched_calls` and `unmatched_calls`, `dropped_rows`, and the `non_http_note`
|
|
89
|
+
where the pair also carries GraphQL, socket or pub/sub operations, whose type
|
|
90
|
+
text this surface does not hold.
|
|
91
|
+
|
|
92
|
+
## 5. Act
|
|
93
|
+
|
|
94
|
+
Change no type unless you were asked to. Offer one issue per DRIFT, and one per
|
|
95
|
+
UNRESOLVED or NOT JUDGED row whose type texts differ. File the ones accepted:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
gh issue create --title "<consumer> and <producer> disagree on <field> of <operation>" --body "<producer type, consumer type, call sites, stored verdict and scanner version>"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
An UNRESOLVED or NOT JUDGED row holds no verdict on the difference you read, so
|
|
102
|
+
its title says the two sides may disagree and its body carries the reason the
|
|
103
|
+
tool gave in place of a stored verdict:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
gh issue create --title "<consumer> and <producer> may disagree on <field> of <operation>" --body "<producer type, consumer type, call sites, and the unresolved_reason or verdict_note as written>"
|
|
107
|
+
```
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: carrick-impact
|
|
3
|
+
description: Use before changing or removing a route, a handler, a response shape, an event, or a function other code calls, and whenever the ask is who calls this, who consumes this endpoint, or what breaks if I change it. Names every producer and consumer call site the Carrick index holds, with file and line.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Who depends on what you are about to change
|
|
7
|
+
|
|
8
|
+
{{SCOPE_NOTE}}
|
|
9
|
+
|
|
10
|
+
Carrick finds the call sites. Your work is to open them, decide whether each one
|
|
11
|
+
breaks, and act.
|
|
12
|
+
|
|
13
|
+
## 1. Name the thing
|
|
14
|
+
|
|
15
|
+
A route, a GraphQL field, a socket event or a pub/sub topic: its method label
|
|
16
|
+
and its path, as the index spells them. A function: its name, and the file it is
|
|
17
|
+
defined in.
|
|
18
|
+
|
|
19
|
+
## 2. Producers and consumers
|
|
20
|
+
|
|
21
|
+
For an operation:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
get_operation({{SCOPE}}, method: "<METHOD>", path: "<path>")
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Four sections carry the answer.
|
|
28
|
+
|
|
29
|
+
- `producers`: every service that serves it, with `file_location` and `source`.
|
|
30
|
+
- `consumers`: every call site that reaches it, with `services`,
|
|
31
|
+
`call_file_location`, `via` (the function the call is written through, where
|
|
32
|
+
the index names one) and `source`.
|
|
33
|
+
- `unmatched_calls`: recorded calls that resolved to no producer.
|
|
34
|
+
- the near-miss sections: rows the index holds under a neighbouring method or a
|
|
35
|
+
path one segment away. Read them as candidates to check in source, never as
|
|
36
|
+
consumers of the operation you asked for.
|
|
37
|
+
|
|
38
|
+
`source` is `fact: …` where a deterministic pass stated the row and
|
|
39
|
+
`candidate: …` where the model did. Both are claims about the same path, so the
|
|
40
|
+
label belongs in your table.
|
|
41
|
+
|
|
42
|
+
For a function:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
get_callers({{SCOPE}}, function_name: "<name>", file: "<file>", depth: 1)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Each row names the enclosing caller and its line span, not the call-site line.
|
|
49
|
+
Raise `depth` to 2 or 3 for transitive callers. Zero recorded callers is not
|
|
50
|
+
deletion evidence, because a function passed as a value is unmeasured.
|
|
51
|
+
|
|
52
|
+
## 3. Verdicts, one consumer at a time
|
|
53
|
+
|
|
54
|
+
For each consumer service the step above listed:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
check_compatibility({{SCOPE}}, consumer_service: "<consumer>", producer_service: "<producer>", path: "<path>")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Pass `path`. Without it a large producer returns hundreds of rows. Read
|
|
61
|
+
`type_verdicts` (`compatible`, `incompatible`, `unresolved`, `not_compared`) and
|
|
62
|
+
the `issues` rows for this operation. A `not_compared` pair has no stored
|
|
63
|
+
verdict, which is never agreement.
|
|
64
|
+
|
|
65
|
+
## 4. A file you have already edited
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
carrick check <file> --recheck --json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`items[]` holds this file's routes and calls with their `counterparts` and
|
|
72
|
+
`verdict`, judged against the working tree. `recheck.ran` says which answer came
|
|
73
|
+
back: `extraction+types` and `extraction` describe the tree, and `none` means
|
|
74
|
+
the rows are the indexed ones with `recheck.reason` saying why. `boundary_note`
|
|
75
|
+
states what the local run could not classify.
|
|
76
|
+
|
|
77
|
+
## 5. Report
|
|
78
|
+
|
|
79
|
+
One table, then the detail.
|
|
80
|
+
|
|
81
|
+
| consumer service | call site | verdict | source |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| admin-ui | src/api/orders.ts:44 | INCOMPATIBLE | fact |
|
|
84
|
+
|
|
85
|
+
Verdict words, and only these: COMPATIBLE, INCOMPATIBLE, UNRESOLVED,
|
|
86
|
+
NOT COMPARED. Every consumer call site the answers returned carries one of them
|
|
87
|
+
and the verdict column is never empty, and the report states call sites returned
|
|
88
|
+
against call sites given a verdict. In the same message, state:
|
|
89
|
+
|
|
90
|
+
- the producers, with file and line;
|
|
91
|
+
- unmatched calls and near misses, listed apart from consumers;
|
|
92
|
+
- the counts the responses carried: `consumer_count`, `consumers_outside_service`
|
|
93
|
+
where present, and `issues_total` against the rows you read.
|
|
94
|
+
|
|
95
|
+
Two limits belong in the report wherever they apply. A route added on your
|
|
96
|
+
branch has no consumers on main, so an empty consumer list says nothing about
|
|
97
|
+
it. A call whose URL is built at the call site can be recorded under the wrong
|
|
98
|
+
method, which is what the near-miss rows exist to show.
|
|
99
|
+
|
|
100
|
+
## 6. Act
|
|
101
|
+
|
|
102
|
+
Confirm each consumer in source before you call it broken. Change no consumer
|
|
103
|
+
code unless you were asked to. Then offer one issue per finding, and file the
|
|
104
|
+
ones accepted:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
gh issue create --title "<consumer> breaks on <METHOD> <path>" --body "<call site, what it expects, what the producer now sends>"
|
|
108
|
+
```
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: carrick-reuse
|
|
3
|
+
description: Use at the end of a task that added or changed functions, and whenever the ask is whether something already exists, whether this duplicates code elsewhere, or where this project has built the same thing twice. Compares against the Carrick function index rather than by name.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# What already does this
|
|
7
|
+
|
|
8
|
+
{{SCOPE_NOTE}}
|
|
9
|
+
|
|
10
|
+
`find_similar` does the comparison. Your work is to read the spans it names
|
|
11
|
+
and class every row it returned.
|
|
12
|
+
|
|
13
|
+
## Targeted: the functions this task added or changed
|
|
14
|
+
|
|
15
|
+
One call, up to 20 entries, run once at the end of the task.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
find_similar({{SCOPE}}, functions: [
|
|
19
|
+
{ name: "<Class.member or name>", file: "<path suffix>" },
|
|
20
|
+
{ description: "<one plain sentence about a function that is not indexed yet>" }
|
|
21
|
+
])
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
An entry is either a `name` with a `file` for a function the index holds, or a
|
|
25
|
+
`description` for code you are about to write or have just written. Use one or
|
|
26
|
+
the other in an entry, never both. A `name` that matches more than one
|
|
27
|
+
definition comes back with its candidates on that entry's `error`.
|
|
28
|
+
|
|
29
|
+
The two kinds are scored on different scales, and each result states the floor
|
|
30
|
+
it was ranked against. `vector_basis` says what the cosines are over. On
|
|
31
|
+
`intent` the vector is the intent sentence alone, and a copy somebody renamed
|
|
32
|
+
scores as close as one that kept its name. On `name_anchored` the function's
|
|
33
|
+
name sits in front of the sentence, a renamed copy scores lower, and
|
|
34
|
+
`intent_text` is the signal that still finds it. Read every score against the
|
|
35
|
+
floor and the basis in the answer you got.
|
|
36
|
+
|
|
37
|
+
## Audit: the whole project
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
find_similar({{SCOPE}})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`clusters` groups functions that describe the same behaviour, ordered by size.
|
|
44
|
+
Call again with `offset: <next_offset>` for as long as the response carries
|
|
45
|
+
`has_more`, and class what every page returned.
|
|
46
|
+
|
|
47
|
+
Where the project is larger than one pass, the response carries `error` in place
|
|
48
|
+
of clusters and names the two routes under the ceiling: a `service`, or a higher
|
|
49
|
+
`min_lines`. Take the route the response names and run it again. Where
|
|
50
|
+
`truncated` is present the audit is partial, and its `scanned_functions` of `of`
|
|
51
|
+
says by how much.
|
|
52
|
+
|
|
53
|
+
## Class every row
|
|
54
|
+
|
|
55
|
+
Every row the answer returned is classed here. In an audit the first member of a
|
|
56
|
+
cluster is what the rest of that cluster is classed against; in a targeted call
|
|
57
|
+
it is the function you asked about. Read that span at the file and line the
|
|
58
|
+
response gave, read each other row the same way, and take the first of these
|
|
59
|
+
that holds:
|
|
60
|
+
|
|
61
|
+
- **FALSE POSITIVE**: the two contracts differ. Different inputs, a different
|
|
62
|
+
result, or a different effect, and the intent sentences alone brought them
|
|
63
|
+
together.
|
|
64
|
+
- **VARIANT**: one contract, and a behavioural difference you can name in a
|
|
65
|
+
clause. A different normalisation, a different error path, a different
|
|
66
|
+
default. Write the clause in the row. Where a comment on the member or at the
|
|
67
|
+
head of its file names the file it mirrors, the clause is "documented
|
|
68
|
+
mirror".
|
|
69
|
+
- **DUPLICATE**: one contract, and nothing left to name. Two bodies that run
|
|
70
|
+
the same once the identifiers are renamed land here.
|
|
71
|
+
|
|
72
|
+
A cluster is transitive, so `lowest_similarity` can sit under the floor and a
|
|
73
|
+
large group can hold more than one idea. A member that shares no contract with
|
|
74
|
+
the first is FALSE POSITIVE on its own row, and stays in the table.
|
|
75
|
+
|
|
76
|
+
`matched_on` says which signal joined a row. `similarity` is the intent vectors;
|
|
77
|
+
`intent_text` is two identical intent sentences, which is the signal that still
|
|
78
|
+
finds a copy somebody renamed.
|
|
79
|
+
|
|
80
|
+
## Report
|
|
81
|
+
|
|
82
|
+
One row per match, and per cluster member beyond the first. The class column
|
|
83
|
+
carries one of the three words and is never empty.
|
|
84
|
+
|
|
85
|
+
| class | member | file:line | against | why |
|
|
86
|
+
|---|---|---|---|---|
|
|
87
|
+
| DUPLICATE | slugify | src/util/url.ts:4 | src/text.ts:12 | same replacement rules |
|
|
88
|
+
| VARIANT | slugTag | src/tags.ts:20 | src/text.ts:12 | documented mirror |
|
|
89
|
+
|
|
90
|
+
State `total_clusters` from the response against the number of clusters carrying
|
|
91
|
+
rows above. Where the two differ, name the clusters left out.
|
|
92
|
+
|
|
93
|
+
Then relay the counts the response stated, in its numbers: `compared_functions`,
|
|
94
|
+
and every key the answer carries under `not_compared` and under `excluded`.
|
|
95
|
+
|
|
96
|
+
Rows outside the comparison were not looked at, so an empty answer covers what
|
|
97
|
+
was compared and nothing further.
|
|
98
|
+
|
|
99
|
+
## Act
|
|
100
|
+
|
|
101
|
+
Merge nothing unless you were asked to. Offer one issue per DUPLICATE, and file
|
|
102
|
+
the ones accepted:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
gh issue create --title "Duplicate: <behaviour> in <n> files" --body "<each file:line, and which one should remain>"
|
|
106
|
+
```
|