ragtime-cli 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ragtime-cli might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ragtime-cli
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Local-first memory and RAG system for Claude Code - semantic search over code, docs, and team knowledge
5
5
  Author-email: Bret Martineau <bretwardjames@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
- ragtime_cli-0.1.0.dist-info/licenses/LICENSE,sha256=9A0wJs2PRDciGRH4F8JUJ-aMKYQyq_gVu2ixrXs-l5A,1070
1
+ ragtime_cli-0.1.2.dist-info/licenses/LICENSE,sha256=9A0wJs2PRDciGRH4F8JUJ-aMKYQyq_gVu2ixrXs-l5A,1070
2
2
  src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- src/cli.py,sha256=7LOyDfHsC-3glutDrasEq8dsLdUPdfWAMauGYfp9tFI,26372
3
+ src/cli.py,sha256=UMY97OO6JbQ4EuzMxLnKddqAiRSwP73YJQgY380h1Wo,35481
4
4
  src/config.py,sha256=_5ev0OkhFKdVaU0T76x-lJUUccn1hB21-dqzV-UleUw,3155
5
5
  src/db.py,sha256=BKrlhilXYHNaj-ZcffinSXVhdUqowmwpFPBx7aLxamU,4642
6
6
  src/mcp_server.py,sha256=Tx0i73GXO0YmcVrdO7UjRMS0auN8fBG2LOpHuf_LUC0,20374
@@ -14,8 +14,8 @@ src/commands/save.md,sha256=7gTpW46AU9Y4l8XVZ8f4h1sEdBfVqIRA7hlidUxMAC4,251
14
14
  src/commands/start.md,sha256=qoqhkMgET74DBx8YPIT1-wqCiVBUDxlmevigsCinHSY,6506
15
15
  src/indexers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  src/indexers/docs.py,sha256=7FENHaKSvC1T557bRzvmrjyaG_vK94GuQG9XMZdr89w,3349
17
- ragtime_cli-0.1.0.dist-info/METADATA,sha256=IdKf5KyWm7JabJTCB4344AYbIYTLKKyQc3nYRDIiiM4,5311
18
- ragtime_cli-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
19
- ragtime_cli-0.1.0.dist-info/entry_points.txt,sha256=cWLbeyMxZNbew-THS3bHXTpCRXt1EaUy5QUOXGXLjl4,75
20
- ragtime_cli-0.1.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
21
- ragtime_cli-0.1.0.dist-info/RECORD,,
17
+ ragtime_cli-0.1.2.dist-info/METADATA,sha256=PsKWnO86RaGbeYZHWpFNpUotMwUkGFrn9K4OQzxVmUg,5311
18
+ ragtime_cli-0.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
19
+ ragtime_cli-0.1.2.dist-info/entry_points.txt,sha256=cWLbeyMxZNbew-THS3bHXTpCRXt1EaUy5QUOXGXLjl4,75
20
+ ragtime_cli-0.1.2.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
21
+ ragtime_cli-0.1.2.dist-info/RECORD,,
src/cli.py CHANGED
@@ -55,8 +55,75 @@ def get_author() -> str:
55
55
  return "unknown"
56
56
 
57
57
 
58
+ def check_ghp_installed() -> bool:
59
+ """Check if ghp-cli is installed."""
60
+ try:
61
+ result = subprocess.run(
62
+ ["ghp", "--version"],
63
+ capture_output=True,
64
+ text=True,
65
+ timeout=5,
66
+ )
67
+ return result.returncode == 0
68
+ except (subprocess.TimeoutExpired, FileNotFoundError):
69
+ return False
70
+
71
+
72
+ def get_issue_from_ghp(issue_num: int, path: Path) -> dict | None:
73
+ """Get issue details using ghp issue open."""
74
+ try:
75
+ result = subprocess.run(
76
+ ["ghp", "issue", "open", str(issue_num), "--json"],
77
+ cwd=path,
78
+ capture_output=True,
79
+ text=True,
80
+ timeout=30,
81
+ )
82
+ if result.returncode == 0:
83
+ import json
84
+ return json.loads(result.stdout)
85
+ except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
86
+ pass
87
+ return None
88
+
89
+
90
+ def get_issue_from_gh(issue_num: int, path: Path) -> dict | None:
91
+ """Get issue details using gh CLI."""
92
+ try:
93
+ result = subprocess.run(
94
+ ["gh", "issue", "view", str(issue_num), "--json", "title,body,labels,number"],
95
+ cwd=path,
96
+ capture_output=True,
97
+ text=True,
98
+ timeout=30,
99
+ )
100
+ if result.returncode == 0:
101
+ import json
102
+ return json.loads(result.stdout)
103
+ except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
104
+ pass
105
+ return None
106
+
107
+
108
+ def get_current_branch(path: Path) -> str | None:
109
+ """Get the current git branch name."""
110
+ try:
111
+ result = subprocess.run(
112
+ ["git", "rev-parse", "--abbrev-ref", "HEAD"],
113
+ cwd=path,
114
+ capture_output=True,
115
+ text=True,
116
+ timeout=5,
117
+ )
118
+ if result.returncode == 0:
119
+ return result.stdout.strip()
120
+ except (subprocess.TimeoutExpired, FileNotFoundError):
121
+ pass
122
+ return None
123
+
124
+
58
125
  @click.group()
59
- @click.version_option(version="0.1.0")
126
+ @click.version_option(version="0.1.2")
60
127
  def main():
61
128
  """Ragtime - semantic search over code and documentation."""
62
129
  pass
@@ -73,6 +140,20 @@ def init(path: Path):
73
140
  click.echo(f" Code paths: {config.code.paths}")
74
141
  click.echo(f" Languages: {config.code.languages}")
75
142
 
143
+ # Create memory directory structure
144
+ memory_dir = path / ".claude" / "memory"
145
+ (memory_dir / "app").mkdir(parents=True, exist_ok=True)
146
+ (memory_dir / "team").mkdir(parents=True, exist_ok=True)
147
+ (memory_dir / "branches").mkdir(parents=True, exist_ok=True)
148
+ click.echo(f"\nCreated .claude/memory/ structure")
149
+
150
+ # Check for ghp-cli
151
+ if check_ghp_installed():
152
+ click.echo(f"\n✓ ghp-cli detected - will use for issue lookups")
153
+ else:
154
+ click.echo(f"\n• ghp-cli not found - will use gh CLI for issue lookups")
155
+ click.echo(f" Install ghp for enhanced workflow: pip install ghp-cli")
156
+
76
157
 
77
158
  @main.command()
78
159
  @click.argument("path", type=click.Path(exists=True, path_type=Path), default=".")
@@ -415,6 +496,141 @@ def reindex(path: Path):
415
496
  click.echo(f"✓ Reindexed {count} memory files")
416
497
 
417
498
 
499
+ @main.command("new-branch")
500
+ @click.argument("issue", type=int)
501
+ @click.option("--path", type=click.Path(exists=True, path_type=Path), default=".")
502
+ @click.option("--content", help="Context document content (overrides auto-generated scaffold)")
503
+ @click.option("--issue-json", "issue_json", help="Issue data as JSON (from ghp hook, skips fetch)")
504
+ @click.option("--branch", "-b", help="Branch name (auto-detected from git if not provided)")
505
+ def new_branch(issue: int, path: Path, content: str, issue_json: str, branch: str):
506
+ """Initialize a branch context from a GitHub issue.
507
+
508
+ Creates .claude/memory/branches/{branch-slug}/context.md with either:
509
+ - Provided content (from --content flag, e.g., LLM-generated plan)
510
+ - Auto-generated scaffold from issue metadata (fallback)
511
+
512
+ Examples:
513
+ ragtime new-branch 42 # Scaffold from issue #42
514
+ ragtime new-branch 42 --content "..." # Use provided content
515
+ ragtime new-branch 42 --issue-json '{...}' # Use JSON from ghp hook
516
+ ragtime new-branch 42 --branch my-feat # Specify branch name
517
+ """
518
+ import json
519
+ from datetime import date
520
+
521
+ path = Path(path).resolve()
522
+
523
+ # Determine branch name
524
+ if not branch:
525
+ branch = get_current_branch(path)
526
+ if not branch or branch in ("main", "master"):
527
+ click.echo("✗ Not on a feature branch. Use --branch to specify.", err=True)
528
+ return
529
+
530
+ # Create branch slug for folder name
531
+ branch_slug = branch.replace("/", "-")
532
+ branch_dir = path / ".claude" / "memory" / "branches" / branch_slug
533
+ branch_dir.mkdir(parents=True, exist_ok=True)
534
+
535
+ context_file = branch_dir / "context.md"
536
+
537
+ if content:
538
+ # Use provided content directly
539
+ context_file.write_text(content)
540
+ click.echo(f"✓ Created context.md with provided content")
541
+ click.echo(f" Path: {context_file.relative_to(path)}")
542
+ return
543
+
544
+ # Get issue data from JSON, ghp, or gh
545
+ issue_data = None
546
+ source = None
547
+
548
+ # Use provided JSON if available (from ghp hook)
549
+ if issue_json:
550
+ try:
551
+ issue_data = json.loads(issue_json)
552
+ source = "ghp-hook"
553
+ except json.JSONDecodeError as e:
554
+ click.echo(f"✗ Invalid JSON: {e}", err=True)
555
+ return
556
+ else:
557
+ # Fall back to fetching from API
558
+ click.echo(f"Fetching issue #{issue}...")
559
+
560
+ # Try ghp first if available
561
+ if check_ghp_installed():
562
+ issue_data = get_issue_from_ghp(issue, path)
563
+ source = "ghp"
564
+
565
+ # Fall back to gh
566
+ if not issue_data:
567
+ issue_data = get_issue_from_gh(issue, path)
568
+ source = "gh"
569
+
570
+ if not issue_data:
571
+ click.echo(f"✗ Could not fetch issue #{issue}", err=True)
572
+ click.echo(" Make sure you're in a git repo with GitHub remote")
573
+ return
574
+
575
+ # Extract issue fields
576
+ title = issue_data.get("title", f"Issue #{issue}")
577
+ body = issue_data.get("body", "")
578
+ labels = issue_data.get("labels", [])
579
+
580
+ # Format labels
581
+ if labels:
582
+ if isinstance(labels[0], dict):
583
+ label_names = [l.get("name", "") for l in labels]
584
+ else:
585
+ label_names = labels
586
+ labels_str = ", ".join(label_names)
587
+ else:
588
+ labels_str = ""
589
+
590
+ # Generate scaffold context.md
591
+ scaffold = f"""---
592
+ type: context
593
+ branch: {branch}
594
+ issue: {issue}
595
+ status: active
596
+ created: '{date.today().isoformat()}'
597
+ author: {get_author()}
598
+ ---
599
+
600
+ ## Issue
601
+
602
+ **#{issue}**: {title}
603
+
604
+ {f"**Labels**: {labels_str}" if labels_str else ""}
605
+
606
+ ## Description
607
+
608
+ {body if body else "_No description provided_"}
609
+
610
+ ## Plan
611
+
612
+ <!-- Implementation steps - fill in or let Claude generate -->
613
+
614
+ - [ ] TODO: Define implementation steps
615
+
616
+ ## Acceptance Criteria
617
+
618
+ <!-- What needs to be true for this to be complete? -->
619
+
620
+ ## Notes
621
+
622
+ <!-- Additional context, decisions, blockers -->
623
+
624
+ """
625
+
626
+ context_file.write_text(scaffold)
627
+
628
+ click.echo(f"✓ Created context.md from issue #{issue}")
629
+ click.echo(f" Path: {context_file.relative_to(path)}")
630
+ click.echo(f" Source: {source}")
631
+ click.echo(f"\nNext: Fill in the Plan section or use /start to generate it")
632
+
633
+
418
634
  # ============================================================================
419
635
  # Command Installation
420
636
  # ============================================================================
@@ -518,6 +734,75 @@ def install_commands(global_install: bool, workspace_install: bool, list_command
518
734
  click.echo(' "ragtime": {"command": "ragtime-mcp", "args": ["--path", "."]}')
519
735
 
520
736
 
737
+ @main.command("setup-ghp")
738
+ @click.option("--remove", is_flag=True, help="Remove ragtime hooks from ghp")
739
+ def setup_ghp(remove: bool):
740
+ """Register ragtime hooks with ghp-cli.
741
+
742
+ Adds event hooks so ghp automatically creates context.md when starting issues.
743
+
744
+ Examples:
745
+ ragtime setup-ghp # Register hooks
746
+ ragtime setup-ghp --remove # Remove hooks
747
+ """
748
+ if not check_ghp_installed():
749
+ click.echo("✗ ghp-cli not installed", err=True)
750
+ click.echo(" Install with: npm install -g @bretwardjames/ghp-cli")
751
+ return
752
+
753
+ hook_name = "ragtime-context"
754
+
755
+ if remove:
756
+ # Remove the hook
757
+ result = subprocess.run(
758
+ ["ghp", "hooks", "remove", hook_name],
759
+ capture_output=True,
760
+ text=True,
761
+ )
762
+ if result.returncode == 0:
763
+ click.echo(f"✓ Removed hook: {hook_name}")
764
+ else:
765
+ if "not found" in result.stderr.lower():
766
+ click.echo(f"• Hook {hook_name} not registered")
767
+ else:
768
+ click.echo(f"✗ Failed to remove hook: {result.stderr}", err=True)
769
+ return
770
+
771
+ # Check if hook already exists
772
+ result = subprocess.run(
773
+ ["ghp", "hooks", "show", hook_name],
774
+ capture_output=True,
775
+ text=True,
776
+ )
777
+ if result.returncode == 0:
778
+ click.echo(f"• Hook {hook_name} already registered")
779
+ click.echo(" Use --remove to unregister first")
780
+ return
781
+
782
+ # Register the hook
783
+ # The command uses ${issue.number}, ${issue.json}, and ${branch} from ghp
784
+ hook_command = "ragtime new-branch ${issue.number} --issue-json '${issue.json}' --branch '${branch}'"
785
+
786
+ result = subprocess.run(
787
+ [
788
+ "ghp", "hooks", "add", hook_name,
789
+ "--event", "issue-started",
790
+ "--command", hook_command,
791
+ "--display-name", "Ragtime Context",
792
+ ],
793
+ capture_output=True,
794
+ text=True,
795
+ )
796
+
797
+ if result.returncode == 0:
798
+ click.echo(f"✓ Registered hook: {hook_name}")
799
+ click.echo(f" Event: issue-started")
800
+ click.echo(f" Action: Creates context.md from issue metadata")
801
+ click.echo(f"\nNow when you run 'ghp start <issue>', ragtime will auto-create context.md")
802
+ else:
803
+ click.echo(f"✗ Failed to register hook: {result.stderr}", err=True)
804
+
805
+
521
806
  # ============================================================================
522
807
  # Cross-Branch Sync Commands
523
808
  # ============================================================================