From 878eb9e83d76659699bc80006b92f18469114d8b Mon Sep 17 00:00:00 2001 From: erishforG Date: Sat, 5 Sep 2026 09:51:14 +0900 Subject: [PATCH] =?UTF-8?q?feat(ship):=20Phase=201=20template=20variable?= =?UTF-8?q?=20substitution=20=E2=80=94=20Refs=20#304?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `substitute_template_vars()` helper that expands `{{ticket}}`, `{{branch}}`, `{{title}}`, `{{ticket_url}}` placeholders in PR template content before embedding it into the PR body. Changes: - src/cli/commands/ship.rs: - New `substitute_template_vars()` with RustDoc explaining each var - `build_pr_body()` gains `branch: &str` parameter; passes it + other context to substitute_template_vars when template content is present - Template section comment updated to reference #304 alongside #233 - 5 unit tests: all-vars, optional-vars-empty, unknown-var-intact, multi-occurrence, build_pr_body end-to-end Unknown `{{…}}` tokens are left intact so templates using other tooling variables are not silently mangled. Refs #304 Co-Authored-By: Claude Sonnet 4.6 --- src/cli/commands/ship.rs | 88 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/src/cli/commands/ship.rs b/src/cli/commands/ship.rs index 4009d46..34db416 100644 --- a/src/cli/commands/ship.rs +++ b/src/cli/commands/ship.rs @@ -172,6 +172,7 @@ pub async fn ship( let pr_body = build_pr_body( &result.ticket, + &result.branch, effective_title, ticket_url.as_deref(), stack_info.as_ref(), @@ -421,8 +422,33 @@ fn gather_stack_info(manager: &WorktreeManager, ticket: &str) -> Option, + ticket_url: Option<&str>, +) -> String { + template + .replace("{{ticket}}", ticket) + .replace("{{branch}}", branch) + .replace("{{title}}", title.unwrap_or("")) + .replace("{{ticket_url}}", ticket_url.unwrap_or("")) +} + fn build_pr_body( ticket: &str, + branch: &str, title: Option<&str>, ticket_url: Option<&str>, stack_info: Option<&StackPrInfo>, @@ -461,10 +487,11 @@ fn build_pr_body( body.push('\n'); } - // Include PR template content (#233) + // Include PR template content (#233 #304) with variable substitution. if let Some(tmpl) = template_content { + let rendered = substitute_template_vars(tmpl, ticket, branch, title, ticket_url); body.push_str("---\n\n"); - body.push_str(tmpl); + body.push_str(&rendered); body.push('\n'); } @@ -504,3 +531,60 @@ fn resolve_template(repo_root: &Path, explicit_path: Option<&str>) -> Option