sift-queue 0.8.0

Queue CLI and queue-native task/review substrate
Documentation
use clap::builder::{StyledStr, Styles};

pub struct HelpDoc {
    sections: Vec<HelpSection>,
}

pub struct HelpSection {
    title: &'static str,
    rows: Vec<HelpRow>,
}

pub enum HelpRow {
    Item {
        usage: &'static str,
        description: &'static str,
    },
    Text(&'static str),
}

impl HelpDoc {
    pub fn new() -> Self {
        Self {
            sections: Vec::new(),
        }
    }

    pub fn section(mut self, section: HelpSection) -> Self {
        self.sections.push(section);
        self
    }

    pub fn render(&self, styles: &Styles) -> StyledStr {
        let header = styles.get_header();
        let literal = styles.get_literal();
        let mut out = String::new();

        for (section_index, section) in self.sections.iter().enumerate() {
            if section_index > 0 {
                out.push_str("\n\n");
            }

            out.push_str(&format!("{header}{}{header:#}\n", section.title));

            let item_width = section
                .rows
                .iter()
                .filter_map(|row| match row {
                    HelpRow::Item { usage, .. } => Some(usage.chars().count()),
                    HelpRow::Text(_) => None,
                })
                .max()
                .unwrap_or(0);

            for (row_index, row) in section.rows.iter().enumerate() {
                if row_index > 0 {
                    out.push('\n');
                }

                match row {
                    HelpRow::Item { usage, description } => {
                        let padding =
                            " ".repeat(item_width.saturating_sub(usage.chars().count()) + 2);
                        out.push_str(&format!(
                            "  {literal}{usage}{literal:#}{padding}{description}"
                        ));
                    }
                    HelpRow::Text(text) => {
                        for (line_index, line) in text.lines().enumerate() {
                            if line_index > 0 {
                                out.push('\n');
                            }
                            out.push_str("  ");
                            out.push_str(line);
                        }
                    }
                }
            }
        }

        StyledStr::from(out)
    }
}

impl HelpSection {
    pub fn new(title: &'static str) -> Self {
        Self {
            title,
            rows: Vec::new(),
        }
    }

    pub fn item(mut self, usage: &'static str, description: &'static str) -> Self {
        self.rows.push(HelpRow::Item { usage, description });
        self
    }

    pub fn text(mut self, text: &'static str) -> Self {
        self.rows.push(HelpRow::Text(text));
        self
    }
}

pub fn root_after_help(styles: &Styles) -> StyledStr {
    HelpDoc::new()
        .section(
            HelpSection::new("Task file:")
                .text("By default, sq discovers the nearest existing .sift/issues.jsonl within the current git worktree and otherwise falls back to <cwd>/.sift/issues.jsonl.")
                .text("Override with -q, --queue <PATH> or SQ_QUEUE_PATH=<PATH>."),
        )
        .section(
            HelpSection::new("Workflow:")
                .item("sq list --ready", "See the next actionable tasks")
                .item("sq add --title <TITLE>", "Create a new task")
                .item("sq show <id>", "Inspect one task in detail")
                .item(
                    "sq edit <id> ...",
                    "Update fields, sources, metadata, or blockers",
                ),
        )
        .section(
            HelpSection::new("Command help:")
                .item(
                    "sq <command> --help",
                    "See command-specific guidance and examples",
                )
                .item("sq prime", "Output workflow context for AI agents"),
        )
        .render(styles)
}