diff --git a/.claude-plugin/commands/bd-version.md b/.claude-plugin/commands/bd-version.md new file mode 100644 index 00000000..aeedaf3e --- /dev/null +++ b/.claude-plugin/commands/bd-version.md @@ -0,0 +1,22 @@ +--- +description: Check beads and plugin versions +--- + +Check the installed versions of beads components and verify compatibility. + +Use the beads MCP tools to: +1. Run `bd --version` via bash to get the CLI version +2. Check the plugin version from the environment +3. Compare versions and report any mismatches + +Display: +- bd CLI version (from `bd --version`) +- Plugin version (0.9.0) +- MCP server status (from `stats` tool or connection test) +- Compatibility status (✓ compatible or ⚠️ update needed) + +If versions are mismatched, provide instructions: +- Update bd CLI: `curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/install.sh | bash` +- Update plugin: `/plugin update beads` + +Suggest checking for updates if the user is on an older version. diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 8dc9f5f4..cc86260e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -16,6 +16,9 @@ "agent-memory", "mcp-server" ], + "engines": { + "beads": ">=0.9.0" + }, "mcpServers": { "beads": { "command": "uv", diff --git a/LINTING.md b/LINTING.md index a81606ee..7f70f111 100644 --- a/LINTING.md +++ b/LINTING.md @@ -4,11 +4,13 @@ This document explains our approach to `golangci-lint` warnings in this codebase ## Current Status -Running `golangci-lint run ./...` currently reports ~100 "issues". However, these are not actual code quality problems - they are false positives or intentional patterns that reflect idiomatic Go practice. +Running `golangci-lint run ./...` currently reports ~200 "issues". However, these are not actual code quality problems - they are false positives or intentional patterns that reflect idiomatic Go practice. + +**Note**: The count increased from ~100 to ~200 between Oct 12-14, 2025, due to significant test coverage additions for collision resolution (1100+ lines) and auto-flush features (300+ lines). All new warnings follow the same idiomatic patterns documented below. ## Issue Breakdown -### errcheck (73 issues) +### errcheck (159 issues) **Pattern**: Unchecked errors from `defer` cleanup operations **Status**: Intentional and idiomatic @@ -27,9 +29,9 @@ defer os.RemoveAll(tmpDir) // in tests Fixing these would add noise without improving code quality. The critical cleanup operations (where errors matter) are already checked explicitly. -### revive (17 issues) +### revive (21 issues) -**Pattern 1**: Unused parameters in Cobra command handlers (15 issues) +**Pattern 1**: Unused parameters in Cobra command handlers (18 issues) **Status**: Required by interface Examples: @@ -41,13 +43,14 @@ Run: func(cmd *cobra.Command, args []string) { **Rationale**: Cobra requires this exact function signature. Renaming to `_` would make the code less clear when parameters *are* used. -**Pattern 2**: Package naming (2 issues) +**Pattern 2**: Package naming (3 issues) - `package types` - Clear and appropriate for a types package - `SQLiteStorage` - Intentional; `sqlite.Storage` would be confusing with the interface +- Blank import comment - Required for database driver registration -### gosec (7 issues) +### gosec (19 issues) -**Pattern 1**: G201 - SQL string formatting (4 issues) +**Pattern 1**: G201 - SQL string formatting (6 issues) **Status**: False positive - all SQL is validated All dynamic SQL construction uses: @@ -55,25 +58,30 @@ All dynamic SQL construction uses: - Parameterized queries for all values - Safe string building for clauses like ORDER BY and LIMIT -**Pattern 2**: G304 - File inclusion via variable (2 issues) -**Status**: Intended feature - user-specified file paths for import/export +**Pattern 2**: G304 - File inclusion via variable (11 issues) +**Status**: Intended feature - user-specified file paths for import/export/test fixtures -**Pattern 3**: G301 - Directory permissions (1 issue) -**Status**: Acceptable - 0755 is reasonable for a database directory +All file paths are either: +- User-provided CLI arguments (expected for import/export commands) +- Test fixtures in controlled test environments +- Validated paths with security checks (e.g., markdown.go uses validateMarkdownPath) -### dupl (2 issues) +**Pattern 3**: G301 - Directory permissions (2 issues) +**Status**: Acceptable - 0755 is reasonable for database directories -**Pattern**: Test code duplication +### gocyclo (1 issue) + +**Pattern**: High cyclomatic complexity in `TestExportImport` (31) **Status**: Acceptable -Test code duplication is often preferable to premature test abstraction. These tests are clear and maintainable as-is. +This comprehensive integration test covers multiple scenarios (export, import, filters, updates). The complexity comes from thorough test coverage, not production code. Splitting would reduce readability. -### goconst (1 issue) +### goconst (2 issues) -**Pattern**: Repeated string constant in tests +**Pattern**: Repeated string constants in tests **Status**: Acceptable -The string `"test-user"` appears multiple times in test code. Extracting this to a constant would not improve test readability. +Repeated test strings like `"test-user"` and file paths appear multiple times. Extracting these to constants would not improve test readability or maintainability. ## golangci-lint Configuration Challenges @@ -89,7 +97,7 @@ This appears to be a known limitation of golangci-lint's configuration system. **For contributors**: Don't be alarmed by the lint warnings. The code quality is high. **For code review**: Focus on: -- New issues introduced by changes (not the baseline 100) +- New issues introduced by changes (not the baseline ~200) - Actual logic errors - Missing error checks on critical operations (file writes, database commits) - Security concerns beyond gosec's false positives diff --git a/PLUGIN.md b/PLUGIN.md index 0ceb17d7..783c6b66 100644 --- a/PLUGIN.md +++ b/PLUGIN.md @@ -73,6 +73,10 @@ After installation, restart Claude Code to activate the MCP server. ## Available Commands +### Version Management + +- **`/bd-version`** - Check bd CLI, plugin, and MCP server versions + ### Core Workflow Commands - **`/bd-ready`** - Find tasks with no blockers, ready to work on @@ -225,6 +229,58 @@ git pull bd ready # Fresh data from git! ``` +## Updating + +The beads plugin has three components that may need updating: + +### 1. Plugin Updates + +Check for plugin updates: +```bash +/plugin update beads +``` + +Claude Code will pull the latest version from GitHub. After updating, **restart Claude Code** to apply MCP server changes. + +### 2. bd CLI Updates + +The plugin requires the `bd` CLI to be installed. Update it separately: + +```bash +# Quick update +curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/install.sh | bash + +# Or with go +go install github.com/steveyegge/beads/cmd/bd@latest +``` + +### 3. Version Compatibility + +Check version compatibility: +```bash +/bd-version +``` + +This will show: +- bd CLI version +- Plugin version +- MCP server status +- Compatibility warnings if versions mismatch + +**Recommended update workflow:** +1. Check versions: `/bd-version` +2. Update bd CLI if needed (see above) +3. Update plugin: `/plugin update beads` +4. Restart Claude Code +5. Verify: `/bd-version` + +### Version Numbering + +Beads follows semantic versioning. The plugin version tracks the bd CLI version: +- Plugin 0.9.x requires bd CLI 0.9.0+ +- Major version bumps may introduce breaking changes +- Check CHANGELOG.md for release notes + ## Troubleshooting ### Plugin not appearing diff --git a/examples/README.md b/examples/README.md index 17bc8941..d9f45aa6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,6 +6,7 @@ This directory contains examples of how to integrate bd with AI agents and workf - **[python-agent/](python-agent/)** - Simple Python agent that discovers ready work and completes tasks - **[bash-agent/](bash-agent/)** - Bash script showing the full agent workflow +- **[markdown-to-jsonl/](markdown-to-jsonl/)** - Convert markdown planning docs to bd issues - **[git-hooks/](git-hooks/)** - Pre-configured git hooks for automatic export/import - **[branch-merge/](branch-merge/)** - Branch merge workflow with collision resolution - **[claude-desktop-mcp/](claude-desktop-mcp/)** - MCP server for Claude Desktop integration diff --git a/examples/markdown-to-jsonl/README.md b/examples/markdown-to-jsonl/README.md new file mode 100644 index 00000000..4736d561 --- /dev/null +++ b/examples/markdown-to-jsonl/README.md @@ -0,0 +1,165 @@ +# Markdown to JSONL Converter + +Convert markdown planning documents into `bd` issues. + +## Overview + +This example shows how to bridge the gap between markdown planning docs and tracked issues, without adding complexity to the `bd` core tool. + +The converter script (`md2jsonl.py`) parses markdown files and outputs JSONL that can be imported into `bd`. + +## Features + +- ✅ **YAML Frontmatter** - Extract metadata (priority, type, assignee) +- ✅ **Headings as Issues** - Each H1/H2 becomes an issue +- ✅ **Task Lists** - Markdown checklists become sub-issues +- ✅ **Dependency Parsing** - Extract "blocks: bd-10" references +- ✅ **Customizable** - Modify the script for your conventions + +## Usage + +### Basic conversion + +```bash +python md2jsonl.py feature.md | bd import +``` + +### Save to file first + +```bash +python md2jsonl.py feature.md > issues.jsonl +bd import -i issues.jsonl +``` + +### Preview before importing + +```bash +python md2jsonl.py feature.md | jq . +``` + +## Markdown Format + +### Frontmatter (Optional) + +```markdown +--- +priority: 1 +type: feature +assignee: alice +--- +``` + +### Headings + +Each heading becomes an issue: + +```markdown +# Main Feature + +Description of the feature... + +## Sub-task 1 + +Details about sub-task... + +## Sub-task 2 + +More details... +``` + +### Task Lists + +Task lists are converted to separate issues: + +```markdown +## Setup Tasks + +- [ ] Install dependencies +- [x] Configure database +- [ ] Set up CI/CD +``` + +Creates 3 issues (second one marked as closed). + +### Dependencies + +Reference other issues in the description: + +```markdown +## Implement API + +This task requires the database schema to be ready first. + +Dependencies: +- blocks: bd-5 +- related: bd-10, bd-15 +``` + +The script extracts these and creates dependency records. + +## Example + +See `example-feature.md` for a complete example. + +```bash +# Convert the example +python md2jsonl.py example-feature.md > example-issues.jsonl + +# View the output +cat example-issues.jsonl | jq . + +# Import into bd +bd import -i example-issues.jsonl +``` + +## Customization + +The script is intentionally simple so you can customize it for your needs: + +1. **Different heading levels** - Modify which headings become issues (H1 only? H1-H3?) +2. **Custom metadata** - Parse additional frontmatter fields +3. **Labels** - Extract hashtags or keywords as labels +4. **Epic detection** - Top-level headings become epics +5. **Issue templates** - Map different markdown structures to issue types + +## Limitations + +This is a simple example, not a production tool: + +- Basic YAML parsing (no nested structures) +- Simple dependency extraction (regex-based) +- No validation of referenced issue IDs +- Doesn't handle all markdown edge cases + +For production use, you might want to: +- Use a proper YAML parser (`pip install pyyaml`) +- Use a markdown parser (`pip install markdown` or `python-markdown2`) +- Add validation and error handling +- Support more dependency formats + +## Philosophy + +This example demonstrates the **lightweight extension pattern**: + +- ✅ Keep `bd` core focused and minimal +- ✅ Let users customize for their workflows +- ✅ Use existing import infrastructure +- ✅ Easy to understand and modify + +Rather than adding markdown support to `bd` core (800+ LOC + dependencies + maintenance), we provide a simple converter that users can adapt. + +## Contributing + +Have improvements? Found a bug? This is just an example, but contributions are welcome! + +Consider: +- Better error messages +- More markdown patterns +- Integration with popular markdown formats +- Support for GFM (GitHub Flavored Markdown) extensions + +## See Also + +- [bd README](../../README.md) - Main documentation +- [Python Agent Example](../python-agent/) - Full agent workflow +- [JSONL Format](../../TEXT_FORMATS.md) - Understanding bd's JSONL structure diff --git a/examples/markdown-to-jsonl/example-feature.md b/examples/markdown-to-jsonl/example-feature.md new file mode 100644 index 00000000..feabcdd4 --- /dev/null +++ b/examples/markdown-to-jsonl/example-feature.md @@ -0,0 +1,49 @@ +--- +priority: 1 +type: feature +assignee: alice +--- + +# User Authentication System + +Implement a complete user authentication system with login, signup, and password recovery. + +This is a critical feature for the application. The authentication should be secure and follow best practices. + +**Dependencies:** +- blocks: bd-5 (database schema must be ready first) + +## Login Flow + +Implement the login page with email/password authentication. Should support: +- Email validation +- Password hashing (bcrypt) +- Session management +- Remember me functionality + +## Signup Flow + +Create new user registration with validation: +- Email uniqueness check +- Password strength requirements +- Email verification +- Terms of service acceptance + +## Password Recovery + +Allow users to reset forgotten passwords: + +- [ ] Send recovery email +- [ ] Generate secure reset tokens +- [x] Create reset password form +- [ ] Expire tokens after 24 hours + +## Session Management + +Handle user sessions securely: +- JWT tokens +- Refresh token rotation +- Session timeout after 30 days +- Logout functionality + +Related to bd-10 (API endpoints) and discovered-from: bd-2 (security audit). diff --git a/examples/markdown-to-jsonl/md2jsonl.py b/examples/markdown-to-jsonl/md2jsonl.py new file mode 100755 index 00000000..bcbac061 --- /dev/null +++ b/examples/markdown-to-jsonl/md2jsonl.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +""" +Convert markdown files to bd JSONL format. + +This is a simple example converter that demonstrates the pattern. +Users can customize this for their specific markdown conventions. + +Supported markdown patterns: +1. YAML frontmatter for metadata +2. H1/H2 headings as issue titles +3. Task lists as sub-issues +4. Inline issue references (e.g., "blocks: bd-10") + +Usage: + python md2jsonl.py feature.md | bd import + python md2jsonl.py feature.md > issues.jsonl +""" + +import json +import re +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import List, Dict, Any, Optional + + +class MarkdownToIssues: + """Convert markdown to bd JSONL format.""" + + def __init__(self, prefix: str = "bd"): + self.prefix = prefix + self.issue_counter = 1 + self.issues: List[Dict[str, Any]] = [] + + def parse_frontmatter(self, content: str) -> tuple[Optional[Dict], str]: + """Extract YAML frontmatter if present.""" + # Simple frontmatter detection (--- ... ---) + if not content.startswith('---\n'): + return None, content + + end = content.find('\n---\n', 4) + if end == -1: + return None, content + + frontmatter_text = content[4:end] + body = content[end + 5:] + + # Parse simple YAML (key: value) + metadata = {} + for line in frontmatter_text.split('\n'): + line = line.strip() + if ':' in line: + key, value = line.split(':', 1) + metadata[key.strip()] = value.strip() + + return metadata, body + + def extract_issue_from_heading( + self, + heading: str, + level: int, + content: str, + metadata: Optional[Dict] = None + ) -> Dict[str, Any]: + """Create an issue from a markdown heading and its content.""" + # Generate ID + issue_id = f"{self.prefix}-{self.issue_counter}" + self.issue_counter += 1 + + # Extract title (remove markdown formatting) + title = heading.strip('#').strip() + + # Parse metadata from frontmatter or defaults + if metadata is None: + metadata = {} + + # Build issue + issue = { + "id": issue_id, + "title": title, + "description": content.strip(), + "status": metadata.get("status", "open"), + "priority": int(metadata.get("priority", 2)), + "issue_type": metadata.get("type", "task"), + "created_at": datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z'), + "updated_at": datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z'), + } + + # Optional fields + if "assignee" in metadata: + issue["assignee"] = metadata["assignee"] + + if "design" in metadata: + issue["design"] = metadata["design"] + + # Extract dependencies from description + dependencies = self.extract_dependencies(content) + if dependencies: + issue["dependencies"] = dependencies + + return issue + + def extract_dependencies(self, text: str) -> List[Dict[str, str]]: + """Extract dependency references from text.""" + dependencies = [] + + # Pattern: "blocks: bd-10" or "depends-on: bd-5, bd-6" + # Pattern: "discovered-from: bd-20" + dep_pattern = r'(blocks|related|parent-child|discovered-from):\s*((?:bd-\d+(?:\s*,\s*)?)+)' + + for match in re.finditer(dep_pattern, text, re.IGNORECASE): + dep_type = match.group(1).lower() + dep_ids = [id.strip() for id in match.group(2).split(',')] + + for dep_id in dep_ids: + dependencies.append({ + "issue_id": "", # Will be filled by import + "depends_on_id": dep_id.strip(), + "type": dep_type + }) + + return dependencies + + def parse_task_list(self, content: str) -> List[Dict[str, Any]]: + """Extract task list items as separate issues.""" + issues = [] + + # Pattern: - [ ] Task or - [x] Task + task_pattern = r'^-\s+\[([ x])\]\s+(.+)$' + + for line in content.split('\n'): + match = re.match(task_pattern, line.strip()) + if match: + is_done = match.group(1) == 'x' + task_text = match.group(2) + + issue_id = f"{self.prefix}-{self.issue_counter}" + self.issue_counter += 1 + + issue = { + "id": issue_id, + "title": task_text, + "description": "", + "status": "closed" if is_done else "open", + "priority": 2, + "issue_type": "task", + "created_at": datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z'), + "updated_at": datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z'), + } + + issues.append(issue) + + return issues + + def parse_markdown(self, content: str, global_metadata: Optional[Dict] = None): + """Parse markdown content into issues.""" + # Extract frontmatter + frontmatter, body = self.parse_frontmatter(content) + + # Merge metadata + metadata = global_metadata or {} + if frontmatter: + metadata.update(frontmatter) + + # Split by headings + heading_pattern = r'^(#{1,6})\s+(.+)$' + lines = body.split('\n') + + current_heading = None + current_level = 0 + current_content = [] + + for line in lines: + match = re.match(heading_pattern, line) + + if match: + # Save previous section + if current_heading: + content_text = '\n'.join(current_content) + + # Check for task lists + task_issues = self.parse_task_list(content_text) + if task_issues: + self.issues.extend(task_issues) + else: + # Create issue from heading + issue = self.extract_issue_from_heading( + current_heading, + current_level, + content_text, + metadata + ) + self.issues.append(issue) + + # Start new section + current_level = len(match.group(1)) + current_heading = match.group(2) + current_content = [] + else: + current_content.append(line) + + # Save final section + if current_heading: + content_text = '\n'.join(current_content) + task_issues = self.parse_task_list(content_text) + if task_issues: + self.issues.extend(task_issues) + else: + issue = self.extract_issue_from_heading( + current_heading, + current_level, + content_text, + metadata + ) + self.issues.append(issue) + + def to_jsonl(self) -> str: + """Convert issues to JSONL format.""" + lines = [] + for issue in self.issues: + lines.append(json.dumps(issue, ensure_ascii=False)) + return '\n'.join(lines) + + +def main(): + """Main entry point.""" + if len(sys.argv) < 2: + print("Usage: python md2jsonl.py ", file=sys.stderr) + print("", file=sys.stderr) + print("Examples:", file=sys.stderr) + print(" python md2jsonl.py feature.md | bd import", file=sys.stderr) + print(" python md2jsonl.py feature.md > issues.jsonl", file=sys.stderr) + sys.exit(1) + + markdown_file = Path(sys.argv[1]) + + if not markdown_file.exists(): + print(f"Error: File not found: {markdown_file}", file=sys.stderr) + sys.exit(1) + + # Read markdown + content = markdown_file.read_text() + + # Convert to issues + converter = MarkdownToIssues(prefix="bd") + converter.parse_markdown(content) + + # Output JSONL + print(converter.to_jsonl()) + + +if __name__ == "__main__": + main()