From 8b25e0390b7139b8008d7434dbb1ea13da677eea Mon Sep 17 00:00:00 2001 From: Nathan Broadbent Date: Mon, 1 Sep 2025 21:11:43 +1200 Subject: [PATCH 01/15] fixed config path --- docs/src/content/docs/mcp/tools.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/content/docs/mcp/tools.mdx b/docs/src/content/docs/mcp/tools.mdx index 50d5b00..f5ddef9 100644 --- a/docs/src/content/docs/mcp/tools.mdx +++ b/docs/src/content/docs/mcp/tools.mdx @@ -306,7 +306,7 @@ Last Applied: System: Renamify Version: 1.2.0 - Config: .renamify/config.json found + Config: .renamify/config.toml found History Entries: 47 ``` From f60464757d76abb7174d2f48157122f3f3afca11 Mon Sep 17 00:00:00 2001 From: Nathan Broadbent Date: Mon, 1 Sep 2025 21:26:07 +1200 Subject: [PATCH 02/15] feat: add atomic identifier support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add --atomic, --atomic-search, --atomic-replace CLI flags - Support atomic identifiers in .renamify/config.toml - Treat atomic identifiers as indivisible units during case transformations - Add comprehensive tests for atomic mode - Update all existing tests to handle atomic_config parameter Examples: - DocSpring (atomic) → docspring in snake_case (not doc_spring) - FormAPIController → DocSpringController when FormAPI is atomic - FORMAPI_TOKEN → DOCSPRING_TOKEN preserving atomic boundaries --- .taskmaster/docs/atomic-prd.txt | 208 ++++++++++++++++ CLAUDE.md | 5 + renamify-cli/src/cli/args.rs | 34 +++ renamify-cli/src/main.rs | 11 + renamify-cli/src/plan.rs | 15 +- renamify-cli/src/replace.rs | 1 + renamify-cli/tests/cli_tests.rs | 3 + renamify-core/src/atomic.rs | 231 ++++++++++++++++++ renamify-core/src/case_model.rs | 137 ++++++++++- renamify-core/src/config.rs | 21 ++ renamify-core/src/lib.rs | 1 + renamify-core/src/operations/plan.rs | 2 + renamify-core/src/operations/rename.rs | 3 +- renamify-core/src/scanner.rs | 25 +- renamify-core/tests/acronym_cli_test.rs | 5 + renamify-core/tests/acronym_variant_test.rs | 5 + .../tests/coercion_separator_test.rs | 11 + renamify-core/tests/coercion_tests.rs | 7 + renamify-core/tests/compound_position_test.rs | 4 + renamify-core/tests/compound_word_test.rs | 13 + renamify-core/tests/diff_rendering_test.rs | 2 + renamify-core/tests/dot_directory_test.rs | 4 + .../tests/hyphenated_capitalized_test.rs | 5 + renamify-core/tests/mixed_style_test.rs | 3 + .../tests/multiple_matches_per_line_test.rs | 7 + .../tests/replace_column_position_test.rs | 3 + .../tests/replace_include_flag_test.rs | 3 + .../tests/tokenization_edge_cases_test.rs | 8 + renamify-core/tests/train_case_apply_test.rs | 1 + .../tests/train_case_literals_test.rs | 2 + 30 files changed, 773 insertions(+), 7 deletions(-) create mode 100644 .taskmaster/docs/atomic-prd.txt create mode 100644 renamify-core/src/atomic.rs diff --git a/.taskmaster/docs/atomic-prd.txt b/.taskmaster/docs/atomic-prd.txt new file mode 100644 index 0000000..2dcbf99 --- /dev/null +++ b/.taskmaster/docs/atomic-prd.txt @@ -0,0 +1,208 @@ +# Atomic Identifiers Feature - Product Requirements Document + +## Executive Summary +Add support for treating certain identifiers as "atomic" - indivisible units that should not have word boundaries detected during case transformations. This solves the problem where compound brand names like "DocSpring" or "TweetGit" need to maintain their identity as single words in all case styles. + +## Problem Statement +Currently, when renaming identifiers like "DocSpring" or "TweetGit", renamify detects word boundaries and generates variants like: +- `doc_spring` (snake_case) +- `doc-spring` (kebab-case) +- `DOC_SPRING` (SCREAMING_SNAKE_CASE) + +However, these brand names should be treated as single, indivisible units: +- `docspring` (snake_case) +- `docspring` (kebab-case) +- `DOCSPRING` (SCREAMING_SNAKE_CASE) +- `DocSpring` (PascalCase - the only time there is a word break) + +This is similar to Rails' `inflect.acronym` feature, but for compound words that aren't acronyms. + +## User Stories + +### Story 1: CLI User with Atomic Identifiers +As a developer renaming "TwitGit" to "TweetGit" in my codebase, +I want to specify that both names are atomic, +So that `twit_git_api` becomes `tweetgit_api` (not `tweet_git_api`). + +### Story 2: Project with Persistent Atomic Configuration +As a team maintaining a project with brand names like "DocSpring", +I want to configure these as atomic in a config file, +So that all team members automatically get correct renaming behavior. + +### Story 3: VS Code Extension User +As a VS Code user, +I want checkboxes to mark search/replace terms as atomic, +So that I can control word boundary detection visually. + +### Story 4: Partial Atomic Matching +As a developer with "FormAPIController" in my code, +I want atomic "FormAPI" to match and replace the atomic portion correctly, +So that it becomes "DocSpringController" (not "DocSpringAPIController"). + +## Functional Requirements + +### 1. Core Atomic Detection +- When an identifier is marked as atomic, it should be treated as a single token +- Atomic identifiers should not have internal word boundaries detected +- The atomic property applies to the identifier wherever it appears (standalone or as part of larger identifiers) + +### 2. CLI Flags +- `--atomic` - Both search and replace are atomic +- `--atomic-search` - Only search term is atomic +- `--atomic-replace` - Only replace term is atomic +- `--no-atomic` - Override config, treat as normal +- `--no-atomic-search` - Override config for search term +- `--no-atomic-replace` - Override config for replace term +- Error if user specifies both `--atomic` and `--atomic-search`/`--atomic-replace` + +### 3. Configuration File +- Support `atomic = ["DocSpring", "TweetGit", "GitHub"]` in `.renamify/config.toml` +- Config entries are case-insensitive matched (PascalCase in config matches all cases) +- Config auto-applies when matching identifiers are used +- CLI flags override config settings + +### 4. Variant Generation +For atomic identifier "DocSpring": +- Generate only: `DocSpring`, `docspring`, `DOCSPRING` +- Do NOT generate: `doc_spring`, `DOC_SPRING`, `doc-spring`, `docSpring` + +### 5. Compound Identifier Handling +When "FormAPI" is atomic: +- `FormAPIController` → `DocSpringController` +- `form_api_client` → `docspring_client` +- `FORMAPI_TOKEN` → `DOCSPRING_TOKEN` +- The atomic portion is preserved as a unit within larger identifiers + +### 6. VS Code Extension +- Add "Atomic" checkbox under Search field with tooltip +- Add "Atomic" checkbox under Replace field with tooltip +- Tooltips explain: "Treat as indivisible (no word boundaries)" +- Pre-check boxes if terms match config entries + +### 7. MCP Server +- Add `atomicSearch: boolean` parameter +- Add `atomicReplace: boolean` parameter +- Read and respect `.renamify/config.toml` atomic entries + +## Technical Requirements + +### 1. Case Model Refactoring +- Split `case_model.rs` into smaller modules (it's currently 1100 lines) +- Create `atomic.rs` module for atomic-specific logic +- Maintain backward compatibility + +### 2. Token Generation +- Modify `parse_to_tokens` to accept atomic hints +- When atomic, return single token regardless of internal capitals +- Preserve existing behavior when not atomic + +### 3. Variant Map Generation +- Update `generate_variant_map` to accept atomic flags +- Generate limited variants for atomic identifiers +- Handle mixed atomic/non-atomic scenarios + +### 4. Testing +- Unit tests for atomic token parsing +- Integration tests for compound identifiers +- End-to-end tests with real examples (DocSpring, TweetGit) +- Performance tests to ensure no regression + +## Non-Functional Requirements + +### 1. Performance +- Atomic detection should not significantly impact scan performance +- Config file parsing should be cached + +### 2. Documentation +- Create "Atomic Identifiers" page in Features section +- Include examples of when to use atomic mode +- Document config file format + +### 3. Error Handling +- Clear error messages for conflicting flags +- Warn if atomic identifier contains delimiters + +## Acceptance Criteria + +### Test Case 1: Basic Atomic Renaming +```bash +renamify rename TwitGit TweetGit --atomic +``` +- `twit_git_api` → `tweetgit_api` +- `TWIT_GIT_TOKEN` → `TWEETGIT_TOKEN` +- `TwitGitController` → `TweetGitController` + +### Test Case 2: Config File Auto-Application +`.renamify/config.toml`: +```toml +atomic = ["DocSpring"] +``` +```bash +renamify rename DocSpring Helper +``` +- `docspring_api` → `helper_api` (NOT `doc_spring_api` → `helper_api`) + +### Test Case 3: Partial Identifier Matching +```bash +renamify rename FormAPI DocSpring --atomic +``` +- `FormAPIController` → `DocSpringController` +- `useFormAPIHook` → `useDocSpringHook` + +### Test Case 4: Override Config +`.renamify/config.toml`: +```toml +atomic = ["DocSpring"] +``` +```bash +renamify rename DocSpring doc_spring --no-atomic-search +``` +- `DocSpring` → `doc_spring` (word boundary detected due to override) + +## Implementation Plan + +### Phase 1: Core Support (Week 1) +1. Refactor case_model.rs into modules +2. Implement atomic token parsing +3. Update variant generation +4. Add comprehensive tests + +### Phase 2: CLI Integration (Week 1) +1. Add CLI flags +2. Implement config file support +3. Wire up to plan operation +4. Add integration tests + +### Phase 3: UI Integration (Week 2) +1. Update VS Code extension +2. Update MCP server +3. Add tooltips and help text +4. End-to-end testing + +### Phase 4: Documentation (Week 2) +1. Create atomic identifiers documentation +2. Update existing docs with atomic examples +3. Add to README and help text + +## Success Metrics +- All test cases pass +- No performance regression (scan time within 5% of baseline) +- User feedback positive on solving word boundary issues +- Documentation clear (no support questions about basic usage) + +## Risks and Mitigations + +### Risk 1: Breaking Changes +**Mitigation**: Atomic mode is opt-in, existing behavior unchanged by default + +### Risk 2: Performance Impact +**Mitigation**: Early detection during parsing, benchmark before/after + +### Risk 3: User Confusion +**Mitigation**: Clear documentation, intuitive naming ("atomic"), helpful tooltips + +## Future Enhancements +- Support for regex patterns in atomic config +- Auto-detection of likely atomic identifiers based on casing patterns +- Project-specific dictionaries of atomic terms +- Integration with language-specific naming conventions diff --git a/CLAUDE.md b/CLAUDE.md index 06f0dd4..f26cf0a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -136,6 +136,11 @@ Exit codes: - append only with checksums and revert info +`.renamify/config.toml` + +- Project configuration including atomic identifiers +- `atomic = ["DocSpring", "TweetGit", "GitHub"]` - identifiers treated as indivisible units + ## Search and plan algorithm 1. Detect input case of `` and `` diff --git a/renamify-cli/src/cli/args.rs b/renamify-cli/src/cli/args.rs index 1f3a742..adcb0e3 100644 --- a/renamify-cli/src/cli/args.rs +++ b/renamify-cli/src/cli/args.rs @@ -121,6 +121,34 @@ pub struct AcronymArgs { pub only_acronyms: Vec, } +/// Atomic identifier arguments +#[derive(Args, Debug, Clone)] +pub struct AtomicArgs { + /// Treat both terms as atomic (single words). E.g. DocSpring becomes docspring in snake_case, not doc_spring + #[arg(long, conflicts_with_all = ["atomic_search", "atomic_replace"])] + pub atomic: bool, + + /// Treat search term as atomic (DocSpring → docspring, not doc_spring) + #[arg(long)] + pub atomic_search: bool, + + /// Treat replace term as atomic (DocSpring → docspring, not doc_spring) + #[arg(long)] + pub atomic_replace: bool, + + /// Override config: allow word boundary detection + #[arg(long, conflicts_with = "atomic")] + pub no_atomic: bool, + + /// Override config for search: allow word boundaries + #[arg(long, conflicts_with = "atomic_search")] + pub no_atomic_search: bool, + + /// Override config for replace: allow word boundaries + #[arg(long, conflicts_with = "atomic_replace")] + pub no_atomic_replace: bool, +} + #[derive(Subcommand, Debug)] pub enum Commands { /// Initialize renamify in the current repository @@ -258,6 +286,9 @@ pub enum Commands { #[command(flatten)] acronyms: AcronymArgs, + #[command(flatten)] + atomic: AtomicArgs, + /// Output format for machine consumption #[arg(long, value_enum, default_value = "summary")] output: OutputFormat, @@ -374,6 +405,9 @@ pub enum Commands { #[command(flatten)] acronyms: AcronymArgs, + #[command(flatten)] + atomic: AtomicArgs, + /// Output format for machine consumption #[arg(long, value_enum, default_value = "summary")] output: OutputFormat, diff --git a/renamify-cli/src/main.rs b/renamify-cli/src/main.rs index 966df36..0770e8f 100644 --- a/renamify-cli/src/main.rs +++ b/renamify-cli/src/main.rs @@ -75,6 +75,7 @@ fn main() { plan_out, dry_run, acronyms, + atomic, output, quiet, } => { @@ -111,6 +112,7 @@ fn main() { acronyms.include_acronyms, acronyms.exclude_acronyms, acronyms.only_acronyms, + atomic, output, quiet, false, // regex flag - not used in Plan command @@ -173,6 +175,14 @@ fn main() { acronyms.include_acronyms, acronyms.exclude_acronyms, acronyms.only_acronyms, + cli::args::AtomicArgs { + atomic: false, + atomic_search: false, + atomic_replace: false, + no_atomic: false, + no_atomic_search: false, + no_atomic_replace: false, + }, output, quiet, false, // regex flag - not used in Search command @@ -227,6 +237,7 @@ fn main() { no_rename_root, dry_run, acronyms, + atomic: _, // Not implemented in rename yet output, quiet, } => { diff --git a/renamify-cli/src/plan.rs b/renamify-cli/src/plan.rs index 36c5b45..89ede8f 100644 --- a/renamify-cli/src/plan.rs +++ b/renamify-cli/src/plan.rs @@ -2,7 +2,7 @@ use anyhow::Result; use renamify_core::{plan_operation, OutputFormatter, Style}; use std::path::PathBuf; -use crate::cli::{types::StyleArg, OutputFormat}; +use crate::cli::{args::AtomicArgs, types::StyleArg, OutputFormat}; use renamify_core::Preview; #[allow(clippy::too_many_arguments)] @@ -30,6 +30,7 @@ pub fn handle_plan( include_acronyms: Vec, exclude_acronyms: Vec, only_acronyms: Vec, + atomic: AtomicArgs, output: OutputFormat, quiet: bool, _regex: bool, // TODO: Implement regex mode @@ -52,6 +53,17 @@ pub fn handle_plan( let include_styles: Vec