mod turn;
pub(crate) use turn::TurnClaims;
pub(crate) fn path_claims(text: &str) -> Vec<String> {
let mut seen = std::collections::BTreeSet::new();
let mut out = Vec::new();
for raw in text.split(|c: char| c.is_whitespace() || "()[]{}<>,;:\"'`*".contains(c)) {
let t = raw.trim_end_matches(|c: char| ".,!?".contains(c));
if t.is_empty() || t.starts_with("//") || t.contains("://") {
continue;
}
let has_ext = t.rsplit_once('.').is_some_and(|(_, ext)| {
(1..=4).contains(&ext.len()) && ext.chars().all(|c| c.is_ascii_alphanumeric())
});
if t.contains('/')
&& has_ext
&& t.chars().any(|c| c.is_ascii_alphabetic())
&& seen.insert(t.to_string())
{
out.push(t.to_string());
}
}
out
}
pub(super) fn asserted_claim_lines(text: &str) -> Vec<&str> {
let mut planning_level = None;
let mut claims = Vec::new();
for line in text.lines() {
let trimmed = line.trim();
let heading_level = trimmed.chars().take_while(|c| *c == '#').count();
let heading = (1..=6).contains(&heading_level)
&& trimmed[heading_level..].starts_with(char::is_whitespace);
let lower = trimmed.trim_matches('#').trim().to_ascii_lowercase();
if lower.starts_with("remaining work") || lower.starts_with("next steps") {
planning_level = Some(if heading { heading_level } else { 0 });
} else if lower.starts_with("current state")
|| lower.starts_with("completed")
|| (heading && planning_level.is_some_and(|level| level == 0 || heading_level <= level))
{
planning_level = None;
}
if planning_level.is_some()
|| lower.starts_with('>')
|| lower.starts_with("planned:")
|| lower.starts_with("will ")
|| lower.starts_with("i will ")
{
continue;
}
claims.push(line);
}
claims
}
fn asserted_path_claims(text: &str) -> Vec<String> {
let mut claims = Vec::new();
for line in asserted_claim_lines(text) {
for claim in path_claims(line) {
if !claims.contains(&claim) {
claims.push(claim);
}
}
}
claims
}
const LISTED_CLAIMS: usize = 8;
#[cfg(test)]
fn annotate_path_claims(text: String, resolve: impl FnMut(&str) -> Option<bool>) -> String {
annotate_path_claims_at(text, resolve, None)
}
fn annotate_path_claims_at(
mut text: String,
mut resolve: impl FnMut(&str) -> Option<bool>,
root: Option<&std::path::Path>,
) -> String {
let location = root
.map(|path| {
format!(
" (root {})",
crate::worktree_adoption::task_path_literal(path)
)
})
.unwrap_or_default();
let mut missing = Vec::new();
let mut unverified = Vec::new();
for claim in asserted_path_claims(&text) {
match resolve(&claim) {
Some(true) => {}
Some(false) => missing.push(claim),
None => unverified.push(claim),
}
}
let mut remaining = LISTED_CLAIMS;
for (claims, label) in [
(missing, "not found in this workspace"),
(unverified, "unverified outside workspace (not inspected)"),
] {
if claims.is_empty() {
continue;
}
let listed: Vec<_> = claims
.iter()
.take(remaining)
.map(|p| format!("`{p}`"))
.collect();
remaining -= listed.len();
let more = claims.len() - listed.len();
let overflow = if more > 0 {
format!(" (+{more} more)")
} else {
String::new()
};
text = format!(
"{text}\n\n⚠ claim check (#867): cited path(s) {label}{location}: {}{overflow} \
— verify these before acting on the summary above.",
listed.join(", ")
);
}
text
}
const EXTRA_BASES_CAP: usize = 40;
fn workspace_claim_resolver(
workspace: &str,
mut exists: impl FnMut(&std::path::Path) -> bool,
) -> impl FnMut(&str) -> Option<bool> {
let root = super::lexical_normalize(std::path::Path::new(workspace));
let mut extra_bases: Vec<std::path::PathBuf> = Vec::new();
move |claim: &str| {
let p = std::path::Path::new(claim);
if p.is_absolute() {
let norm = super::lexical_normalize(p);
return norm.starts_with(&root).then(|| exists(&norm));
}
let mut in_workspace = false;
for base in root
.ancestors()
.chain(extra_bases.iter().map(|p| p.as_path()))
{
let norm = super::lexical_normalize(&base.join(p));
if !norm.starts_with(&root) {
continue;
}
in_workspace = true;
if exists(&norm) {
for parent in norm
.ancestors()
.skip(1)
.take_while(|p| p.starts_with(&root))
{
if extra_bases.len() < EXTRA_BASES_CAP
&& !extra_bases.iter().any(|p| p == parent)
{
extra_bases.push(parent.to_path_buf());
}
}
return Some(true);
}
}
in_workspace.then_some(false)
}
}
pub(crate) fn workspace_resolver(workspace: &str) -> impl FnMut(&str) -> bool {
let mut resolve = workspace_claim_resolver(workspace, std::path::Path::exists);
move |claim| resolve(claim) == Some(true)
}
pub(crate) fn annotate_against_workspace(text: String, workspace: &str) -> String {
annotate_path_claims_at(
text,
workspace_claim_resolver(workspace, std::path::Path::exists),
Some(std::path::Path::new(workspace)),
)
}
pub(crate) fn annotate_in_context(
text: String,
workspace: &str,
directories: &[std::path::PathBuf],
read_scope: &crate::Scope<String>,
) -> String {
if directories.is_empty() && matches!(read_scope, crate::Scope::All) {
return annotate_against_workspace(text, workspace);
}
let mut roots = directories.to_vec();
roots.push(std::path::PathBuf::from(workspace));
let uninspected = roots
.iter()
.any(|root| !crate::caveats::permits_path(read_scope, &root.to_string_lossy()));
roots.retain(|root| crate::caveats::permits_path(read_scope, &root.to_string_lossy()));
let mut resolvers: Vec<_> = roots
.iter()
.map(|root| workspace_claim_resolver(&root.to_string_lossy(), std::path::Path::exists))
.collect();
annotate_path_claims_at(
text,
|claim| {
let mut inspected = false;
for resolve in &mut resolvers {
match resolve(claim) {
Some(true) => return Some(true),
Some(false) => inspected = true,
None => {}
}
}
(inspected && !uninspected).then_some(false)
},
Some(std::path::Path::new(workspace)),
)
}
const OBSERVED_CAP: usize = 40;
#[derive(Default)]
pub(crate) struct ObservedPaths {
ordered: Vec<String>,
}
impl ObservedPaths {
pub(crate) fn record(&mut self, text: &str, mut exists: impl FnMut(&str) -> bool) {
for claim in path_claims(text) {
if self.ordered.len() >= OBSERVED_CAP {
return;
}
if exists(&claim) && !self.ordered.contains(&claim) {
self.ordered.push(claim);
}
}
}
pub(crate) fn into_vec(self) -> Vec<String> {
self.ordered
}
}
pub(crate) struct TurnGitEvidence {
pub branches: Vec<String>,
pub head_before: Option<String>,
pub head_now: String,
pub dirty_paths: Option<StatusSnapshot>,
}
pub type StatusSnapshot = std::collections::BTreeMap<String, String>;
fn parse_porcelain_z(out: &str) -> StatusSnapshot {
let mut snapshot = StatusSnapshot::new();
let mut entries = out.split('\0');
while let Some(entry) = entries.next() {
let (Some(code), Some(path)) = (entry.get(..2), entry.get(3..)) else {
continue;
};
if code.contains(['R', 'C']) {
entries.next();
}
snapshot.insert(path.to_string(), code.to_string());
}
snapshot
}
#[must_use]
pub fn files_changed_between(before: &StatusSnapshot, after: &StatusSnapshot) -> Vec<String> {
after
.iter()
.filter(|(path, code)| before.get(*path) != Some(*code))
.map(|(path, _)| path.clone())
.collect()
}
const SNAPSHOT_STATUS_ARGS: [&str; 4] = ["status", "--porcelain=v1", "-z", "--untracked-files=all"];
#[derive(Debug, Clone)]
pub struct NestedRepoSnapshot {
pub repo: String,
pub status: Option<StatusSnapshot>,
}
fn is_repo_root(dir: &std::path::Path) -> bool {
dir.join(".git").exists()
}
#[must_use]
pub fn snapshot_nested_repos(
workspace: &str,
read_scope: &crate::Scope<String>,
) -> Vec<NestedRepoSnapshot> {
crate::tooling::first_level_subdirs(std::path::Path::new(workspace))
.into_iter()
.filter(|dir| {
dir.symlink_metadata()
.is_ok_and(|meta| !meta.file_type().is_symlink())
})
.filter(|dir| is_repo_root(dir))
.filter_map(|dir| {
let repo = dir.file_name()?.to_string_lossy().into_owned();
let dir_str = dir.to_string_lossy().into_owned();
let status = crate::caveats::permits_path(read_scope, &dir_str)
.then(|| git_in(&dir_str, &SNAPSHOT_STATUS_ARGS, read_scope))
.flatten()
.map(|out| parse_porcelain_z(&out));
Some(NestedRepoSnapshot { repo, status })
})
.collect()
}
#[must_use]
pub fn nested_files_changed_between(
before: &[NestedRepoSnapshot],
after: &[NestedRepoSnapshot],
) -> (Vec<String>, Vec<String>) {
let mut files = Vec::new();
let mut unprobed = Vec::new();
for repo_after in after {
let Some(after_status) = &repo_after.status else {
unprobed.push(repo_after.repo.clone());
continue;
};
let before_status = before
.iter()
.find(|b| b.repo == repo_after.repo)
.and_then(|b| b.status.as_ref());
let Some(before_status) = before_status else {
unprobed.push(repo_after.repo.clone());
continue;
};
for path in files_changed_between(before_status, after_status) {
files.push(format!("{}/{path}", repo_after.repo));
}
}
for repo_before in before {
if !after.iter().any(|a| a.repo == repo_before.repo) {
unprobed.push(repo_before.repo.clone());
}
}
files.sort();
unprobed.sort();
unprobed.dedup();
(files, unprobed)
}
#[must_use]
pub fn nested_current_paths(snapshots: &[NestedRepoSnapshot]) -> (Vec<String>, Vec<String>) {
let mut files = Vec::new();
let mut unprobed = Vec::new();
for repo in snapshots {
match &repo.status {
Some(status) => {
for path in status.keys() {
files.push(format!("{}/{path}", repo.repo));
}
}
None => unprobed.push(repo.repo.clone()),
}
}
files.sort();
(files, unprobed)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WorkspaceRepoLocation {
OwnRoot,
InsideRepo { prefix: String },
NotARepo,
}
#[must_use]
pub fn locate_workspace_repo(
workspace: &str,
read_scope: &crate::Scope<String>,
) -> WorkspaceRepoLocation {
let Some(toplevel) = git_in(workspace, &["rev-parse", "--show-toplevel"], read_scope) else {
return WorkspaceRepoLocation::NotARepo;
};
let (Ok(toplevel), Ok(root)) = (
std::fs::canonicalize(toplevel.trim()),
std::fs::canonicalize(workspace),
) else {
return WorkspaceRepoLocation::NotARepo;
};
if toplevel == root {
return WorkspaceRepoLocation::OwnRoot;
}
let Some(prefix) = git_in(workspace, &["rev-parse", "--show-prefix"], read_scope) else {
return WorkspaceRepoLocation::NotARepo;
};
WorkspaceRepoLocation::InsideRepo {
prefix: prefix.trim().to_string(),
}
}
#[must_use]
pub fn is_workspace_repo_root(workspace: &str, read_scope: &crate::Scope<String>) -> bool {
locate_workspace_repo(workspace, read_scope) == WorkspaceRepoLocation::OwnRoot
}
#[must_use]
pub fn snapshot_workspace(
workspace: &str,
read_scope: &crate::Scope<String>,
) -> Option<StatusSnapshot> {
if !is_workspace_repo_root(workspace, read_scope) {
return None;
}
let out = git_in(workspace, &SNAPSHOT_STATUS_ARGS, read_scope)?;
Some(parse_porcelain_z(&out))
}
#[must_use]
pub fn snapshot_workspace_subtree(
workspace: &str,
prefix: &str,
read_scope: &crate::Scope<String>,
) -> Option<StatusSnapshot> {
let mut args: Vec<&str> = SNAPSHOT_STATUS_ARGS.to_vec();
args.extend(["--", "."]);
let out = git_in(workspace, &args, read_scope)?;
Some(
parse_porcelain_z(&out)
.into_iter()
.filter_map(|(path, code)| {
let rel = path.strip_prefix(prefix)?;
(!rel.ends_with('/')).then(|| (rel.to_string(), code))
})
.collect(),
)
}
pub(crate) fn claimed_branches(text: &str) -> Vec<String> {
const WINDOW: usize = 4;
let mut out = Vec::new();
for line in text
.lines()
.filter(|line| !line.trim_start().starts_with('>'))
{
let toks: Vec<&str> = line.split_whitespace().collect();
for (i, tok) in toks.iter().enumerate() {
let key = tok
.trim_matches(|c: char| !c.is_ascii_alphanumeric())
.to_lowercase();
if key != "branch" && key != "branches" {
continue;
}
for (offset, cand) in toks.iter().skip(i + 1).take(WINDOW).enumerate() {
if offset > 0 && ends_a_clause(toks[i + offset]) {
break;
}
if matches!(*cand, "☐" | "✓" | "→" | "•") {
break;
}
let wrapped = cand.starts_with(['`', '\'', '"']);
let numbered = cand.ends_with('.')
&& cand
.trim_end_matches('.')
.chars()
.all(|c| c.is_ascii_digit());
let cand = cand
.trim_end_matches('.')
.trim_matches(|c: char| "`'\"()[],;:!?*".contains(c));
let cand = cand.trim_end_matches('.');
let refy = cand.contains('/') || cand.chars().any(|c| c.is_ascii_digit());
if !cand.is_empty()
&& (!numbered || wrapped)
&& refy
&& cand
.chars()
.all(|c| c.is_ascii_alphanumeric() || "/-_.".contains(c))
{
if !out.contains(&cand.to_string()) {
out.push(cand.to_string());
}
break; }
}
}
}
out
}
const ACTION_VOID_WORDS: [&str; 13] = [
"not",
"no",
"never",
"will",
"would",
"should",
"could",
"must",
"pending",
"if",
"until",
"previously",
"earlier",
];
const CLAUSE_BOUNDARY: [char; 4] = ['.', '!', '?', ';'];
const CLAUSE_ABBREVIATIONS: [&str; 6] = ["e.g.", "i.e.", "etc.", "vs.", "mr.", "dr."];
pub(super) fn ends_a_clause(tok: &str) -> bool {
tok.ends_with(CLAUSE_BOUNDARY)
&& !CLAUSE_ABBREVIATIONS.contains(&tok.to_ascii_lowercase().as_str())
}
pub(crate) fn claims_committed(text: &str) -> bool {
claims_completed_action(text, "committed")
}
pub(crate) fn claims_completed_action(text: &str, action: &str) -> bool {
let normalize = |t: &str| {
t.trim_matches(|c: char| !c.is_ascii_alphanumeric() && c != '\'')
.to_ascii_lowercase()
};
text.lines().any(|line| {
if line.trim_start().starts_with('>') {
return false; }
let toks: Vec<&str> = line.split_whitespace().collect();
toks.iter().enumerate().any(|(i, tok)| {
if normalize(tok) != action {
return false;
}
let clause_start = toks[..i]
.iter()
.rposition(|t| ends_a_clause(t))
.map_or(0, |p| p + 1);
let clause: Vec<String> = toks[clause_start..i].iter().map(|t| normalize(t)).collect();
let voided = clause
.iter()
.any(|k| ACTION_VOID_WORDS.contains(&k.as_str()) || k.contains("n't"))
|| clause
.windows(2)
.any(|w| matches!(w[0].as_str(), "yet" | "ready") && w[1] == "to");
!voided
})
})
}
fn commit_facts_note(before: Option<&str>, now: &str, dirty: Option<&StatusSnapshot>) -> String {
let head = match before {
None => format!(
"HEAD is `{}`; no same-checkout baseline (workspace changed during turn)",
short_sha(now)
),
Some(before) if before == now => "HEAD did not move".to_string(),
Some(before) => format!(
"HEAD moved from `{}` to `{}`",
short_sha(before),
short_sha(now)
),
};
let changes = match dirty {
None => "not probed".to_string(),
Some(map) if map.is_empty() => "none".to_string(),
Some(map) => {
let listed: Vec<_> = map
.keys()
.take(LISTED_CLAIMS)
.map(|p| format!("`{p}`"))
.collect();
let more = map.len() - listed.len();
if more > 0 {
format!("{} (+{more} more)", listed.join(", "))
} else {
listed.join(", ")
}
}
};
format!(
"repository state at the end of this turn: {head}; uncommitted changes: {changes} \
— verify the workspace state before trusting the summary above."
)
}
fn short_sha(sha: &str) -> &str {
&sha[..sha.len().min(12)]
}
#[cfg(test)]
fn annotate_action_claims(text: String, evidence: Option<&TurnGitEvidence>) -> String {
annotate_action_claims_at(text, evidence, None, false)
}
fn annotate_action_claims_at(
text: String,
evidence: Option<&TurnGitEvidence>,
root: Option<&std::path::Path>,
root_changed: bool,
) -> String {
let Some(ev) = evidence else { return text };
let mut notes: Vec<String> = Vec::new();
for b in claimed_branches(&text) {
if !ev.branches.iter().any(|have| have == &b) {
notes.push(format!("claimed branch `{b}` does not exist"));
}
}
let commit_claimed = claims_committed(&text);
let mut text = text;
if !notes.is_empty() {
text = format!(
"{text}\n\n⚠ claim check (#1214): {} — verify the workspace state before \
trusting the summary above.",
notes.join("; ")
);
}
if commit_claimed && (ev.head_before.is_some() || root_changed) {
let location = root
.map(|path| format!("in {}: ", crate::worktree_adoption::task_path_literal(path)))
.unwrap_or_default();
text = format!(
"{text}\n\n⚠ claim check (#2683): {location}{}",
commit_facts_note(
ev.head_before.as_deref(),
&ev.head_now,
ev.dirty_paths.as_ref()
)
);
}
text
}
pub(crate) fn collect_git_evidence(
workspace: &str,
read_scope: &crate::Scope<String>,
head_before: Option<&str>,
) -> Option<TurnGitEvidence> {
let head_now = git_head(workspace, read_scope)?;
let branches = git_in(
workspace,
&["branch", "--format=%(refname:short)"],
read_scope,
)?
.lines()
.map(|l| l.trim().to_string())
.filter(|l| !l.is_empty())
.collect();
let dirty_paths =
git_in(workspace, &SNAPSHOT_STATUS_ARGS, read_scope).map(|out| parse_porcelain_z(&out));
Some(TurnGitEvidence {
branches,
head_before: head_before.map(str::to_string),
head_now,
dirty_paths,
})
}
pub(crate) fn git_head(workspace: &str, read_scope: &crate::Scope<String>) -> Option<String> {
git_in(workspace, &["rev-parse", "HEAD"], read_scope).map(|s| s.trim().to_string())
}
fn git_in(workspace: &str, args: &[&str], read_scope: &crate::Scope<String>) -> Option<String> {
let out = crate::git_hardening::metadata_git(std::path::Path::new(workspace), args, read_scope)
.ok()?
.output()
.ok()?;
out.status
.success()
.then(|| String::from_utf8_lossy(&out.stdout).into_owned())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snapshot_lists_individual_untracked_files() {
assert!(SNAPSHOT_STATUS_ARGS.contains(&"--untracked-files=all"));
}
#[test]
fn porcelain_z_parses_spaces_untracked_and_renames() {
let out = " M src/a b.rs\0?? new.txt\0R moved.rs\0old.rs\0";
let s = parse_porcelain_z(out);
assert_eq!(s.get("src/a b.rs").map(String::as_str), Some(" M"));
assert_eq!(s.get("new.txt").map(String::as_str), Some("??"));
assert_eq!(s.get("moved.rs").map(String::as_str), Some("R "));
assert!(
!s.contains_key("old.rs"),
"a rename origin is not a path of its own"
);
assert!(parse_porcelain_z("").is_empty());
}
#[test]
fn files_changed_is_the_delta_not_the_dirt() {
let snap = |v: &[(&str, &str)]| {
v.iter()
.map(|(p, c)| (p.to_string(), c.to_string()))
.collect::<StatusSnapshot>()
};
let before = snap(&[("dirty.txt", " M"), ("staged.txt", "A ")]);
let after = snap(&[
("dirty.txt", " M"),
("staged.txt", "AM"),
("stray.sh", "??"),
]);
assert_eq!(
files_changed_between(&before, &after),
["staged.txt", "stray.sh"]
);
assert!(files_changed_between(&after, &after).is_empty());
}
#[test]
fn path_claims_extracts_slash_ext_tokens_from_chat_prose() {
let text = "Defined in **newt-tui/src/commands.rs** (lines 38-40); \
handler at `session/mod.rs:567`. See \
https://github.com/x/y/blob/main/z.rs and spec 1.2/3.4.";
assert_eq!(
path_claims(text),
vec!["newt-tui/src/commands.rs", "session/mod.rs"]
);
}
#[test]
fn path_claims_keeps_leading_dots_dedupes_and_skips_prose() {
let text = "check .newt/config.toml then ./src/lib.rs, then .newt/config.toml again. \
A plain sentence, an edit/shell mention, and lib.rs alone are not claims.";
assert_eq!(path_claims(text), vec![".newt/config.toml", "./src/lib.rs"]);
assert!(path_claims("no paths here at all").is_empty());
}
#[test]
fn annotate_is_a_noop_when_claims_verify_or_are_absent() {
let clean = "all good, nothing cited".to_string();
assert_eq!(annotate_path_claims(clean.clone(), |_| Some(false)), clean);
let cited = "the fix is in a/b.rs and c/d.rs".to_string();
assert_eq!(annotate_path_claims(cited.clone(), |_| Some(true)), cited);
}
#[test]
fn annotate_appends_refutation_and_preserves_the_prose_prefix() {
let cited = "the fix is in a/b.rs and c/d.rs".to_string();
let out = annotate_path_claims(cited.clone(), |c| Some(c == "a/b.rs"));
assert!(out.starts_with(&cited), "prose must be an exact prefix");
assert!(out.contains("⚠ claim check (#867)"), "got: {out}");
assert!(out.contains("`c/d.rs`"), "the missing path is named");
assert!(!out.contains("`a/b.rs`"), "verified paths are not listed");
}
#[test]
fn annotate_caps_the_listed_paths() {
let cited: String = (0..12)
.map(|i| format!("see dir{i}/f{i}.rs "))
.collect::<String>();
let out = annotate_path_claims(cited, |_| Some(false));
assert!(out.contains("`dir0/f0.rs`"));
assert!(out.contains("`dir7/f7.rs`"));
assert!(!out.contains("`dir8/f8.rs`"), "capped at {LISTED_CLAIMS}");
assert!(out.contains("(+4 more)"), "got: {out}");
}
#[test]
fn observed_paths_records_verified_dedupes_and_caps() {
let mut led = ObservedPaths::default();
led.record("src/a.rs:12: hit and src/b.rs:9: hit", |c| c != "src/b.rs");
led.record("src/a.rs:44: again, plus docs/x.md", |_| true);
assert_eq!(led.into_vec(), vec!["src/a.rs", "docs/x.md"]);
let mut full = ObservedPaths::default();
let many: String = (0..50).map(|i| format!("d/f{i}.rs ")).collect();
full.record(&many, |_| true);
let v = full.into_vec();
assert_eq!(v.len(), 40, "capped at OBSERVED_CAP");
assert_eq!(v[0], "d/f0.rs");
assert_eq!(v[39], "d/f39.rs");
}
fn evidence(branches: &[&str]) -> TurnGitEvidence {
TurnGitEvidence {
branches: branches.iter().map(|s| s.to_string()).collect(),
head_before: None,
head_now: "0000000000000000000000000000000000000000".to_string(),
dirty_paths: None,
}
}
fn evidence_with_head(branches: &[&str], before: &str, now: &str) -> TurnGitEvidence {
TurnGitEvidence {
branches: branches.iter().map(|s| s.to_string()).collect(),
head_before: Some(before.to_string()),
head_now: now.to_string(),
dirty_paths: None,
}
}
fn evidence_with_dirty(
branches: &[&str],
before: &str,
now: &str,
dirty: &[&str],
) -> TurnGitEvidence {
TurnGitEvidence {
branches: branches.iter().map(|s| s.to_string()).collect(),
head_before: Some(before.to_string()),
head_now: now.to_string(),
dirty_paths: Some(
dirty
.iter()
.map(|p| (p.to_string(), " M".to_string()))
.collect(),
),
}
}
#[test]
fn refutes_only_the_phantom_branch_from_the_live_transcript() {
let text = "Branch is clean on step-09.Help-rollup-for-the-548 \
(only my single commit ahead of bench/548-base). \
All existing tests pass."
.to_string();
let ev = evidence(&["bench/548-base", "main"]);
let out = annotate_action_claims(text.clone(), Some(&ev));
assert!(out.starts_with(&text), "prose is an exact prefix");
assert!(out.contains("⚠ claim check (#1214)"), "got: {out}");
assert!(
out.contains("`step-09.Help-rollup-for-the-548` does not exist"),
"phantom branch refuted: {out}"
);
assert!(!out.contains("no commit was created"), "{out}");
assert!(!out.contains("`bench/548-base` does not exist"), "{out}");
}
#[test]
fn honest_summaries_pass_untouched() {
let honest = "committed the fix on branch fix/x-1; tests pass".to_string();
let ev = evidence(&["fix/x-1", "main"]);
assert_eq!(annotate_action_claims(honest.clone(), Some(&ev)), honest);
let no_claims = "I explored the code and here is my analysis".to_string();
let ev = evidence(&["main"]);
assert_eq!(
annotate_action_claims(no_claims.clone(), Some(&ev)),
no_claims
);
let claimy = "committed and pushed".to_string();
assert_eq!(annotate_action_claims(claimy.clone(), None), claimy);
}
#[test]
fn git_state_does_not_refute_negated_or_unrelated_action_claims() {
for text in [
"These are all __pycache__ .pyc files generated by running the CI tests. \
They're not staged or committed — just sitting untracked in the working tree.",
"All tests passed.",
"I pushed the existing commit to origin.",
"I opened a pull request for the existing commit.",
"I have not committed these changes.",
"After approval, these files will be committed.",
"Previously, I committed the change.",
"> committed the change",
"The existing branch is one commit ahead of origin/main.",
"These changes are yet to be committed.",
"The files have not, for safety reasons, been committed.",
"I have not changed src/a.rs or committed anything",
] {
let ev = evidence(&["main"]);
assert_eq!(
annotate_action_claims(text.to_string(), Some(&ev)),
text,
"Git metadata does not contradict this statement: {text}"
);
let ev = evidence_with_head(&["main"], "aaa111", "aaa111");
assert_eq!(
annotate_action_claims(text.to_string(), Some(&ev)),
text,
"not a direct, present-tense commit claim, even with an unmoved-HEAD \
baseline on hand: {text}"
);
}
}
#[test]
fn branch_inventory_is_not_commit_execution_evidence() {
let text = "I committed the change".to_string();
let ev = evidence(&["main"]);
assert_eq!(annotate_action_claims(text.clone(), Some(&ev)), text);
}
#[test]
fn commit_claim_gets_a_facts_note_when_head_did_not_move() {
let text = "I committed the change locally. cargo check passes.".to_string();
let ev = evidence_with_head(&["main"], "aaa111", "aaa111");
let out = annotate_action_claims(text.clone(), Some(&ev));
assert!(out.starts_with(&text), "prose is an exact prefix: {out}");
assert!(out.contains("⚠ claim check (#2683)"), "got: {out}");
assert!(out.contains("HEAD did not move"), "got: {out}");
}
#[test]
fn head_unmoved_with_staged_changes_gets_the_facts_note() {
let text = "I committed the change locally.".to_string();
let ev = evidence_with_dirty(&["main"], "aaa111", "aaa111", &["src/a.rs"]);
let out = annotate_action_claims(text.clone(), Some(&ev));
assert!(out.contains("⚠ claim check (#2683)"), "got: {out}");
assert!(out.contains("HEAD did not move"), "got: {out}");
assert!(out.contains("`src/a.rs`"), "got: {out}");
}
#[test]
fn a_true_committed_claim_gets_a_facts_note_showing_head_moved() {
let text = "I committed the change locally.".to_string();
let ev = evidence_with_head(&["main"], "aaa111", "bbb222");
let out = annotate_action_claims(text.clone(), Some(&ev));
assert!(out.starts_with(&text), "prose is an exact prefix: {out}");
assert!(out.contains("⚠ claim check (#2683)"), "got: {out}");
assert!(
out.contains("HEAD moved from `aaa111` to `bbb222`"),
"got: {out}"
);
assert!(
out.contains("uncommitted changes: not probed"),
"got: {out}"
);
}
#[test]
fn an_honest_partial_commit_claim_gets_facts_never_a_false_not_committed_verdict() {
let text = "Committed src/a.rs. src/b.rs remains uncommitted.".to_string();
let ev = evidence_with_dirty(&["main"], "aaa111", "bbb222", &["src/b.rs"]);
let out = annotate_action_claims(text.clone(), Some(&ev));
assert!(out.starts_with(&text), "prose is an exact prefix: {out}");
assert!(out.contains("⚠ claim check (#2683)"), "got: {out}");
assert!(
out.contains("HEAD moved from `aaa111` to `bbb222`"),
"got: {out}"
);
assert!(
out.contains("`src/b.rs`"),
"the dirty file is a FACT: {out}"
);
let annotation = out.strip_prefix(&text).expect("exact prefix");
assert!(
!annotation.to_lowercase().contains("not committed"),
"no claim-to-file verdict of ours: {out}"
);
}
#[test]
fn claims_committed_recognizes_the_verb_but_not_voided_or_noun_forms() {
assert!(claims_committed(
"committed the fix on branch fix/x-1; tests pass"
));
assert!(claims_committed("Committed the fix locally."));
assert!(!claims_committed("I have not committed these changes."));
assert!(!claims_committed(
"After approval, these files will be committed."
));
assert!(!claims_committed("Previously, I committed the change."));
assert!(!claims_committed("> committed the change"));
assert!(!claims_committed("I pushed the existing commit to origin."));
assert!(!claims_committed(
"The existing branch is one commit ahead of origin/main."
));
}
#[test]
fn claims_committed_is_clause_aware_not_window_bounded() {
assert!(claims_committed("I have already committed the fix"));
assert!(claims_committed("Not staged; committed."));
assert!(!claims_committed("These changes are yet to be committed."));
assert!(!claims_committed(
"The files have not, for safety reasons, been committed."
));
assert!(!claims_committed(
"I have not changed src/a.rs or committed anything"
));
assert!(claims_committed("Verified src/a.rs. I committed the fix."));
assert!(!claims_committed(
"The patch has not, e.g. for safety reasons, been committed."
));
}
#[test]
fn ordinary_language_and_clause_boundary_commit_claims_are_also_refuted() {
for text in ["I have already committed the fix", "Not staged; committed."] {
let ev = evidence_with_head(&["main"], "aaa111", "aaa111");
let out = annotate_action_claims(text.to_string(), Some(&ev));
assert!(out.contains("⚠ claim check (#2683)"), "{text} -> {out}");
}
}
#[test]
fn branch_claim_detection_is_conservative() {
assert_eq!(
claimed_branches("on branch `fix/a-1` and branch main stays; the branch is fine"),
vec!["fix/a-1"],
"prose words and bare names without ref-chars are not claims"
);
}
#[test]
fn workspace_wiring_fences_and_resolves() {
let ws = env!("CARGO_MANIFEST_DIR");
let ok = annotate_against_workspace("see src/lib.rs".to_string(), ws);
assert!(!ok.contains("⚠ claim check"), "real file verifies: {ok}");
let bad = annotate_against_workspace(
"see src/nope.rs and ../escape/x.rs and /etc/hosts.d/y.rs".to_string(),
ws,
);
assert!(bad.contains("`src/nope.rs`"), "got: {bad}");
assert!(bad.contains("`../escape/x.rs`"), "escape refuted: {bad}");
assert!(
bad.contains("`/etc/hosts.d/y.rs`"),
"outside refuted: {bad}"
);
}
#[test]
fn external_path_claims_are_unverified_not_missing() {
let tree = tempfile::tempdir().unwrap();
let workspace = tree.path().join("workspace");
let reports = tree.path().join("reports");
std::fs::create_dir_all(&workspace).unwrap();
std::fs::create_dir_all(&reports).unwrap();
let existing = reports.join("report.md");
std::fs::write(&existing, "observed report").unwrap();
let absolute = cfg!(unix).then(|| existing.to_string_lossy().into_owned());
for claim in absolute
.as_deref()
.into_iter()
.chain(["../reports/report.md", "../reports/absent.md"])
{
let text = format!("See `{claim}`.");
let out = annotate_against_workspace(text.clone(), workspace.to_str().unwrap());
let annotation = out.strip_prefix(&text).expect("preserve original prose");
assert!(
annotation.contains("unverified outside workspace"),
"external existence is not known to this checker: {out}"
);
assert!(!annotation.contains("not found"), "{out}");
assert!(annotation.contains(&format!("`{claim}`")), "{out}");
}
}
#[test]
fn mixed_path_claims_keep_missing_and_unverified_separate() {
let workspace = env!("CARGO_MANIFEST_DIR");
let text = "See src/lib.rs, src/absent-file.rs and ../../reports/report.md.".to_string();
let out = annotate_against_workspace(text.clone(), workspace);
let annotation = out.strip_prefix(&text).expect("preserve original prose");
let (missing, unverified) = annotation
.split_once("unverified outside workspace")
.expect("external claims have a distinct annotation");
assert!(missing.contains("not found in this workspace"), "{out}");
assert!(missing.contains("`src/absent-file.rs`"), "{out}");
assert!(!missing.contains("`../../reports/report.md`"), "{out}");
assert!(unverified.contains("`../../reports/report.md`"), "{out}");
assert!(!annotation.contains("`src/lib.rs`"), "{out}");
}
#[test]
fn external_path_annotation_keeps_the_existing_list_bound() {
let text: String = (0..12)
.map(|i| format!("See ../reports/report{i}.md. "))
.collect();
let out = annotate_against_workspace(text.clone(), env!("CARGO_MANIFEST_DIR"));
let annotation = out.strip_prefix(&text).expect("preserve original prose");
assert!(annotation.contains("unverified outside workspace"), "{out}");
assert!(annotation.contains("`../reports/report0.md`"), "{out}");
assert!(annotation.contains("`../reports/report7.md`"), "{out}");
assert!(!annotation.contains("`../reports/report8.md`"), "{out}");
assert!(annotation.contains("(+4 more)"), "{out}");
}
#[test]
fn unverified_external_claims_do_not_enter_observed_paths() {
let mut observed = ObservedPaths::default();
observed.record(
"See src/lib.rs and ../../reports/report.md.",
workspace_resolver(env!("CARGO_MANIFEST_DIR")),
);
assert_eq!(observed.into_vec(), vec!["src/lib.rs"]);
}
#[test]
fn workspace_claim_resolver_never_probes_lexical_escapes() {
let workspace = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let outside = workspace.parent().unwrap().join("external-report.md");
let mut probes = Vec::new();
{
let mut resolve = workspace_claim_resolver(workspace.to_str().unwrap(), |path| {
probes.push(path.to_path_buf());
true
});
assert_eq!(resolve(outside.to_str().unwrap()), None);
assert_eq!(resolve("../../reports/report.md"), None);
assert_eq!(resolve("src/lib.rs"), Some(true));
assert_eq!(resolve("../../reports/report.md"), None);
}
assert_eq!(probes, vec![workspace.join("src/lib.rs")]);
}
#[test]
fn missing_and_unverified_claims_share_one_display_cap() {
let text: String = (0..12).map(|i| format!("See dir/file{i}.md. ")).collect();
let out = annotate_path_claims(text.clone(), |claim| {
claim.ends_with("0.md").then_some(false)
});
let annotation = out.strip_prefix(&text).unwrap();
assert_eq!(annotation.matches('`').count(), LISTED_CLAIMS * 2);
assert!(annotation.contains("not found in this workspace"));
assert!(annotation.contains("unverified outside workspace"));
assert!(annotation.contains("(+4 more)"));
}
#[test]
fn bare_fragment_resolves_under_an_earlier_verified_claims_directory() {
let ws = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("newt-core has a workspace root parent");
let ws = ws.to_str().expect("workspace root is valid UTF-8");
let text = "see newt-core/Cargo.toml and also src/lib.rs".to_string();
let out = annotate_against_workspace(text.clone(), ws);
assert_eq!(out, text, "both claims verify, no annotation: {out}");
}
#[test]
fn a_genuinely_absent_fragment_still_refutes_even_with_a_learned_base() {
let ws = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("newt-core has a workspace root parent");
let ws = ws.to_str().expect("workspace root is valid UTF-8");
let text = "see newt-core/Cargo.toml and also nope/nope.rs".to_string();
let out = annotate_against_workspace(text, ws);
assert!(out.contains("⚠ claim check (#867)"), "got: {out}");
assert!(out.contains("`nope/nope.rs`"), "got: {out}");
}
#[test]
fn a_claim_resolvable_under_multiple_bases_still_verifies() {
let ws = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("newt-core has a workspace root parent");
let ws = ws.to_str().expect("workspace root is valid UTF-8");
let text = "see newt-core/Cargo.toml and also Cargo.toml".to_string();
let out = annotate_against_workspace(text.clone(), ws);
assert_eq!(out, text, "ambiguous-but-real is still a verify: {out}");
}
fn git_in_dir(dir: &std::path::Path, args: &[&str]) {
assert!(
std::process::Command::new("git")
.args(args)
.current_dir(dir)
.output()
.expect("git")
.status
.success(),
"git {args:?} in {}",
dir.display()
);
}
fn init_repo(dir: &std::path::Path) {
std::fs::create_dir_all(dir).unwrap();
git_in_dir(dir, &["init", "-q"]);
git_in_dir(dir, &["config", "user.email", "t@example.com"]);
git_in_dir(dir, &["config", "user.name", "t"]);
}
fn bare_multi_repo_fixture() -> tempfile::TempDir {
let root = tempfile::tempdir().expect("bare multi-repo root");
let repo_a = root.path().join("repoA");
init_repo(&repo_a);
std::fs::write(repo_a.join("a.txt"), "one\n").unwrap();
git_in_dir(&repo_a, &["add", "a.txt"]);
git_in_dir(&repo_a, &["commit", "-q", "-m", "init"]);
std::fs::write(repo_a.join("a.txt"), "one\ntwo\n").unwrap(); init_repo(&root.path().join("repoB"));
root
}
#[test]
fn snapshot_nested_repos_probes_each_first_level_git_child() {
let root = bare_multi_repo_fixture();
assert!(
snapshot_workspace(&root.path().to_string_lossy(), &crate::Scope::All).is_none(),
"the bare root itself is not a repo"
);
let snapshots = snapshot_nested_repos(&root.path().to_string_lossy(), &crate::Scope::All);
assert_eq!(snapshots.len(), 2, "{snapshots:?}");
let repo_a = snapshots
.iter()
.find(|s| s.repo == "repoA")
.expect("repoA probed");
let status_a = repo_a.status.as_ref().expect("repoA's probe must succeed");
assert_eq!(status_a.get("a.txt").map(String::as_str), Some(" M"));
let repo_b = snapshots
.iter()
.find(|s| s.repo == "repoB")
.expect("repoB probed");
assert_eq!(
repo_b
.status
.as_ref()
.expect("repoB's probe must succeed")
.len(),
0,
"repoB is clean"
);
}
#[test]
fn snapshot_nested_repos_ignores_non_repo_subdirs() {
let root = tempfile::tempdir().unwrap();
std::fs::create_dir_all(root.path().join("just_a_folder")).unwrap();
let snapshots = snapshot_nested_repos(&root.path().to_string_lossy(), &crate::Scope::All);
assert!(snapshots.is_empty(), "{snapshots:?}");
}
#[test]
fn snapshot_nested_repos_names_every_repo_as_unprobed_under_a_bounded_scope() {
let root = bare_multi_repo_fixture();
let scope = crate::Scope::only([root.path().to_string_lossy().into_owned()]);
let snapshots = snapshot_nested_repos(&root.path().to_string_lossy(), &scope);
assert_eq!(snapshots.len(), 2, "{snapshots:?}");
for repo in &snapshots {
assert!(
repo.status.is_none(),
"{}: a bounded fs_read scope refuses every metadata git read, \
the same as snapshot_workspace — named, not probed",
repo.repo
);
}
assert!(snapshot_workspace(&root.path().join("repoA").to_string_lossy(), &scope).is_none());
}
#[test]
fn nested_files_changed_between_prefixes_by_repo_and_ignores_clean_repos() {
let root = tempfile::tempdir().unwrap();
let repo_a = root.path().join("repoA");
init_repo(&repo_a);
std::fs::write(repo_a.join("a.txt"), "one\n").unwrap();
git_in_dir(&repo_a, &["add", "a.txt"]);
git_in_dir(&repo_a, &["commit", "-q", "-m", "init"]);
init_repo(&root.path().join("repoB"));
let workspace = root.path().to_string_lossy().into_owned();
let before = snapshot_nested_repos(&workspace, &crate::Scope::All);
std::fs::write(repo_a.join("a.txt"), "one\ntwo\n").unwrap();
let after = snapshot_nested_repos(&workspace, &crate::Scope::All);
let (files, unprobed) = nested_files_changed_between(&before, &after);
assert_eq!(files, vec!["repoA/a.txt".to_string()], "{files:?}");
assert!(unprobed.is_empty(), "{unprobed:?}");
}
#[test]
fn nested_current_paths_lists_every_probed_repos_dirty_files() {
let root = bare_multi_repo_fixture();
let snapshots = snapshot_nested_repos(&root.path().to_string_lossy(), &crate::Scope::All);
let (files, unprobed) = nested_current_paths(&snapshots);
assert_eq!(files, vec!["repoA/a.txt".to_string()], "{files:?}");
assert!(unprobed.is_empty(), "{unprobed:?}");
}
#[test]
fn a_repo_root_still_probes_via_snapshot_workspace_unaffected_by_nesting() {
let root = tempfile::tempdir().unwrap();
init_repo(root.path());
assert!(snapshot_workspace(&root.path().to_string_lossy(), &crate::Scope::All).is_some());
}
#[test]
fn snapshot_workspace_never_discovers_an_enclosing_repo_above_the_workspace() {
let outer = tempfile::tempdir().expect("outer root");
init_repo(outer.path());
std::fs::write(outer.path().join("outer.txt"), "outer\n").unwrap();
git_in_dir(outer.path(), &["add", "outer.txt"]);
git_in_dir(outer.path(), &["commit", "-q", "-m", "outer init"]);
let workspace = outer.path().join("workspace");
std::fs::create_dir_all(&workspace).unwrap();
let inner = workspace.join("inner");
init_repo(&inner);
std::fs::write(inner.join("i.txt"), "one\n").unwrap();
git_in_dir(&inner, &["add", "i.txt"]);
git_in_dir(&inner, &["commit", "-q", "-m", "inner init"]);
std::fs::write(inner.join("i.txt"), "one\ntwo\n").unwrap();
let workspace_str = workspace.to_string_lossy().into_owned();
assert!(
!is_workspace_repo_root(&workspace_str, &crate::Scope::All),
"a subfolder of a larger repo is not its own repo root"
);
assert!(
snapshot_workspace(&workspace_str, &crate::Scope::All).is_none(),
"must never resolve to the enclosing repo's status"
);
let snapshots = snapshot_nested_repos(&workspace_str, &crate::Scope::All);
assert_eq!(snapshots.len(), 1, "{snapshots:?}");
assert_eq!(snapshots[0].repo, "inner");
let status = snapshots[0]
.status
.as_ref()
.expect("inner's probe must succeed");
assert_eq!(status.get("i.txt").map(String::as_str), Some(" M"));
}
#[test]
#[cfg(unix)]
fn is_workspace_repo_root_true_through_a_symlinked_spelling_of_the_root() {
let real = tempfile::tempdir().expect("real root");
init_repo(real.path());
let parent = real.path().parent().expect("tempdir has a parent");
let link = parent.join(format!(
"symlink-{}",
real.path().file_name().unwrap().to_string_lossy()
));
std::os::unix::fs::symlink(real.path(), &link).expect("symlink");
assert!(
is_workspace_repo_root(&link.to_string_lossy(), &crate::Scope::All),
"a symlinked spelling of the real root must still count as its own repo"
);
std::fs::remove_file(&link).ok();
}
#[test]
#[cfg(unix)]
fn snapshot_nested_repos_skips_a_symlink_to_an_outside_repo() {
let workspace = tempfile::tempdir().expect("workspace");
let outside = tempfile::tempdir().expect("outside repo");
init_repo(outside.path());
std::fs::write(outside.path().join("secret.txt"), "leak\n").unwrap();
git_in_dir(outside.path(), &["add", "secret.txt"]);
git_in_dir(outside.path(), &["commit", "-q", "-m", "init"]);
std::fs::write(outside.path().join("secret.txt"), "leak\nmore\n").unwrap();
let link = workspace.path().join("ext");
std::os::unix::fs::symlink(outside.path(), &link).expect("symlink");
let snapshots =
snapshot_nested_repos(&workspace.path().to_string_lossy(), &crate::Scope::All);
assert!(
snapshots.is_empty(),
"a symlinked first-level entry must never be probed: {snapshots:?}"
);
}
#[test]
fn nested_files_changed_between_names_a_repo_deleted_during_the_run() {
let before = vec![NestedRepoSnapshot {
repo: "gone".to_string(),
status: Some(StatusSnapshot::new()),
}];
let after: Vec<NestedRepoSnapshot> = Vec::new();
let (files, unprobed) = nested_files_changed_between(&before, &after);
assert!(files.is_empty(), "{files:?}");
assert_eq!(unprobed, vec!["gone".to_string()], "{unprobed:?}");
}
}
#[cfg(test)]
mod issue_2718_tests {
use super::*;
#[test]
fn plan_steps_do_not_become_branches() {
assert!(claimed_branches("Worktree + branch created. Extraction 1 done.\n6. Push the branch to origin ☐ 7. Open PR").is_empty());
assert_eq!(claimed_branches("Created branch `123`."), ["123"]);
assert_eq!(claimed_branches("Branch is clean on fix/a-1"), ["fix/a-1"]);
}
#[test]
fn shortened_path_and_future_path_are_not_refuted() {
let mut resolve = workspace_claim_resolver("/repo", |p| {
p == std::path::Path::new("/repo/newt-core/src/agentic/mod.rs")
});
assert_eq!(resolve("newt-core/src/agentic/mod.rs"), Some(true));
assert_eq!(resolve("agentic/mod.rs"), Some(true));
let text = "Remaining work:\n1. Extract newt-core/src/agentic/loop_infer.rs\n\nCurrent state:\nUpdated src/missing.rs";
let got = annotate_path_claims(text.into(), |_| Some(false));
let note = got.strip_prefix(text).unwrap();
assert!(!note.contains("loop_infer"), "{got}");
assert!(note.contains("src/missing.rs"), "{got}");
}
#[test]
fn round2_plan_section_does_not_hide_validation_claims() {
let text = "## Next steps\nCreate src/future.rs\n## Validation\nUpdated src/missing.rs";
let got = annotate_path_claims(text.into(), |_| Some(false));
let note = got.strip_prefix(text).unwrap();
assert!(!note.contains("src/future.rs"), "{got}");
assert!(note.contains("src/missing.rs"), "{got}");
}
#[test]
fn observed_worktree_context_stays_inside_read_authority() {
let root = tempfile::tempdir().unwrap();
let launch = root.path().join("launch");
let tree = root.path().join("tree");
std::fs::create_dir_all(&launch).unwrap();
std::fs::create_dir_all(tree.join("src")).unwrap();
std::fs::write(tree.join("src/new.rs"), "// present").unwrap();
let text = "Updated src/new.rs";
assert_eq!(
annotate_in_context(
text.into(),
launch.to_str().unwrap(),
std::slice::from_ref(&tree),
&crate::Scope::All
),
text
);
let denied = annotate_in_context(
text.into(),
launch.to_str().unwrap(),
&[tree],
&crate::Scope::only([launch.to_string_lossy().into_owned()]),
);
assert!(denied.contains("unverified"));
assert!(!denied.contains("not found"));
}
}
#[cfg(test)]
mod issue_2741_tests {
use super::*;
#[test]
fn issue_2741_repository_relative_citation_from_observed_file_parent() {
let mut resolve = workspace_claim_resolver("/task/newt-core/src", |p| {
p == std::path::Path::new("/task/newt-core/src/loop.rs")
});
assert_eq!(resolve("newt-core/src/loop.rs"), Some(true));
assert_eq!(resolve("src/loop.rs"), Some(true));
assert_ne!(resolve("newt-tui/src/loop.rs"), Some(true));
}
}