1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
//! Method extraction — the OQ-1 seam.
//!
//! Why: a ticket/spec body is free prose; the matrix (spec §4) needs a
//! *method* (a prescribed approach/technique/constraint), not the whole body.
//! Extraction must be **conservative**: ambiguous text → `None`, never a
//! hallucinated constraint (spec §6.2).
//!
//! **OQ-1 decision (settled in C1, per spec §9 OQ-1 — "leans hybrid"):**
//! C1 ships a **heuristic-first, pluggable-extractor** design.
//! - The DEFAULT path is [`HeuristicMethodExtractor`] — pure-Rust pattern
//! matching over imperative-method phrasing. It requires **no network**, so
//! the resolver (and its tests) work fully offline. This is the right C1
//! default: zero cost/latency, no false-method risk from an LLM.
//! - The [`MethodExtractor`] trait is the pluggable seam. A future LLM-backed
//! extractor (adapting `trusty_common::chat::ChatProvider` with the fixed
//! classification prompt from spec §6.2) can be supplied by a caller without
//! touching the resolver — realising the "heuristic first, LLM fallback"
//! hybrid the spec recommends. We deliberately do NOT wire an LLM call by
//! default: it would make `resolve` network-dependent and is out of C1 scope.
//!
//! What: the `MethodExtractor` trait + the heuristic default + the standalone
//! `heuristic_method` function the spec-resolver reuses.
//! Test: `super::tests::extract_*`.
use ;
/// Pluggable method-extraction strategy (the OQ-1 seam).
///
/// Why: extraction policy (heuristic vs. LLM vs. hybrid) is the one genuinely
/// open design question (spec OQ-1). Modelling it as a trait lets a caller swap
/// strategies — including an LLM-backed one — without changing the resolver.
/// What: one method, `extract`, mapping a free-prose body to an optional
/// `Method`. Implementors MUST be conservative: ambiguous → `None`.
/// Test: `super::tests::extract_*` exercise the default impl.
/// The default, network-free heuristic extractor (OQ-1 default path).
///
/// Why: the common case ("use cursor-based pagination", "no new dependency",
/// "reuse the existing `ContextSource` trait") is recognisable from imperative
/// phrasing without an LLM, and a heuristic carries zero cost/latency and no
/// hallucination risk (spec §6.2, OQ-1).
/// What: a zero-field marker whose [`MethodExtractor::extract`] delegates to
/// [`heuristic_method`].
/// Test: `super::tests::extract_heuristic_*`.
;
/// Conservative, pure-Rust method extraction over imperative phrasing.
///
/// Why: shared by the ticket path (default extractor) and the spec path
/// (`spec_resolve::extract_spec_method`) so "what counts as a method" is
/// defined once (spec §6.2).
/// What: scans each line for a recognised method cue and, on the first match,
/// returns a `Method` classified by cue kind with the matched line as the
/// verbatim `source_excerpt`. Cues recognised:
/// - **Constraint:** "no new dep…", "do not …", "don't …", "must not …",
/// "never …", "without …".
/// - **Reuse:** "reuse …", "use the existing …".
/// - **Approach:** a narrow `use …` directive (a known technique lead like
/// "use cursor…"/"use offset…", or an article form like "use the existing
/// X" — *not* a Rust `use std::…;` import), "cursor[- ]based pagination", a
/// "method:"/"approach:" labelled line, or "gate … behind a feature flag".
///
/// Ambiguous prose with no cue → `None` (conservative).
///
/// Test: `super::tests::extract_heuristic_*`.
/// Classify a method cue, or return `None` when the line is not a method.
///
/// Why: keeping the cue taxonomy in one small function keeps
/// [`heuristic_method`] readable and makes the conservative bias auditable.
/// What: returns `Some(MethodKind)` for a recognised constraint/reuse/approach
/// cue; `None` otherwise. `lower` is the whole lowercased line (for phrase
/// matches); `cue` is the prefix-stripped variant (for leading-keyword matches).
/// Test: `super::tests::extract_heuristic_*`.
/// Whether a `use …` cue is a *prescribed approach*, not a Rust import.
///
/// Why: `cue.starts_with("use ")` is too broad — it captures Rust import
/// statements (`use std::collections::HashMap;`) and casual phrasing (`use
/// caution`) as `MethodKind::Approach`, fabricating a method the author never
/// prescribed (spec §6.2 conservative bias).
/// What: returns `true` only when the token after `use ` is a recognised
/// approach lead — a known technique noun (`cursor`, `offset`, `pagination`,
/// `token`, `cache`, `index`, …) or an article/determiner that introduces an
/// imperative directive (`the`, `a`, `an`). Rust import lines are excluded
/// because their follow-token is a `::`-qualified path or ends in `;`.
/// Test: `super::tests::extract_heuristic_use_import_not_approach`.