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.
Files changed (79) hide show
  1. package/README.md +46 -19
  2. package/bin/carrick.mjs +45 -2
  3. package/dist/contract.d.ts +17 -0
  4. package/dist/contract.js.map +1 -1
  5. package/dist/hook/apply-patch.d.ts +13 -0
  6. package/dist/hook/apply-patch.js +100 -0
  7. package/dist/hook/apply-patch.js.map +1 -0
  8. package/dist/hook/post-edit.d.ts +30 -1
  9. package/dist/hook/post-edit.js +95 -24
  10. package/dist/hook/post-edit.js.map +1 -1
  11. package/dist/hook/reuse.d.ts +97 -0
  12. package/dist/hook/reuse.js +245 -0
  13. package/dist/hook/reuse.js.map +1 -0
  14. package/dist/hook/stop.d.ts +3 -0
  15. package/dist/hook/stop.js +76 -0
  16. package/dist/hook/stop.js.map +1 -0
  17. package/dist/hook/user-prompt.d.ts +9 -0
  18. package/dist/hook/user-prompt.js +79 -0
  19. package/dist/hook/user-prompt.js.map +1 -0
  20. package/dist/init/codex.d.ts +51 -0
  21. package/dist/init/codex.js +167 -0
  22. package/dist/init/codex.js.map +1 -0
  23. package/dist/init/connect.d.ts +13 -0
  24. package/dist/init/connect.js +21 -15
  25. package/dist/init/connect.js.map +1 -1
  26. package/dist/init/doctor.d.ts +36 -0
  27. package/dist/init/doctor.js +97 -2
  28. package/dist/init/doctor.js.map +1 -1
  29. package/dist/init/files.d.ts +16 -0
  30. package/dist/init/files.js +35 -0
  31. package/dist/init/files.js.map +1 -0
  32. package/dist/init/outdated.d.ts +54 -0
  33. package/dist/init/outdated.js +175 -0
  34. package/dist/init/outdated.js.map +1 -0
  35. package/dist/init/output.d.ts +35 -3
  36. package/dist/init/output.js +115 -23
  37. package/dist/init/output.js.map +1 -1
  38. package/dist/init/projects.d.ts +27 -13
  39. package/dist/init/projects.js +48 -50
  40. package/dist/init/projects.js.map +1 -1
  41. package/dist/init/remove.d.ts +0 -2
  42. package/dist/init/remove.js +91 -23
  43. package/dist/init/remove.js.map +1 -1
  44. package/dist/init/repos.d.ts +34 -0
  45. package/dist/init/repos.js +77 -0
  46. package/dist/init/repos.js.map +1 -1
  47. package/dist/init/run.d.ts +73 -6
  48. package/dist/init/run.js +382 -101
  49. package/dist/init/run.js.map +1 -1
  50. package/dist/init/settings.d.ts +20 -0
  51. package/dist/init/settings.js +42 -4
  52. package/dist/init/settings.js.map +1 -1
  53. package/dist/init/task-skills.d.ts +97 -0
  54. package/dist/init/task-skills.js +267 -0
  55. package/dist/init/task-skills.js.map +1 -0
  56. package/dist/init/workspace-file.d.ts +73 -0
  57. package/dist/init/workspace-file.js +173 -0
  58. package/dist/init/workspace-file.js.map +1 -0
  59. package/dist/scan.d.ts +13 -5
  60. package/dist/scan.js +21 -10
  61. package/dist/scan.js.map +1 -1
  62. package/dist/templates.d.ts +9 -0
  63. package/dist/templates.js +9 -1
  64. package/dist/templates.js.map +1 -1
  65. package/package.json +6 -6
  66. package/plugin/hooks/hooks.json +11 -0
  67. package/sidecar/dist/src/capture/check-classify.d.ts +10 -1
  68. package/sidecar/dist/src/capture/check-classify.js +66 -8
  69. package/sidecar/dist/src/capture/check-deep.d.ts +16 -4
  70. package/sidecar/dist/src/capture/check-deep.js +21 -17
  71. package/sidecar/dist/src/capture/check-fields.d.ts +83 -0
  72. package/sidecar/dist/src/capture/check-fields.js +259 -0
  73. package/sidecar/dist/src/capture/check-probe.d.ts +21 -1
  74. package/sidecar/dist/src/capture/check-probe.js +39 -0
  75. package/sidecar/dist/src/capture/check.js +9 -2
  76. package/templates/skills/carrick-census.md +89 -0
  77. package/templates/skills/carrick-drift.md +107 -0
  78. package/templates/skills/carrick-impact.md +108 -0
  79. 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
+ ```