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.
@@ -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
+ ```