- 1724
- 1725
// --------------------------------------------------------- extraction --- - 1726
- 1727
/// Tier 1 over a whole request read as one part. A pure function of - 1728
/// `request`; the resolver reads requests part by part through - 1729
/// [`extract_part`]. - 1730
pub fn extract(request: &Request<'_>) -> Extraction { - 1731
let prepared = prepare(request.text); - 1732
let clauses: Vec<ClauseRead> = crate::strand::segment(&prepared.instruction) - 1733
.into_iter() - 1734
.map(|clause| read_clause(&clause.text, clause.boundary, clause.question)) - 1735
.collect(); - 1736
let all: Vec<&ClauseRead> = clauses.iter().collect(); - 1737
extract_part( - 1738
request, - 1739
&all, - 1740
&prepared, - 1741
list_items(&prepared.instruction), - 1742
false, - 1743
) - 1744
} - 1745
- 1746
/// How many enumerated items the request holds. - 1747
pub(crate) fn list_items(instruction: &str) -> usize { - 1748
instruction - 1749
.lines() - 1750
.filter(|line| is_list_item(line)) - 1751
.count() - 1752
} - 1753
- 1754
/// Tier 1 over one part of a request: the clauses that make it up, and the - 1755
/// pasted material and enumeration of the request it belongs to. - 1756
pub(crate) fn extract_part( - 1757
request: &Request<'_>, - 1758
clauses: &[&ClauseRead], - 1759
prepared: &Prepared, - 1760
enumerated: usize, - 1761
earlier_parts: bool, - 1762
) -> Extraction { - 1763
let mut out = Extraction { - 1764
attendance: request - 1765
.attendance_override - 1766
.unwrap_or_else(|| request.surface.implied_attendance()), - 1767
..Extraction::default() - 1768
}; - 1769
out.signals.push(Signal::new( - 1770
SignalKind::Surface, - 1771
format!("surface:{}", request.surface.as_str()), - 1772
0.0, - 1773
format!( - 1774
"arrived on {} ⇒ {} by default", - 1775
request.surface.as_str(), - 1776
out.attendance.as_str() - 1777
), - 1778
)); - 1779
- 1780
let part_words: Vec<String> = clauses - 1781
.iter() - 1782
.flat_map(|clause| clause.tokens.words.iter().cloned()) - 1783
.collect(); - 1784
let checkable = part_words - 1785
.iter() - 1786
.any(|word| CHECKABLE_WORDS.contains(&word.as_str())); - 1787
let mut asks = false; - 1788
let mut work = false; - 1789
- 1790
for clause in clauses { - 1791
for stripped in &clause.stripped { - 1792
out.signals.push(Signal::new( - 1793
SignalKind::Lexical, - 1794
"stripped", - 1795
0.0, - 1796
format!("{stripped} set aside"), - 1797
)); - 1798
} - 1799
if clause.greeting > 0.0 { - 1800
out.act.add(Act::Converse, clause.greeting); - 1801
out.signals.push(Signal::new( - 1802
SignalKind::Lexical, - 1803
"social:greeting", - 1804
clause.greeting, - 1805
"a greeting ⇒ converse".to_string(), - 1806
)); - 1807
} - 1808
work |= clause.kind.is_work(); - 1809
read_acts(clause, &mut out); - 1810
if clause.kind == ClauseKind::Question { - 1811
asks = true; - 1812
} - 1813
read_stakes_and_evidence(clause, checkable, &mut out); - 1814
read_recency(clause, &mut out); - 1815
if matches!(clause.kind, ClauseKind::Imperative | ClauseKind::Request) { - 1816
read_horizon(clause, &mut out); - 1817
} - 1818
read_structure(clause, &mut out); - 1819
} - 1820
if asks { - 1821
out.signals.push(Signal::new( - 1822
SignalKind::Structural, - 1823
"question", - 1824
0.0, - 1825
"the part asks a question", - 1826
)); - 1827
} - 1828
- 1829
// A greeting and nothing else is one reply. - 1830
if !work - 1831
&& out - 1832
.act - 1833
.ranked() - 1834
.iter() - 1835
.all(|(act, _)| *act == Act::Converse) - 1836
&& !out.act.is_empty() - 1837
{ - 1838
out.horizon.add(Horizon::Immediate, 0.6); - 1839
out.signals.push(Signal::new( - 1840
SignalKind::Structural, - 1841
"social-only", - 1842
0.6, - 1843
"social talk and nothing else ⇒ immediate", - 1844
)); - 1845
} - 1846
- 1847
// --- material --------------------------------------------------------- - 1848
if prepared.fenced_blocks > 0 { - 1849
out.act.add(Act::Modify, 0.3); - 1850
out.signals.push(Signal::new( - 1851
SignalKind::Structural, - 1852
"code-fence", - 1853
0.3, - 1854
format!("{} fenced block(s) set aside", prepared.fenced_blocks), - 1855
)); - 1856
} - 1857
if prepared.pasted_lines > 0 { - 1858
out.input_modalities.push(Modality::Data); - 1859
out.signals.push(Signal::new( - 1860
SignalKind::Structural, - 1861
"pasted-material", - 1862
0.0, - 1863
format!( - 1864
"{} pasted line(s) read as material, not instructions", - 1865
prepared.pasted_lines - 1866
), - 1867
)); - 1868
} - 1869
// Enumerated steps are the honest multi-step marker: a list the user - 1870
// wrote themselves. Multi-step is a session, not a durable obligation. - 1871
if enumerated >= 2 && work { - 1872
out.horizon.add(Horizon::Session, 0.9); - 1873
out.signals.push(Signal::new( - 1874
SignalKind::Structural, - 1875
"enumerated-steps", - 1876
0.9, - 1877
format!("{enumerated} enumerated items ⇒ session"), - 1878
)); - 1879
} - 1880
- 1881
// --- deixis ----------------------------------------------------------- - 1882
let deictic: Vec<&str> = DEICTIC_WORDS - 1883
.iter() - 1884
.copied() - 1885
.filter(|w| part_words.iter().any(|t| t == w)) - 1886
.collect(); - 1887
out.deictic = !deictic.is_empty(); - 1888
if !deictic.is_empty() { - 1889
let bare = clauses.iter().any(|clause| { - 1890
clause.kind.is_work() - 1891
&& clause - 1892
.tokens - 1893
.words - 1894
.last() - 1895
.is_some_and(|last| BARE_PRONOUNS.contains(&last.as_str())) - 1896
}); - 1897
// Pointing at something is only ambiguous when there is nothing to - 1898
// point at: the first part of a first message with no attachment. - 1899
// Mid-conversation, after an earlier part of the same message ("explain - 1900
// the parser, then refactor it"), or with an attached file, it is - 1901
// ordinary and clear. - 1902
if request.history.turn_index == 0 && request.attachments.is_empty() && !earlier_parts { - 1903
if bare { - 1904
out.clarity.add(Clarity::Ambiguous, 0.7); - 1905
out.signals.push(Signal::new( - 1906
SignalKind::Deictic, - 1907
"unresolved-reference", - 1908
0.7, - 1909
format!("`{}` with no prior context", deictic.join("`, `")), - 1910
)); - 1911
} - 1912
} else { - 1913
out.clarity.add(Clarity::Clear, 0.4); - 1914
out.signals.push(Signal::new( - 1915
SignalKind::Deictic, - 1916
"resolved-reference", - 1917
0.4, - 1918
format!("`{}` resolvable from context", deictic.join("`, `")), - 1919
)); - 1920
} - 1921
} - 1922
- 1923
// --- workspace -------------------------------------------------------- - 1924
if request.workspace.is_repo { - 1925
out.domains_from_environment.push("engineering".into()); - 1926
out.signals.push(Signal::new( - 1927
SignalKind::Workspace, - 1928
"git-repo", - 1929
0.3, - 1930
"workspace is a git repository", - 1931
)); - 1932
} - 1933
if request.workspace.has_uncommitted_changes { - 1934
out.stakes_from_environment.add(Stakes::Reversible, 0.5); - 1935
out.signals.push(Signal::new( - 1936
SignalKind::Workspace, - 1937
"dirty-tree", - 1938
0.5, - 1939
"uncommitted changes present", - 1940
)); - 1941
} - 1942
- 1943
// --- session ---------------------------------------------------------- - 1944
if let Some(previous) = request.history.previous_act { - 1945
// Continuity is a real prior, but a weak one: people change subject. - 1946
out.act.add(previous, 0.25); - 1947
out.signals.push(Signal::new( - 1948
SignalKind::Session, - 1949
format!("continues:{}", previous.as_str()), - 1950
0.25, - 1951
format!("previous turn read as {}", previous.as_str()), - 1952
)); - 1953
} - 1954
- 1955
// --- attachments ------------------------------------------------------ - 1956
for attachment in request.attachments { - 1957
out.input_modalities.push(attachment.modality); - 1958
out.signals.push(Signal::new( - 1959
SignalKind::Attachment, - 1960
format!("input:{}", attachment.modality.as_str()), - 1961
1.0, - 1962
format!( - 1963
"attached {} `{}`", - 1964
attachment.modality.as_str(), - 1965
attachment.name - 1966
), - 1967
)); - 1968
if attachment.modality != Modality::Text { - 1969
out.act.add(Act::Analyze, 0.4); - 1970
} - 1971
} - 1972
if !out.input_modalities.contains(&Modality::Text) { - 1973
out.input_modalities.push(Modality::Text); - 1974
} - 1975
out.output_modalities.push(Modality::Text); - 1976
- 1977
out.input_modalities.sort(); - 1978
out.input_modalities.dedup(); - 1979
out.output_modalities.sort(); - 1980
out.output_modalities.dedup(); - 1981
out.domains.sort(); - 1982
out.domains.dedup(); - 1983
out - 1984
} - 1985
- 1986
fn read_acts(clause: &ClauseRead, out: &mut Extraction) { - 1987
let tokens = &clause.tokens; - 1988
for (target, act, weight) in VERB_TARGETS.iter() { - 1989
let Some(position) = tokens.position(target, &clause.consumed) else { - 1990
continue; - 1991
}; - 1992
let head = clause.is_head(position); - 1993
let weight = match clause.kind { - 1994
// A statement's first word is its subject, and its other nouns - 1995
// are not verbs; only a verb heading a later sub-clause ("it - 1996
// crashes, fix it") asks for anything. - 1997
ClauseKind::Statement if !head || position == 0 => continue, - 1998
ClauseKind::Social if !head => continue, - 1999
// In a question the head asks; other verbs are what it asks - 2000
// about ("did the email send?") and count for half. - 2001
ClauseKind::Question if !head => weight * 0.5, - 2002
_ if head => weight * IMPERATIVE_BONUS, - 2003
_ => *weight, - 2004
}; - 2005
// "Alert me", "send me the report": delivering the result to the - 2006
// person asking is an answer, not an operation on someone else. - 2007
let to_requester = *act == Act::Operate - 2008
&& tokens - 2009
.get(position + 1) - 2010
.is_some_and(|next| REQUESTER.contains(&next)); - 2011
let (act, weight) = if to_requester { - 2012
(Act::Answer, weight * 0.5) - 2013
} else { - 2014
(*act, weight) - 2015
}; - 2016
out.act.add(act, weight); - 2017
out.signals.push(Signal::new( - 2018
SignalKind::Lexical, - 2019
format!("verb:{}", target.word), - 2020
weight, - 2021
if to_requester { - 2022
format!("`{} me` delivers to the requester ⇒ answer", target.word) - 2023
} else if position == 0 { - 2024
format!("`{}` (leading verb) ⇒ {}", target.word, act.as_str()) - 2025
} else if head { - 2026
format!("`{}` (clause head verb) ⇒ {}", target.word, act.as_str()) - 2027
} else { - 2028
format!("`{}` ⇒ {}", target.word, act.as_str()) - 2029
}, - 2030
)); - 2031
} - 2032
if clause.kind == ClauseKind::Question { - 2033
let first = tokens.get(0).unwrap_or(""); - 2034
if !WH_WORDS.contains(&first) { - 2035
let weight = if AUX_WORDS.contains(&first) { - 2036
0.8 * IMPERATIVE_BONUS - 2037
} else { - 2038
0.8 - 2039
}; - 2040
out.act.add(Act::Answer, weight); - 2041
out.signals.push(Signal::new( - 2042
SignalKind::Lexical, - 2043
"question:head", - 2044
weight, - 2045
if AUX_WORDS.contains(&first) { - 2046
format!("`{first}` opens a question ⇒ answer") - 2047
} else { - 2048
"a question mark ⇒ answer".to_string() - 2049
}, - 2050
)); - 2051
} - 2052
} - 2053
for (phrase, weight) in SOCIAL_PHRASES { - 2054
if tokens.phrase_at(&split_phrase(phrase)).is_some() { - 2055
out.act.add(Act::Converse, *weight); - 2056
out.signals.push(Signal::new( - 2057
SignalKind::Lexical, - 2058
format!("social:{}", phrase.replace(' ', "-")), - 2059
*weight, - 2060
format!("`{phrase}` ⇒ converse"), - 2061
)); - 2062
} - 2063
} - 2064
if clause.kind == ClauseKind::Social - 2065
&& let Some(first) = tokens.get(0) - 2066
&& let Some((word, weight)) = SOCIAL_WORDS.iter().find(|(word, _)| *word == first) - 2067
{ - 2068
out.act.add(Act::Converse, *weight); - 2069
out.signals.push(Signal::new( - 2070
SignalKind::Lexical, - 2071
format!("social:{word}"), - 2072
*weight, - 2073
format!("`{word}` ⇒ converse"), - 2074
)); - 2075
} - 2076
} - 2077
- 2078
fn read_stakes_and_evidence(clause: &ClauseRead, checkable: bool, out: &mut Extraction) { - 2079
let tokens = &clause.tokens; - 2080
for (phrase, stakes, weight) in STAKES_WORDS { - 2081
let words = split_phrase(phrase); - 2082
let hit = if words.len() > 1 { - 2083
tokens.phrase_at(&words).is_some() - 2084
} else { - 2085
tokens.position(&Target::new(phrase), &[]).is_some() - 2086
}; - 2087
if hit { - 2088
out.stakes_from_words.add(*stakes, *weight); - 2089
out.signals.push(Signal::new( - 2090
SignalKind::Lexical, - 2091
format!("stakes:{}", phrase.replace(' ', "-")), - 2092
*weight, - 2093
format!("`{phrase}` ⇒ {}", stakes.as_str()), - 2094
)); - 2095
} - 2096
} - 2097
for (phrase, evidence, weight) in EVIDENCE_WORDS { - 2098
let words = split_phrase(phrase); - 2099
let hit = if words.len() > 1 { - 2100
tokens.phrase_at(&words).is_some() - 2101
} else if EXACT_EVIDENCE_WORDS.contains(phrase) { - 2102
tokens.words.iter().any(|t| t == phrase) - 2103
} else { - 2104
tokens.position(&Target::new(phrase), &[]).is_some() - 2105
}; - 2106
if hit { - 2107
out.evidence.add(*evidence, *weight); - 2108
out.signals.push(Signal::new( - 2109
SignalKind::Lexical, - 2110
format!("evidence:{}", phrase.replace(' ', "-")), - 2111
*weight, - 2112
format!("`{phrase}` ⇒ {}", evidence.as_str()), - 2113
)); - 2114
} - 2115
} - 2116
for (phrase, weight) in ASSURANCE_PHRASES { - 2117
let words = split_phrase(phrase); - 2118
let hit = if words.len() > 1 { - 2119
tokens.phrase_at(&words).is_some() - 2120
} else { - 2121
tokens.position(&Target::new(phrase), &[]).is_some() - 2122
}; - 2123
if hit && checkable { - 2124
out.evidence.add(Evidence::Verified, *weight); - 2125
out.signals.push(Signal::new( - 2126
SignalKind::Lexical, - 2127
format!("evidence:{}", phrase.replace(' ', "-")), - 2128
*weight, - 2129
format!("`{phrase}` something checkable ⇒ verified"), - 2130
)); - 2131
} - 2132
} - 2133
} - 2134
- 2135
fn read_recency(clause: &ClauseRead, out: &mut Extraction) { - 2136
let tokens = &clause.tokens; - 2137
let only_live_is_current = - 2138
live_means_current(&tokens.words) && tokens.words.iter().any(|word| word == LIVE_WORD); - 2139
if TIME_WORDS - 2140
.iter() - 2141
.any(|word| tokens.words.iter().any(|t| t == word)) - 2142
&& !only_live_is_current - 2143
{ - 2144
return; - 2145
} - 2146
for (phrase, weight) in RECENCY_PHRASES { - 2147
let words = split_phrase(phrase); - 2148
let Some(at) = tokens.phrase_at(&words) else { - 2149
continue; - 2150
}; - 2151
// "We live in the city" is not a question about time. - 2152
if *phrase == LIVE_WORD && !live_means_current(&tokens.words) { - 2153
continue; - 2154
} - 2155
let next = tokens.get(at + words.len()).unwrap_or(""); - 2156
// "today's": the possessive splits into `today` `s`; look past it. - 2157
let next = if next == "s" { - 2158
tokens.get(at + words.len() + 1).unwrap_or("") - 2159
} else { - 2160
next - 2161
}; - 2162
// The thing that is current: the noun after the word ("the current - 2163
// directory"), just before it ("is my branch up to date"), or what a - 2164
// wh-question asks about ("what tasks are scheduled right now"). - 2165
let before = (at.saturating_sub(2)..at).filter_map(|i| tokens.get(i)); - 2166
let asked_about = tokens - 2167
.get(0) - 2168
.filter(|first| matches!(*first, "what" | "which")) - 2169
.and_then(|_| tokens.get(1)); - 2170
if LOCAL_NOUNS.contains(&next) - 2171
|| before.into_iter().any(|word| LOCAL_NOUNS.contains(&word)) - 2172
|| asked_about.is_some_and(|word| LOCAL_NOUNS.contains(&word)) - 2173
{ - 2174
continue; - 2175
} - 2176
if out.recency.as_ref().is_none_or(|(_, best)| *weight > *best) { - 2177
out.recency = Some((phrase.to_string(), *weight)); - 2178
} - 2179
} - 2180
if let Some((phrase, weight)) = out.recency.clone() - 2181
&& !out - 2182
.signals - 2183
.iter() - 2184
.any(|s| s.name == format!("recency:{}", phrase.replace(' ', "-"))) - 2185
{ - 2186
out.signals.push(Signal::new( - 2187
SignalKind::Lexical, - 2188
format!("recency:{}", phrase.replace(' ', "-")), - 2189
weight, - 2190
format!("`{phrase}` ⇒ a current value, if the part asks for a fact"), - 2191
)); - 2192
} - 2193
} - 2194
- 2195
fn read_horizon(clause: &ClauseRead, out: &mut Extraction) { - 2196
let tokens = &clause.tokens; - 2197
for (phrase, horizon, weight) in HORIZON_PHRASES { - 2198
// A recurrence adjective names a thing, not a schedule: "the nightly - 2199
// job", "our daily report". Only the adverb says the work recurs. - 2200
if RECURRENCE_ADJECTIVES.contains(phrase) - 2201
&& tokens - 2202
.words - 2203
.windows(2) - 2204
.any(|pair| DETERMINERS.contains(&pair[0].as_str()) && pair[1] == *phrase) - 2205
&& !tokens - 2206
.words - 2207
.windows(2) - 2208
.any(|pair| !DETERMINERS.contains(&pair[0].as_str()) && pair[1] == *phrase) - 2209
&& tokens.get(0) != Some(phrase) - 2210
{ - 2211
continue; - 2212
} - 2213
if tokens.phrase_at(&split_phrase(phrase)).is_some() { - 2214
out.horizon.add(*horizon, *weight); - 2215
out.signals.push(Signal::new( - 2216
SignalKind::Lexical, - 2217
format!("horizon:{}", phrase.replace(' ', "-")), - 2218
*weight, - 2219
format!("`{phrase}` ⇒ {}", horizon.as_str()), - 2220
)); - 2221
} - 2222
} - 2223
} - 2224
- 2225
fn read_structure(clause: &ClauseRead, out: &mut Extraction) { - 2226
let text = &clause.text; - 2227
let lower = text.to_ascii_lowercase(); - 2228
if lower.contains("http://") || lower.contains("https://") { - 2229
out.act.add(Act::Analyze, 0.3); - 2230
out.signals.push(Signal::new( - 2231
SignalKind::Structural, - 2232
"url", - 2233
0.3, - 2234
"a URL to fetch", - 2235
)); - 2236
} - 2237
let path_like = text - 2238
.split_whitespace() - 2239
.any(|w| w.contains('/') && w.contains('.') && !w.contains("://")); - 2240
if path_like { - 2241
out.signals.push(Signal::new( - 2242
SignalKind::Structural, - 2243
"path-mention", - 2244
0.4, - 2245
"a file path is named", - 2246
)); - 2247
out.act.add(Act::Modify, 0.2); - 2248
out.clarity.add(Clarity::Clear, 0.5); - 2249
} - 2250
} - 2251
- 2252
#[cfg(test)] - 2253
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - 2254
mod tests { - 2255
use super::*; - 2256
- 2257
fn request<'a>(text: &'a str) -> Request<'a> { - 2258
Request { - 2259
text, - 2260
..Request::default() - 2261
} - 2262
} - 2263
- 2264
#[test] - 2265
fn temporal_deixis_is_recorded_and_local_nouns_are_not() { - 2266
let now = extract(&request("what is the current price of copper")); - 2267
assert_eq!(now.recency.as_ref().map(|r| r.0.as_str()), Some("current")); - 2268
assert!(now.signals.iter().any(|s| s.name.starts_with("recency:"))); - 2269
// The evidence standard is left to the request's own words. - 2270
assert_ne!(now.evidence.winner().map(|w| w.0), Some(Evidence::Cited)); - 2271
- 2272
for local in [ - 2273
"what's in the current directory", - 2274
"update the README to reflect the latest changes", - 2275
"fix the live reload bug", - 2276
"what time is it right now", - 2277
"what's today's date", - 2278
] { - 2279
assert!(extract(&request(local)).recency.is_none(), "{local}"); - 2280
} - 2281
assert!( - 2282
extract(&request("explain how copper is refined")) - 2283
.recency - 2284
.is_none() - 2285
); - 2286
} - 2287
- 2288
#[test] - 2289
fn live_as_reside_is_neither_current_nor_irreversible() { - 2290
for text in [ - 2291
"Help me plan Saturday with the kids. They are 6 and 9, we live in the city, and I would like a couple of options for the afternoon.", - 2292
"deploy the site to wherever I live near", - 2293
"update the doc: my son lives with his grandparents", - 2294
"rewrite the letter, we have lived in Pune for years", - 2295
"fix the budget for living costs", - 2296
"where do you live", - 2297
"change the address, my kids live in the suburbs", - 2298
] { - 2299
let x = extract(&request(text)); - 2300
assert!(x.recency.is_none(), "{text}: {:?}", x.recency); - 2301
assert!( - 2302
!x.signals - 2303
.iter() - 2304
.any(|s| s.name.starts_with("stakes:") && s.name.contains("live")), - 2305
"{text}" - 2306
); - 2307
} - 2308
} - 2309
- 2310
/// `live` in the sense of *current* is a recency word — whether it asks - 2311
/// for live data is then the act's question (a request for a fact) — and - 2312
/// "go live" is a launch, which raises stakes for the work that does it. - 2313
#[test] - 2314
fn live_as_current_still_counts() { - 2315
for text in [ - 2316
"what is the live score of the match", - 2317
"show me the live price", - 2318
"is it live", - 2319
"find a live stream of the launch", - 2320
"deploy it, the site is live in production", - 2321
"we go live in an hour, deploy the fix", - 2322
] { - 2323
let x = extract(&request(text)); - 2324
assert!( - 2325
x.recency - 2326
.as_ref() - 2327
.is_some_and(|(phrase, _)| phrase == "live"), - 2328
"{text}: {:?}", - 2329
x.recency - 2330
); - 2331
} - 2332
let launch = extract(&request("we go live in an hour, deploy the fix")); - 2333
assert!( - 2334
launch.signals.iter().any(|s| s.name == "stakes:go-live"), - 2335
"{:?}", - 2336
launch.signals - 2337
); - 2338
} - 2339
- 2340
#[test] - 2341
fn extraction_is_a_pure_function_of_its_input() { - 2342
let a = extract(&request("refactor the parser and run the tests")); - 2343
let b = extract(&request("refactor the parser and run the tests")); - 2344
assert_eq!(a.signals, b.signals); - 2345
assert_eq!(a.act.winner().map(|w| w.0), b.act.winner().map(|w| w.0)); - 2346
} - 2347
- 2348
#[test] - 2349
fn a_greeting_reads_as_converse_and_immediate() { - 2350
let extraction = extract(&request("hi")); - 2351
assert_eq!(extraction.act.winner().map(|w| w.0), Some(Act::Converse)); - 2352
assert_eq!( - 2353
extraction.horizon.winner().map(|w| w.0), - 2354
Some(Horizon::Immediate) - 2355
); - 2356
} - 2357
- 2358
#[test] - 2359
fn a_greeting_in_front_of_a_question_does_not_take_it_over() { - 2360
let extraction = extract(&request( - 2361
"hey, what did we decide about the schema yesterday?", - 2362
)); - 2363
assert_eq!(extraction.act.winner().map(|w| w.0), Some(Act::Answer)); - 2364
let extraction = extract(&request("hey can you check the logs")); - 2365
assert_eq!(extraction.act.winner().map(|w| w.0), Some(Act::Verify)); - 2366
let extraction = extract(&request("hello there, how are you doing today")); - 2367
assert_eq!(extraction.act.winner().map(|w| w.0), Some(Act::Converse)); - 2368
} - 2369
- 2370
#[test] - 2371
fn production_words_raise_stakes_whatever_the_verb() { - 2372
let extraction = extract(&request("deploy the service to production")); - 2373
assert_eq!(extraction.act.winner().map(|w| w.0), Some(Act::Operate)); - 2374
assert_eq!( - 2375
extraction.stakes_from_words.winner().map(|w| w.0), - 2376
Some(Stakes::Irreversible) - 2377
); - 2378
} - 2379
- 2380
#[test] - 2381
fn topic_nouns_do_not_raise_stakes() { - 2382
for text in [ - 2383
"rename the Customer struct to Client", - 2384
"fix the payment form validation", - 2385
"make the banner visible to everyone", - 2386
"the query is expensive, optimize it", - 2387
] { - 2388
assert!( - 2389
extract(&request(text)).stakes_from_words.winner().is_none(), - 2390
"{text}" - 2391
); - 2392
} - 2393
} - 2394
- 2395
#[test] - 2396
fn unknown_input_abstains_rather_than_guessing() { - 2397
let extraction = extract(&request("zorble the frobnicator")); - 2398
assert!(extraction.act.is_empty(), "lexicon invented a reading"); - 2399
} - 2400
- 2401
#[test] - 2402
fn a_statement_casts_no_act_vote() { - 2403
let extraction = extract(&request("the deploy script is broken")); - 2404
assert!(extraction.act.is_empty(), "{:?}", extraction.act.ranked()); - 2405
let extraction = extract(&request("it crashes on empty input, fix it")); - 2406
assert_eq!(extraction.act.winner().map(|w| w.0), Some(Act::Modify)); - 2407
} - 2408
- 2409
#[test] - 2410
fn a_question_about_an_effect_is_a_question() { - 2411
let extraction = extract(&request("Did the email send?")); - 2412
assert_eq!(extraction.act.winner().map(|w| w.0), Some(Act::Answer)); - 2413
let extraction = extract(&request("is the deploy broken")); - 2414
assert_eq!(extraction.act.winner().map(|w| w.0), Some(Act::Answer)); - 2415
// A request that is phrased as a question is still a request. - 2416
let extraction = extract(&request("can you deploy the service?")); - 2417
assert_eq!(extraction.act.winner().map(|w| w.0), Some(Act::Operate)); - 2418
} - 2419
- 2420
#[test] - 2421
fn conjunctions_alone_do_not_imply_a_long_horizon() { - 2422
let extraction = extract(&request("explain what this and that mean")); - 2423
let horizon = extraction.horizon.winner().map(|w| w.0); - 2424
assert_ne!(horizon, Some(Horizon::Durable)); - 2425
assert_ne!(horizon, Some(Horizon::Session)); - 2426
} - 2427
- 2428
#[test] - 2429
fn recurrence_phrases_imply_a_durable_horizon_for_requests_only() { - 2430
let extraction = extract(&request("check the cloud bill every day and alert me")); - 2431
assert_eq!( - 2432
extraction.horizon.winner().map(|w| w.0), - 2433
Some(Horizon::Durable) - 2434
); - 2435
for question in [ - 2436
"what happens whenever I press ctrl-c in the REPL?", - 2437
"why does the test fail continuously on CI?", - 2438
] { - 2439
assert_ne!( - 2440
extract(&request(question)).horizon.winner().map(|w| w.0), - 2441
Some(Horizon::Durable), - 2442
"{question}" - 2443
); - 2444
} - 2445
} - 2446
- 2447
#[test] - 2448
fn a_cron_surface_is_unattended_and_a_cli_is_interactive() { - 2449
let mut cron = request("run the nightly sweep"); - 2450
cron.surface = Surface::Cron; - 2451
assert_eq!(extract(&cron).attendance, Attendance::Unattended); - 2452
assert_eq!(extract(&request("hi")).attendance, Attendance::Interactive); - 2453
} - 2454
- 2455
#[test] - 2456
fn an_explicit_attendance_override_beats_the_surface_guess() { - 2457
let mut req = request("do the thing"); - 2458
req.surface = Surface::Cron; - 2459
req.attendance_override = Some(Attendance::Interactive); - 2460
assert_eq!(extract(&req).attendance, Attendance::Interactive); - 2461
} - 2462
- 2463
#[test] - 2464
fn attachments_become_required_input_modalities() { - 2465
let attachments = vec![Attachment { - 2466
modality: Modality::Image, - 2467
name: "screenshot.png".into(), - 2468
}]; - 2469
let mut req = request("what is wrong here"); - 2470
req.attachments = &attachments; - 2471
let extraction = extract(&req); - 2472
assert!(extraction.input_modalities.contains(&Modality::Image)); - 2473
assert!(extraction.input_modalities.contains(&Modality::Text)); - 2474
} - 2475
- 2476
#[test] - 2477
fn only_a_bare_pronoun_is_ambiguous_and_only_without_context() { - 2478
let cold = extract(&request("fix this")); - 2479
assert_eq!(cold.clarity.winner().map(|w| w.0), Some(Clarity::Ambiguous)); - 2480
- 2481
// `that` as a relative pronoun points at nothing. - 2482
let relative = extract(&request("Write a function that parses ISO dates")); - 2483
assert_ne!( - 2484
relative.clarity.winner().map(|w| w.0), - 2485
Some(Clarity::Ambiguous) - 2486
); - 2487
- 2488
let mut warm = request("fix this"); - 2489
warm.history.turn_index = 3; - 2490
warm.history.previous_act = Some(Act::Locate); - 2491
assert_eq!( - 2492
extract(&warm).clarity.winner().map(|w| w.0), - 2493
Some(Clarity::Clear) - 2494
); - 2495
} - 2496
- 2497
#[test] - 2498
fn pasted_material_is_set_aside() { - 2499
let mut log = String::from("why is this service failing? here is the log:\n"); - 2500
for i in 0..40 { - 2501
log.push_str(&format!( - 2502
"2026-09-26T10:00:{i:02}Z INFO worker deploy job {i} for customer account\n" - 2503
)); - 2504
} - 2505
let prepared = prepare(&log); - 2506
assert_eq!(prepared.pasted_lines, 40); - 2507
assert!(!prepared.instruction.contains("worker deploy")); - 2508
let extraction = extract(&request(&log)); - 2509
assert_eq!(extraction.act.winner().map(|w| w.0), Some(Act::Answer)); - 2510
assert!(extraction.input_modalities.contains(&Modality::Data)); - 2511
- 2512
let fenced = "what does this do?\n```rust\nfn deploy() { send(); }\n```"; - 2513
let prepared = prepare(fenced); - 2514
assert_eq!(prepared.fenced_blocks, 1); - 2515
assert_eq!(prepared.instruction, "what does this do?"); - 2516
} - 2517
- 2518
#[test] - 2519
fn hard_wrapped_prose_is_kept() { - 2520
let text = "please update the config so that\nthe retries are bounded and the timeout\nis configurable per provider"; - 2521
let prepared = prepare(text); - 2522
assert_eq!(prepared.pasted_lines, 0, "{prepared:?}"); - 2523
} - 2524
- 2525
#[test] - 2526
fn categorical_confidence_reflects_margin_not_just_score() { - 2527
let mut tied: Votes<Act> = Votes::default(); - 2528
tied.add(Act::Answer, 2.0); - 2529
tied.add(Act::Modify, 2.0); - 2530
let (_, tied_confidence) = tied.winner().unwrap(); - 2531
- 2532
let mut clear: Votes<Act> = Votes::default(); - 2533
clear.add(Act::Modify, 2.0); - 2534
let (_, clear_confidence) = clear.winner().unwrap(); - 2535
- 2536
assert!(tied_confidence < clear_confidence); - 2537
} - 2538
- 2539
/// On an ordered axis a weaker level *corroborates* a stronger one. - 2540
#[test] - 2541
fn a_lower_level_never_argues_against_a_higher_one() { - 2542
let mut alone: Votes<Stakes> = Votes::default(); - 2543
alone.add(Stakes::Irreversible, 1.0); - 2544
let (_, alone_confidence) = alone.winner().unwrap(); - 2545
- 2546
let mut corroborated: Votes<Stakes> = Votes::default(); - 2547
corroborated.add(Stakes::Irreversible, 1.0); - 2548
corroborated.add(Stakes::Reversible, 0.5); - 2549
let (value, confidence) = corroborated.winner().unwrap(); - 2550
- 2551
assert_eq!(value, Stakes::Irreversible); - 2552
assert!(confidence >= alone_confidence); - 2553
} - 2554
- 2555
#[test] - 2556
fn ordered_axes_take_the_highest_supported_level() { - 2557
let mut stakes: Votes<Stakes> = Votes::default(); - 2558
stakes.add(Stakes::Inert, 3.0); - 2559
stakes.add(Stakes::Irreversible, 0.9); - 2560
assert_eq!(stakes.winner().map(|w| w.0), Some(Stakes::Irreversible)); - 2561
- 2562
let mut evidence: Votes<Evidence> = Votes::default(); - 2563
evidence.add(Evidence::Cited, 2.0); - 2564
evidence.add(Evidence::Audited, 0.8); - 2565
assert_eq!(evidence.winner().map(|w| w.0), Some(Evidence::Audited)); - 2566
} - 2567
- 2568
#[test] - 2569
fn weak_hints_do_not_escalate_an_ordered_axis() { - 2570
let mut horizon: Votes<Horizon> = Votes::default(); - 2571
horizon.add(Horizon::Immediate, 0.6); - 2572
horizon.add(Horizon::Durable, 0.3); - 2573
assert_eq!(horizon.winner().map(|w| w.0), Some(Horizon::Immediate)); - 2574
} - 2575
- 2576
#[test] - 2577
fn assurance_needs_something_checkable() { - 2578
assert_eq!( - 2579
extract(&request("make sure the tests pass")) - 2580
.evidence - 2581
.winner() - 2582
.map(|w| w.0), - 2583
Some(Evidence::Verified) - 2584
); - 2585
assert!( - 2586
extract(&request("write a poem, make sure it rhymes")) - 2587
.evidence - 2588
.winner() - 2589
.is_none() - 2590
); - 2591
} - 2592
} - 2593
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.