From bc8d05d0f7cf91cac0e05f903268488f39d857ee Mon Sep 17 00:00:00 2001 From: sato820 Date: Thu, 25 Jun 2026 14:41:21 -0400 Subject: [PATCH] Add JSON output mode --- README.md | 5 +++++ git-context | 44 +++++++++++++++++++++++++++++++++++++---- git_context/__init__.py | 44 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bfa51e7..baa19ce 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ One command → complete project context ready for any LLM: - 📜 Recent commit history - 📄 Source file contents (intelligent truncation) +- JSON output for scripts and integrations + ## Usage ```bash @@ -20,6 +22,9 @@ python3 git-context --files # Custom depth (default: 4) python3 git-context --depth 2 +# Machine-readable JSON output +python3 git-context --json + # Write to file instead of stdout python3 git-context --files -o context.txt diff --git a/git-context b/git-context index 92e2b81..e72a676 100755 --- a/git-context +++ b/git-context @@ -4,10 +4,11 @@ git-context — Generate AI-friendly context for any git repo. Dump project structure, git log, file contents, and branch topology in one optimized prompt-ready block. -Usage: git context [--depth N] [--files] [--log N] [--output file] [--dir ] +Usage: git context [--depth N] [--files] [--json] [--log N] [--output file] [--dir ] """ import argparse +import json import os import subprocess import sys @@ -31,6 +32,12 @@ def run(cmd, cwd=None): except Exception: return "" +def configure_stdout(): + try: + sys.stdout.reconfigure(encoding='utf-8') + except (AttributeError, ValueError): + pass + def size_fmt(n): if n > 1_000_000: return f"{n/1_000_000:.1f}MB" if n > 1_000: return f"{n/1_000:.0f}KB" @@ -114,6 +121,9 @@ def file_contents(path, ignored=DEFAULT_IGNORE, max_total=15000): break return result +def split_lines(text): + return text.splitlines() if text else [] + def fmt_timestamp(ts): try: dt = datetime.fromisoformat(ts) @@ -122,9 +132,11 @@ def fmt_timestamp(ts): return ts[:19] def main(): + configure_stdout() p = argparse.ArgumentParser(description='Generate AI-friendly context for a git repo') p.add_argument('--depth', type=int, default=4, help='Directory tree depth (default: 4)') p.add_argument('--files', action='store_true', help='Include source file contents') + p.add_argument('--json', dest='json_output', action='store_true', help='Output repo context as JSON') p.add_argument('--log', type=int, default=20, help='Number of recent commits (default: 20, 0=skip)') p.add_argument('--output', '-o', help='Write to file instead of stdout') p.add_argument('--dir', default=os.getcwd(), help='Target directory (default: cwd)') @@ -136,9 +148,10 @@ def main(): sys.exit(1) repo_name = os.path.basename(target) + generated_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S') sections = [] sections.append(f"# git-context: {repo_name}") - sections.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + sections.append(f"Generated: {generated_at}") sections.append(f"Path: {target}") sections.append("") @@ -158,6 +171,7 @@ def main(): sections.append(status) # Recent commits + log = "" if args.log > 0: log = run(["git", "log", f"--max-count={args.log}", "--oneline", "--graph", "--pretty=format:%h %d %s (%an, %ar)"], target) @@ -177,16 +191,38 @@ def main(): sections.append(f"```\n{tree_out}\n```") # File contents + contents = "" if args.files: contents = file_contents(target) if contents: sections.append("\n## File Contents") sections.append(contents) - output = "\n".join(sections) + if args.json_output: + context = { + "repo": repo_name, + "generated": generated_at, + "path": target, + "git": { + "branch": branch, + "remote": remote, + "working_tree": { + "clean": not has_unstaged and not has_staged, + "unstaged": has_unstaged, + "staged": has_staged, + }, + }, + "recent_commits": split_lines(log), + "branches": split_lines(branches), + "project_structure": tree_out, + "file_contents": contents, + } + output = json.dumps(context, indent=2, ensure_ascii=False) + else: + output = "\n".join(sections) if args.output: - Path(args.output).write_text(output) + Path(args.output).write_text(output, encoding='utf-8') print(f"✅ Written to {args.output}") else: print(output) diff --git a/git_context/__init__.py b/git_context/__init__.py index 92e2b81..e72a676 100755 --- a/git_context/__init__.py +++ b/git_context/__init__.py @@ -4,10 +4,11 @@ Dump project structure, git log, file contents, and branch topology in one optimized prompt-ready block. -Usage: git context [--depth N] [--files] [--log N] [--output file] [--dir ] +Usage: git context [--depth N] [--files] [--json] [--log N] [--output file] [--dir ] """ import argparse +import json import os import subprocess import sys @@ -31,6 +32,12 @@ def run(cmd, cwd=None): except Exception: return "" +def configure_stdout(): + try: + sys.stdout.reconfigure(encoding='utf-8') + except (AttributeError, ValueError): + pass + def size_fmt(n): if n > 1_000_000: return f"{n/1_000_000:.1f}MB" if n > 1_000: return f"{n/1_000:.0f}KB" @@ -114,6 +121,9 @@ def file_contents(path, ignored=DEFAULT_IGNORE, max_total=15000): break return result +def split_lines(text): + return text.splitlines() if text else [] + def fmt_timestamp(ts): try: dt = datetime.fromisoformat(ts) @@ -122,9 +132,11 @@ def fmt_timestamp(ts): return ts[:19] def main(): + configure_stdout() p = argparse.ArgumentParser(description='Generate AI-friendly context for a git repo') p.add_argument('--depth', type=int, default=4, help='Directory tree depth (default: 4)') p.add_argument('--files', action='store_true', help='Include source file contents') + p.add_argument('--json', dest='json_output', action='store_true', help='Output repo context as JSON') p.add_argument('--log', type=int, default=20, help='Number of recent commits (default: 20, 0=skip)') p.add_argument('--output', '-o', help='Write to file instead of stdout') p.add_argument('--dir', default=os.getcwd(), help='Target directory (default: cwd)') @@ -136,9 +148,10 @@ def main(): sys.exit(1) repo_name = os.path.basename(target) + generated_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S') sections = [] sections.append(f"# git-context: {repo_name}") - sections.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + sections.append(f"Generated: {generated_at}") sections.append(f"Path: {target}") sections.append("") @@ -158,6 +171,7 @@ def main(): sections.append(status) # Recent commits + log = "" if args.log > 0: log = run(["git", "log", f"--max-count={args.log}", "--oneline", "--graph", "--pretty=format:%h %d %s (%an, %ar)"], target) @@ -177,16 +191,38 @@ def main(): sections.append(f"```\n{tree_out}\n```") # File contents + contents = "" if args.files: contents = file_contents(target) if contents: sections.append("\n## File Contents") sections.append(contents) - output = "\n".join(sections) + if args.json_output: + context = { + "repo": repo_name, + "generated": generated_at, + "path": target, + "git": { + "branch": branch, + "remote": remote, + "working_tree": { + "clean": not has_unstaged and not has_staged, + "unstaged": has_unstaged, + "staged": has_staged, + }, + }, + "recent_commits": split_lines(log), + "branches": split_lines(branches), + "project_structure": tree_out, + "file_contents": contents, + } + output = json.dumps(context, indent=2, ensure_ascii=False) + else: + output = "\n".join(sections) if args.output: - Path(args.output).write_text(output) + Path(args.output).write_text(output, encoding='utf-8') print(f"✅ Written to {args.output}") else: print(output)