1use std::path::Path;
8
9pub use crate::RunOptions;
10
11pub mod actions {
13 pub use crate::actions::{Dispatch, Interface, Offer, available, list, offered, perform};
14}
15
16pub mod person {
19 pub use crate::person::{Stream, TIMEOUT, open_terminal, run, sandboxed, shown, speccoder};
20}
21
22pub mod copy {
24 pub use crate::copy::block;
25 pub use crate::doctor::bug_report;
26}
27
28pub mod approvals {
30 pub use crate::approvals::{Answer, Approver, Presentation, Who, list};
31}
32
33pub mod progress {
35 pub use crate::progress::{Event, Progress, read_live};
36}
37
38pub mod status {
40 pub use crate::status::report;
41}
42
43pub mod shape {
46 pub use crate::shape::{FIELDS, findings, judge, shape};
47}
48
49pub mod next {
51 pub use crate::next::attach;
52}
53
54pub mod review {
56 pub use crate::comments::list as comments;
58 pub use crate::review::{readiness, report};
59}
60
61pub mod planning {
64 pub use crate::planning_chain::{similar_command, similar_to, suggest};
65}
66
67pub mod new_project {
69 pub use crate::new_project::{Creation, creation, first_feature, plan, undo_offer};
70 pub use speccoder_core::new_project::{NewChoices, Stack, slug};
71}
72
73pub mod welcome {
75 pub use crate::welcome::{
76 Recent, RecentView, default_parent, folders, forget, is_project, remember, tilde, views,
77 };
78}
79
80pub mod sample {
82 pub use crate::sample::{available, copy, home, missing_tool};
83}
84
85pub mod inbox {
87 pub use crate::inbox::inbox;
88}
89
90pub mod bug {
92 pub use crate::bug::show;
93}
94
95pub mod evidence {
97 pub use crate::evidence::{account_of, banner, rows, statements};
98}
99
100pub mod tour {
102 use std::path::Path;
103
104 use speccoder_core::{CommandData, CommandResult, CommandStatus};
105
106 #[must_use]
110 pub fn of_task(root: &Path, feature: Option<&str>, task: &str) -> CommandResult {
111 let selected = || match crate::inspect(root, None).data() {
112 Some(CommandData::Inspect(snap)) => {
113 snap.feature.as_ref().map(|f| f.feature.name.clone())
114 }
115 _ => None,
116 };
117 let Some(feature) = feature.map(str::to_owned).or_else(selected) else {
118 return CommandResult::new(
119 "tour",
120 CommandStatus::SelectionNeeded,
121 "no feature selected",
122 );
123 };
124 crate::tour::command(
125 root,
126 &feature,
127 task,
128 &crate::attest::branch::Options::default(),
129 "tour",
130 )
131 }
132}
133
134pub mod documents {
136 pub use crate::documents::{of_feature, render};
137}
138
139pub mod doctor {
141 pub use crate::doctor::{agents, run};
142}
143
144pub mod settings {
146 pub use crate::approvals::coverage::show as coverage;
148 pub use crate::settings::{apply, commit, show};
149}
150
151pub mod controls {
153 pub use crate::controls::{SkillCost, cost, skill_cost};
154}
155
156pub mod code {
158 pub use crate::code::{changes, file, tree, worktree_file, worktree_view};
159}
160
161pub mod init {
163 pub use crate::init::{commit_written, finish, prepare, preview, root_of};
164}
165
166pub mod current {
168 pub use crate::current::list;
169}
170
171pub mod handoff {
173 pub use crate::handoff::{PrOptions, pr};
174}
175
176pub mod worktree {
178 pub use crate::worktree::state_dir;
179}
180
181pub mod github {
183 pub use crate::github::{
184 Ctx, RepoEntry, description, issue, issues, login_with_token, pr_for, reach, refused, repo,
185 repositories, started, status, valid_slug,
186 };
187
188 pub mod auth {
190 pub use crate::github::auth::{Pending, client_id, start, wait};
191 }
192
193 pub mod http {
195 pub use crate::github::http::oauth;
196 }
197}
198
199#[derive(Debug, Clone, Copy)]
201pub struct FormatWords {
202 pub name: &'static str,
204 pub unit: &'static str,
206}
207
208#[must_use]
210pub fn format_words(root: &Path) -> FormatWords {
211 let format = crate::format::at(root);
212 FormatWords {
213 name: format.wording().name,
214 unit: format.messages().unit,
215 }
216}
217
218#[derive(Debug, Clone, serde::Serialize)]
222pub struct PlanningWords {
223 pub creates: Option<String>,
225 pub toward: Vec<String>,
227 pub checks: Option<String>,
229 pub revises: Option<String>,
231}
232
233#[must_use]
235pub fn planning_words(root: &Path) -> PlanningWords {
236 let p = crate::format::at(root).planning();
237 let full = |s: &str| format!("{}{s}", p.prefix);
238 let creates = p.creates.first().map(|c| full(c));
239 PlanningWords {
240 toward: (p.toward_tasks)(false)
241 .iter()
242 .map(|s| full(s))
243 .filter(|s| Some(s) != creates.as_ref())
244 .collect(),
245 creates,
246 checks: p.checks.map(full),
247 revises: p.revises.map(full),
248 }
249}
250
251#[must_use]
253pub fn create_skill(root: &Path) -> Option<String> {
254 let planning = crate::format::at(root).planning();
255 planning
256 .creates
257 .first()
258 .map(|c| format!("{}{c}", planning.prefix))
259}
260
261#[must_use]
264pub fn commit_patch(root: &Path, commit: &str) -> String {
265 commit_patch_of(root, commit)
266}
267
268fn reachable(git: &crate::git::Git, id: &str) -> bool {
270 if git
271 .succeeds(&["merge-base", "--is-ancestor", id, "HEAD"])
272 .unwrap_or(false)
273 {
274 return true;
275 }
276 git.line(&[
277 "for-each-ref",
278 "--count=1",
279 "--format=%(refname)",
280 "--contains",
281 id,
282 "refs/heads",
283 "refs/speccoder/attempts",
284 ])
285 .is_ok_and(|l| !l.trim().is_empty())
286}
287
288fn commit_patch_of(root: &Path, commit: &str) -> String {
289 let git = crate::git::Git::at(root);
290 let Ok(id) = git.line(&[
292 "rev-parse",
293 "--verify",
294 "-q",
295 &format!("{commit}^{{commit}}"),
296 ]) else {
297 return String::new();
298 };
299 if !reachable(&git, &id) {
303 return String::new();
304 }
305 git.run(&["show", "--no-color", "--patch", "--format=%h %s%n%n%b", &id])
306 .map(|b| String::from_utf8_lossy(&b).into_owned())
307 .unwrap_or_default()
308}