fix: add plan_cache to base schema + feature/{slug} branch naming
- Add plan_cache table to base SCHEMA so fresh databases have it
(was only created via migration, causing "no such table" errors)
- Change worktree branch naming from `{title}` to `feature/{slug}`
- Add slugify() to handle titles with spaces like "Minimal Job Submission"
- Update cleanup handler to use same naming convention
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8eefd33085
commit
0246ef6a22
2 changed files with 83 additions and 8 deletions
|
|
@ -222,6 +222,16 @@ const SCHEMA: &str = r#"
|
||||||
doc_type,
|
doc_type,
|
||||||
updated_at
|
updated_at
|
||||||
) WHERE deleted_at IS NULL;
|
) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- Plan cache for tracking plan file sync state (RFC 0017)
|
||||||
|
CREATE TABLE IF NOT EXISTS plan_cache (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
document_id INTEGER NOT NULL UNIQUE,
|
||||||
|
cache_mtime TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_plan_cache_document ON plan_cache(document_id);
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
/// FTS5 schema for full-text search
|
/// FTS5 schema for full-text search
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
//! Handles git worktree operations for isolated feature development.
|
//! Handles git worktree operations for isolated feature development.
|
||||||
//!
|
//!
|
||||||
//! Branch naming convention (RFC 0007):
|
//! Branch naming convention (RFC 0007):
|
||||||
//! - RFC file: `NNNN-feature-description.md`
|
//! - RFC file: `NNNN-feature-description.md` or "Feature Description" title
|
||||||
//! - Branch: `feature-description` (number prefix stripped)
|
//! - Branch: `feature/{slug}` where slug is lowercase with hyphens
|
||||||
//! - Worktree: `feature-description`
|
//! - Worktree: `{slug}` directory name
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
|
@ -95,6 +95,47 @@ pub fn strip_rfc_number_prefix(title: &str) -> (String, Option<u32>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a title to a URL-safe slug
|
||||||
|
///
|
||||||
|
/// - Converts to lowercase
|
||||||
|
/// - Replaces spaces and underscores with hyphens
|
||||||
|
/// - Removes non-alphanumeric characters (except hyphens)
|
||||||
|
/// - Collapses multiple hyphens
|
||||||
|
/// - Trims leading/trailing hyphens
|
||||||
|
fn slugify(title: &str) -> String {
|
||||||
|
let slug: String = title
|
||||||
|
.to_lowercase()
|
||||||
|
.chars()
|
||||||
|
.map(|c| {
|
||||||
|
if c.is_ascii_alphanumeric() {
|
||||||
|
c
|
||||||
|
} else if c == ' ' || c == '_' {
|
||||||
|
'-'
|
||||||
|
} else {
|
||||||
|
'-'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Collapse multiple hyphens and trim
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut prev_hyphen = false;
|
||||||
|
for c in slug.chars() {
|
||||||
|
if c == '-' {
|
||||||
|
if !prev_hyphen && !result.is_empty() {
|
||||||
|
result.push(c);
|
||||||
|
}
|
||||||
|
prev_hyphen = true;
|
||||||
|
} else {
|
||||||
|
result.push(c);
|
||||||
|
prev_hyphen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim trailing hyphen
|
||||||
|
result.trim_end_matches('-').to_string()
|
||||||
|
}
|
||||||
|
|
||||||
/// Handle blue_worktree_create
|
/// Handle blue_worktree_create
|
||||||
pub fn handle_create(state: &ProjectState, args: &Value) -> Result<Value, ServerError> {
|
pub fn handle_create(state: &ProjectState, args: &Value) -> Result<Value, ServerError> {
|
||||||
let title = args
|
let title = args
|
||||||
|
|
@ -152,10 +193,11 @@ pub fn handle_create(state: &ProjectState, args: &Value) -> Result<Value, Server
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create branch name and worktree path (RFC 0007: strip number prefix)
|
// Create branch name and worktree path (RFC 0007: feature/{slug} convention)
|
||||||
let (stripped_name, _rfc_number) = strip_rfc_number_prefix(title);
|
let (stripped_name, _rfc_number) = strip_rfc_number_prefix(title);
|
||||||
let branch_name = stripped_name.clone();
|
let slug = slugify(&stripped_name);
|
||||||
let worktree_path = state.home.worktrees_path.join(&stripped_name);
|
let branch_name = format!("feature/{}", slug);
|
||||||
|
let worktree_path = state.home.worktrees_path.join(&slug);
|
||||||
|
|
||||||
// Try to create the git worktree
|
// Try to create the git worktree
|
||||||
let repo_path = state.home.root.clone();
|
let repo_path = state.home.root.clone();
|
||||||
|
|
@ -291,9 +333,10 @@ pub fn handle_cleanup(state: &ProjectState, args: &Value) -> Result<Value, Serve
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.ok_or(ServerError::InvalidParams)?;
|
.ok_or(ServerError::InvalidParams)?;
|
||||||
|
|
||||||
// Support both old (rfc/title) and new (stripped) naming conventions
|
// Support both old and new naming conventions - slugify for feature/ branches
|
||||||
let (stripped_name, _) = strip_rfc_number_prefix(title);
|
let (stripped_name, _) = strip_rfc_number_prefix(title);
|
||||||
let branch_name = stripped_name.clone();
|
let slug = slugify(&stripped_name);
|
||||||
|
let branch_name = format!("feature/{}", slug);
|
||||||
|
|
||||||
// Find the RFC to get worktree info
|
// Find the RFC to get worktree info
|
||||||
let doc = state
|
let doc = state
|
||||||
|
|
@ -491,6 +534,28 @@ mod tests {
|
||||||
assert_eq!(number, None);
|
assert_eq!(number, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slugify() {
|
||||||
|
// Spaces to hyphens, lowercase
|
||||||
|
assert_eq!(slugify("Minimal Job Submission"), "minimal-job-submission");
|
||||||
|
|
||||||
|
// Already slugified
|
||||||
|
assert_eq!(slugify("consistent-branch-naming"), "consistent-branch-naming");
|
||||||
|
|
||||||
|
// Mixed case with spaces
|
||||||
|
assert_eq!(slugify("Add User Authentication"), "add-user-authentication");
|
||||||
|
|
||||||
|
// Underscores converted
|
||||||
|
assert_eq!(slugify("some_feature_name"), "some-feature-name");
|
||||||
|
|
||||||
|
// Special characters removed
|
||||||
|
assert_eq!(slugify("Feature: Add (New) Stuff!"), "feature-add-new-stuff");
|
||||||
|
|
||||||
|
// Multiple spaces/hyphens collapsed
|
||||||
|
assert_eq!(slugify("too many spaces"), "too-many-spaces");
|
||||||
|
assert_eq!(slugify("too---many---hyphens"), "too-many-hyphens");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_worktree_requires_plan() {
|
fn test_worktree_requires_plan() {
|
||||||
use blue_core::{Document, ProjectState};
|
use blue_core::{Document, ProjectState};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue