Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions .claude/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,27 @@ Users should be able to:
The release tool uses semantic versioning for config files to handle breaking changes and new features gracefully.

### Current Version
- **Latest**: 1.2 (defined in `src/release_tool/migrations/manager.py`)
- Stored in config file as `config_version = "1.2"`
- **Latest**: 1.10 (defined in `src/release_tool/migrations/manager.py`)
- Stored in config file as `config_version = "1.10"`

### Version History
- **1.0**: Initial config format
- **1.1**: Added template variables (issue_url, pr_url)
- **1.2**: Added partial_issue_action policy
- **1.3**: Fixed issue key format (removed '#' prefix from database)
- **1.4**: Added dual template support (GitHub + Docusaurus)
- **1.5**: Renamed issue terminology to issue for consistency
- **1.6**: Refactored templates (pr_code.templates array)
- **1.7**: Moved version policy to templates
- **1.8**: Removed GitHub token from config (security)
- **1.9**: Added multi-repository support with aliases
- Changed `repository.code_repo` (string) → `repository.code_repos` (list of RepoInfo)
- Changed `repository.issue_repos` (list of strings) → list of RepoInfo objects
- Each repository now has `link` (owner/repo) and `alias` (short identifier)
- Template variables: `{{code_repo.primary.slug}}`, `{{code_repo.<alias>.link}}`, etc.
- Removed `pull.clone_code_repo` field (code repos always cloned now)
- Removed `pull.code_repo_path` field (path always uses `.release_tool_cache/{repo_alias}`)
- Migration auto-generates aliases from repository names

### When to Bump Config Version

Expand Down
52 changes: 52 additions & 0 deletions .claude/update_source_code_refs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""Script to update source code references from code_repo to get_primary_code_repo().link."""

import re
from pathlib import Path

def update_file(file_path: Path):
"""Update a single file."""
print(f"Updating {file_path}...")

content = file_path.read_text()
original_content = content

# Replace config.repository.code_repo with config.get_primary_code_repo().link
# But be careful with comments and documentation
pattern = r'(\w+)\.repository\.code_repo(?!\w)'

def replacer(match):
var_name = match.group(1)
return f'{var_name}.get_primary_code_repo().link'

content = re.sub(pattern, replacer, content)

if content != original_content:
file_path.write_text(content)
print(f" ✓ Updated {file_path.name}")
else:
print(f" - No changes needed for {file_path.name}")

def main():
"""Main entry point."""
commands_dir = Path(__file__).parent.parent / "src" / "release_tool" / "commands"

# List of command files to update
files_to_update = [
"push.py",
"cancel.py",
"list_releases.py",
"pull.py",
"generate.py",
"merge.py",
]

for filename in files_to_update:
file_path = commands_dir / filename
if file_path.exists():
update_file(file_path)
else:
print(f" ✗ File not found: {filename}")

if __name__ == "__main__":
main()
110 changes: 110 additions & 0 deletions .claude/update_test_configs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Script to update test config dictionaries to new format (v1.9)."""

import re
from pathlib import Path

def extract_alias_from_repo(repo_link: str) -> str:
"""Extract alias from repo link (e.g., 'sequentech/step' -> 'step')."""
return repo_link.split('/')[-1]

def replace_code_repo(content: str) -> str:
"""Replace code_repo with code_repos format."""
# Pattern: "code_repo": "owner/repo"
pattern = r'"code_repo":\s*"([^"]+)"'

def replacer(match):
repo_link = match.group(1)
alias = extract_alias_from_repo(repo_link)
return f'"code_repos": [{{"link": "{repo_link}", "alias": "{alias}"}}]'

return re.sub(pattern, replacer, content)

def replace_issue_repos(content: str) -> str:
"""Replace issue_repos list of strings with list of RepoInfo."""
# Pattern: "issue_repos": ["repo1", "repo2", ...]
# This is tricky because the list can span multiple lines
# Let's use a simpler approach - find each issue_repos and handle it

pattern = r'"issue_repos":\s*\[((?:[^]]*?))\]'

def replacer(match):
inner = match.group(1).strip()
if not inner:
# Empty list
return '"issue_repos": []'

# Extract all quoted strings
repos = re.findall(r'"([^"]+)"', inner)

# Build RepoInfo objects
repo_infos = []
for repo_link in repos:
alias = extract_alias_from_repo(repo_link)
repo_infos.append(f'{{"link": "{repo_link}", "alias": "{alias}"}}')

return f'"issue_repos": [{", ".join(repo_infos)}]'

return re.sub(pattern, replacer, content, flags=re.DOTALL)

def remove_clone_code_repo(content: str) -> str:
"""Remove clone_code_repo field from config dicts."""
# Pattern: "clone_code_repo": True/False with optional comma and whitespace
pattern = r',?\s*"clone_code_repo":\s*(?:True|False)\s*,?'

# Replace with empty string, but need to handle comma cleanup
def replacer(match):
text = match.group(0)
# If there's a comma before and after, keep one
if text.strip().startswith(',') and text.strip().endswith(','):
return ','
return ''

return re.sub(pattern, replacer, content)

def update_file(file_path: Path):
"""Update a single file."""
print(f"Updating {file_path}...")

content = file_path.read_text()
original_content = content

# Apply transformations
content = replace_code_repo(content)
content = replace_issue_repos(content)
content = remove_clone_code_repo(content)

if content != original_content:
file_path.write_text(content)
print(f" ✓ Updated {file_path.name}")
else:
print(f" - No changes needed for {file_path.name}")

def main():
"""Main entry point."""
test_dir = Path(__file__).parent.parent / "tests"

# List of files to update
files_to_update = [
"test_policies.py",
"test_e2e_cancel.py",
"test_cancel.py",
"test_template_separation.py",
"test_output_template.py",
"test_push.py",
"test_push_mark_published_mode.py",
"test_partial_issues.py",
"test_inclusion_policy.py",
"test_default_template.py",
"test_category_validation.py",
]

for filename in files_to_update:
file_path = test_dir / filename
if file_path.exists():
update_file(file_path)
else:
print(f" ✗ File not found: {filename}")

if __name__ == "__main__":
main()
Loading