carrick 0.3.80 → 0.3.82
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/bin/carrick.mjs +47 -2
- package/dist/init/files.d.ts +1 -0
- package/dist/init/files.js +16 -0
- package/dist/init/files.js.map +1 -0
- package/dist/init/output.d.ts +20 -1
- package/dist/init/output.js +6 -2
- package/dist/init/output.js.map +1 -1
- package/dist/init/remove.js +36 -14
- package/dist/init/remove.js.map +1 -1
- package/dist/init/run.js +29 -11
- package/dist/init/run.js.map +1 -1
- package/dist/init/task-skills.d.ts +70 -0
- package/dist/init/task-skills.js +204 -0
- package/dist/init/task-skills.js.map +1 -0
- package/dist/scan.d.ts +160 -0
- package/dist/scan.js +415 -0
- package/dist/scan.js.map +1 -0
- 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/templates/skills/carrick-census.md +80 -0
- package/templates/skills/carrick-drift.md +101 -0
- package/templates/skills/carrick-impact.md +106 -0
- package/templates/skills/carrick-reuse.md +89 -0
|
@@ -0,0 +1,101 @@
|
|
|
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. A reading of the two type texts goes in the verdict
|
|
78
|
+
column beside the stored state, in the words "type texts differ", so nothing in
|
|
79
|
+
the table reads as a verdict the index did not give you.
|
|
80
|
+
|
|
81
|
+
State alongside it: `operations_total` against `operations_shown`,
|
|
82
|
+
`matched_calls` and `unmatched_calls`, `dropped_rows`, and the `non_http_note`
|
|
83
|
+
where the pair also carries GraphQL, socket or pub/sub operations, whose type
|
|
84
|
+
text this surface does not hold.
|
|
85
|
+
|
|
86
|
+
## 5. Act
|
|
87
|
+
|
|
88
|
+
Change no type unless you were asked to. Offer one issue per DRIFT, and one per
|
|
89
|
+
UNRESOLVED or NOT JUDGED row whose type texts differ. File the ones accepted:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
gh issue create --title "<consumer> and <producer> disagree on <field> of <operation>" --body "<producer type, consumer type, call sites, stored verdict and scanner version>"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
An UNRESOLVED or NOT JUDGED row holds no verdict on the difference you read, so
|
|
96
|
+
its title says the two sides may disagree and its body carries the reason the
|
|
97
|
+
tool gave in place of a stored verdict:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
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>"
|
|
101
|
+
```
|
|
@@ -0,0 +1,106 @@
|
|
|
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. In the same message, state:
|
|
87
|
+
|
|
88
|
+
- the producers, with file and line;
|
|
89
|
+
- unmatched calls and near misses, listed apart from consumers;
|
|
90
|
+
- the counts the responses carried: `consumer_count`, `consumers_outside_service`
|
|
91
|
+
where present, and `issues_total` against the rows you read.
|
|
92
|
+
|
|
93
|
+
Two limits belong in the report wherever they apply. A route added on your
|
|
94
|
+
branch has no consumers on main, so an empty consumer list says nothing about
|
|
95
|
+
it. A call whose URL is built at the call site can be recorded under the wrong
|
|
96
|
+
method, which is what the near-miss rows exist to show.
|
|
97
|
+
|
|
98
|
+
## 6. Act
|
|
99
|
+
|
|
100
|
+
Confirm each consumer in source before you call it broken. Change no consumer
|
|
101
|
+
code unless you were asked to. Then offer one issue per finding, and file the
|
|
102
|
+
ones accepted:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
gh issue create --title "<consumer> breaks on <METHOD> <path>" --body "<call site, what it expects, what the producer now sends>"
|
|
106
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
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 both spans and class
|
|
11
|
+
each pair.
|
|
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 the response states both
|
|
30
|
+
floors: 0.85 between two indexed functions, 0.45 for a description. A stored
|
|
31
|
+
vector carries the function's name in front of its intent and a bare sentence
|
|
32
|
+
does not, so a description scoring 0.5 is a hit worth reading.
|
|
33
|
+
|
|
34
|
+
## Audit: the whole project
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
find_similar({{SCOPE}})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`clusters` groups functions that describe the same behaviour, ordered by size.
|
|
41
|
+
Page with `offset: <next_offset>` while `has_more` is true. A group is
|
|
42
|
+
transitive, so `lowest_similarity` can sit under the floor and a large group can
|
|
43
|
+
hold more than one idea.
|
|
44
|
+
|
|
45
|
+
Where the project is larger than one pass, the response carries `error` in place
|
|
46
|
+
of clusters and names the two routes under the ceiling: a `service`, or a higher
|
|
47
|
+
`min_lines`. Take the route the response names and run it again. Where
|
|
48
|
+
`truncated` is present the audit is partial, and its `scanned_functions` of `of`
|
|
49
|
+
says by how much.
|
|
50
|
+
|
|
51
|
+
## Class each pair
|
|
52
|
+
|
|
53
|
+
Read both spans in source, then class:
|
|
54
|
+
|
|
55
|
+
- **DUPLICATE**: the same behaviour, and one call site could use the other.
|
|
56
|
+
- **VARIANT**: near neighbours that cannot share an implementation. Say in one
|
|
57
|
+
line why they cannot.
|
|
58
|
+
- **FALSE POSITIVE**: the index describes them alike and the code does different
|
|
59
|
+
work.
|
|
60
|
+
|
|
61
|
+
`matched_on` says which signal joined a row. `similarity` is the intent vectors;
|
|
62
|
+
`intent_text` is two identical intent sentences, which is the signal that still
|
|
63
|
+
finds a copy somebody renamed.
|
|
64
|
+
|
|
65
|
+
## Report
|
|
66
|
+
|
|
67
|
+
| class | function | file:line | pair | why |
|
|
68
|
+
|---|---|---|---|---|
|
|
69
|
+
| DUPLICATE | slugify | src/text.ts:12 | src/util/url.ts:4 | same replacement rules |
|
|
70
|
+
|
|
71
|
+
Then relay the counts the response stated, in its numbers:
|
|
72
|
+
|
|
73
|
+
- `compared_functions`, and `total_clusters` on an audit;
|
|
74
|
+
- `not_compared`: `without_intent`, `intent_not_embedded`, `awaiting_embedding`,
|
|
75
|
+
`model_mismatch`;
|
|
76
|
+
- `excluded`: `below_min_lines`, `tests`, `generated`, `callbacks`,
|
|
77
|
+
`other_service`.
|
|
78
|
+
|
|
79
|
+
Rows outside the comparison were not looked at, so an empty answer covers what
|
|
80
|
+
was compared and nothing further.
|
|
81
|
+
|
|
82
|
+
## Act
|
|
83
|
+
|
|
84
|
+
Merge nothing unless you were asked to. Offer one issue per DUPLICATE, and file
|
|
85
|
+
the ones accepted:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
gh issue create --title "Duplicate: <behaviour> in <n> files" --body "<each file:line, and which one should remain>"
|
|
89
|
+
```
|