repokg 0.1.0__tar.gz

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.
repokg-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nihar Shah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
repokg-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,160 @@
1
+ Metadata-Version: 2.4
2
+ Name: repokg
3
+ Version: 0.1.0
4
+ Summary: Generate an AI-ready knowledge graph (KNOWLEDGE_GRAPH.md + kg.json) of any codebase: modules, dependency edges, branches, PRs, ops surface.
5
+ Author: Nihar Shah
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/NehharShah/repokg
8
+ Keywords: knowledge-graph,codebase,ai,agents,documentation,repo-map
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Documentation
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Dynamic: license-file
19
+
20
+ # repokg
21
+
22
+ Generate an **AI-ready knowledge graph** of any codebase — so an AI agent (or a new
23
+ developer) can read one file and start building immediately.
24
+
25
+ `repokg` extracts everything that can be known *deterministically* about a repo —
26
+ module inventory, internal import graph, every branch classified against every PR
27
+ (merged / squash-merged / abandoned / stale), contributor stats, CI/Docker/Helm/Make
28
+ surface — and renders it as:
29
+
30
+ - **`KNOWLEDGE_GRAPH.md`** — a single human/AI-readable document with a mermaid architecture
31
+ graph, module tables, branch & PR catalog, timeline, and ops inventory.
32
+ - **`.repokg/kg.json`** — the same graph, machine-readable.
33
+
34
+ The semantic layer (module purposes, data-flow narratives, project eras, gotchas)
35
+ can't be produced by static analysis without guessing — so repokg is
36
+ **agent-first**: it emits `.repokg/prompts/enrich.md`, a rigorous prompt any AI coding
37
+ agent (Claude Code, Cursor, Copilot Workspace…) executes to verify-and-fill the
38
+ narrative sections, writing `.repokg/narratives.json`. Re-render and the knowledge graph is
39
+ complete. No API keys, no LLM dependency in the tool itself.
40
+
41
+ ## Install
42
+
43
+ ```sh
44
+ pipx install repokg # or: pip install repokg
45
+ # from source:
46
+ pipx install git+https://github.com/NehharShah/repokg
47
+ ```
48
+
49
+ Requirements: Python ≥ 3.9, `git`. Optional: [`gh`](https://cli.github.com) (logged
50
+ in) for the PR/branch cross-reference — without it the knowledge graph still builds, minus PR data.
51
+
52
+ ## Usage
53
+
54
+ ```sh
55
+ cd your-repo
56
+ repokg # = generate: scan + prompts + render
57
+ ```
58
+
59
+ Output:
60
+
61
+ ```
62
+ .repokg/kg.json # machine-readable knowledge graph
63
+ .repokg/prompts/enrich.md # hand this to your AI agent
64
+ KNOWLEDGE_GRAPH.md # the knowledge graph document
65
+ ```
66
+
67
+ Then, in your AI agent of choice:
68
+
69
+ > Follow the instructions in .repokg/prompts/enrich.md
70
+
71
+ The agent explores the code, writes `.repokg/narratives.json`, and runs
72
+ `repokg render` — KNOWLEDGE_GRAPH.md now carries verified purposes, data flows,
73
+ timeline eras, and gotchas alongside the deterministic structure.
74
+
75
+ ### Commands
76
+
77
+ | Command | Effect |
78
+ |---|---|
79
+ | `repokg scan [path]` | Extract structure → `.repokg/kg.json` |
80
+ | `repokg prompts [path]` | Write the enrichment prompt |
81
+ | `repokg render [path]` | `kg.json` (+ `narratives.json`) → `KNOWLEDGE_GRAPH.md` |
82
+ | `repokg generate [path]` | All three (default) |
83
+ | `repokg inject [path]` | Wire the knowledge graph into `CLAUDE.md` / `AGENTS.md` / Cursor rules |
84
+ | `repokg check [path]` | Exit 1 if the knowledge graph is stale vs `HEAD` (CI-friendly) |
85
+
86
+ Flags: `--out DIR` (default `<repo>/.repokg`), `--md FILE` (default `<repo>/KNOWLEDGE_GRAPH.md`),
87
+ `--no-github`, `--pr-limit N`.
88
+
89
+ ## Agent integration
90
+
91
+ `repokg inject` adds a **managed block** (delimited by
92
+ `<!-- repokg:begin/end -->`, idempotent, never touches your hand-written
93
+ content) pointing agents at KNOWLEDGE_GRAPH.md:
94
+
95
+ - **`CLAUDE.md`** (Claude Code) — updated if present
96
+ - **`AGENTS.md`** (the cross-tool agent standard) — updated if present, created if
97
+ no agent file exists at all
98
+ - **`.github/copilot-instructions.md`** (Copilot) — updated if present
99
+ - **`.cursor/rules/repokg.mdc`** (Cursor, with `alwaysApply: true`) — created
100
+ if `.cursor/rules/` exists; falls back to legacy `.cursorrules`
101
+
102
+ Keep it fresh in CI:
103
+
104
+ ```yaml
105
+ - run: pipx run repokg check . || echo "::warning::KNOWLEDGE_GRAPH.md is stale"
106
+ ```
107
+
108
+ KNOWLEDGE_GRAPH.md itself also lists any agent-context files it found, so an agent landing
109
+ on the knowledge graph discovers your rules — and vice versa.
110
+
111
+ ## What gets extracted (all verified, never guessed)
112
+
113
+ | Area | How |
114
+ |---|---|
115
+ | Branch classification | `git for-each-ref` + `--merged` ancestry vs the integration branch (auto-detects `staging`/`develop`), cross-referenced with every PR's head ref via `gh` — distinguishes true merges from squash-merges from abandoned work |
116
+ | PR catalog | `gh pr list --state all` — open / merged / closed-unmerged, full appendix table |
117
+ | Module inventory | Filesystem walk with LOC per directory, language detection, generated-code flagging |
118
+ | Import graph | Go: `import` blocks resolved against `go.mod` module paths · Python: stdlib `ast` incl. relative imports · JS/TS: relative `import`/`require` resolution. Directory→directory edges with counts |
119
+ | Ops surface | CI workflow names, Dockerfiles, compose files, Helm charts, Makefile targets, config/docs/test/migration dirs |
120
+ | Timeline | Merged PRs grouped by month with conventional-commit scope frequencies (replaced by agent-written eras after enrichment) |
121
+
122
+ ## Why agent-first instead of calling an LLM API?
123
+
124
+ Because the enrichment quality depends on *reading the code*, and your coding agent
125
+ already has the repo open, tools to search it, and your permission model. A prompt it
126
+ can execute beats a second LLM integration with its own keys, costs, and context limits.
127
+ The contract between tool and agent is one JSON file (`narratives.json`) with a fixed
128
+ schema — everything else stays deterministic and reproducible.
129
+
130
+ ## Known limitations
131
+
132
+ - **JS/TS**: only relative imports are resolved; alias imports (`@/…`,
133
+ tsconfig `paths`) are ignored.
134
+ - **Fork PRs**: a fork PR whose head branch name matches a local branch will be
135
+ linked to it (GitHub's API reports bare head refs).
136
+ - **Python**: packages are discovered at the repo root and under `src/`;
137
+ deeper monorepo layouts (`packages/*/src/…`) get file-level edges only.
138
+ - Branch `ahead` counts use one batched git call on git ≥ 2.41, with a
139
+ per-branch fallback on older git.
140
+
141
+ ## Roadmap
142
+
143
+ - [ ] Rust / Java / Kotlin import graphs
144
+ - [ ] `--exclude` glob patterns
145
+ - [ ] `llms.txt` emission alongside KNOWLEDGE_GRAPH.md
146
+ - [ ] tsconfig `paths` alias resolution
147
+ - [ ] PyPI release + prebuilt GitHub Action
148
+
149
+ ## Development
150
+
151
+ ```sh
152
+ pip install -e .
153
+ python -m unittest discover -s tests -v
154
+ ```
155
+
156
+ No runtime dependencies — stdlib only.
157
+
158
+ ## License
159
+
160
+ MIT
repokg-0.1.0/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # repokg
2
+
3
+ Generate an **AI-ready knowledge graph** of any codebase — so an AI agent (or a new
4
+ developer) can read one file and start building immediately.
5
+
6
+ `repokg` extracts everything that can be known *deterministically* about a repo —
7
+ module inventory, internal import graph, every branch classified against every PR
8
+ (merged / squash-merged / abandoned / stale), contributor stats, CI/Docker/Helm/Make
9
+ surface — and renders it as:
10
+
11
+ - **`KNOWLEDGE_GRAPH.md`** — a single human/AI-readable document with a mermaid architecture
12
+ graph, module tables, branch & PR catalog, timeline, and ops inventory.
13
+ - **`.repokg/kg.json`** — the same graph, machine-readable.
14
+
15
+ The semantic layer (module purposes, data-flow narratives, project eras, gotchas)
16
+ can't be produced by static analysis without guessing — so repokg is
17
+ **agent-first**: it emits `.repokg/prompts/enrich.md`, a rigorous prompt any AI coding
18
+ agent (Claude Code, Cursor, Copilot Workspace…) executes to verify-and-fill the
19
+ narrative sections, writing `.repokg/narratives.json`. Re-render and the knowledge graph is
20
+ complete. No API keys, no LLM dependency in the tool itself.
21
+
22
+ ## Install
23
+
24
+ ```sh
25
+ pipx install repokg # or: pip install repokg
26
+ # from source:
27
+ pipx install git+https://github.com/NehharShah/repokg
28
+ ```
29
+
30
+ Requirements: Python ≥ 3.9, `git`. Optional: [`gh`](https://cli.github.com) (logged
31
+ in) for the PR/branch cross-reference — without it the knowledge graph still builds, minus PR data.
32
+
33
+ ## Usage
34
+
35
+ ```sh
36
+ cd your-repo
37
+ repokg # = generate: scan + prompts + render
38
+ ```
39
+
40
+ Output:
41
+
42
+ ```
43
+ .repokg/kg.json # machine-readable knowledge graph
44
+ .repokg/prompts/enrich.md # hand this to your AI agent
45
+ KNOWLEDGE_GRAPH.md # the knowledge graph document
46
+ ```
47
+
48
+ Then, in your AI agent of choice:
49
+
50
+ > Follow the instructions in .repokg/prompts/enrich.md
51
+
52
+ The agent explores the code, writes `.repokg/narratives.json`, and runs
53
+ `repokg render` — KNOWLEDGE_GRAPH.md now carries verified purposes, data flows,
54
+ timeline eras, and gotchas alongside the deterministic structure.
55
+
56
+ ### Commands
57
+
58
+ | Command | Effect |
59
+ |---|---|
60
+ | `repokg scan [path]` | Extract structure → `.repokg/kg.json` |
61
+ | `repokg prompts [path]` | Write the enrichment prompt |
62
+ | `repokg render [path]` | `kg.json` (+ `narratives.json`) → `KNOWLEDGE_GRAPH.md` |
63
+ | `repokg generate [path]` | All three (default) |
64
+ | `repokg inject [path]` | Wire the knowledge graph into `CLAUDE.md` / `AGENTS.md` / Cursor rules |
65
+ | `repokg check [path]` | Exit 1 if the knowledge graph is stale vs `HEAD` (CI-friendly) |
66
+
67
+ Flags: `--out DIR` (default `<repo>/.repokg`), `--md FILE` (default `<repo>/KNOWLEDGE_GRAPH.md`),
68
+ `--no-github`, `--pr-limit N`.
69
+
70
+ ## Agent integration
71
+
72
+ `repokg inject` adds a **managed block** (delimited by
73
+ `<!-- repokg:begin/end -->`, idempotent, never touches your hand-written
74
+ content) pointing agents at KNOWLEDGE_GRAPH.md:
75
+
76
+ - **`CLAUDE.md`** (Claude Code) — updated if present
77
+ - **`AGENTS.md`** (the cross-tool agent standard) — updated if present, created if
78
+ no agent file exists at all
79
+ - **`.github/copilot-instructions.md`** (Copilot) — updated if present
80
+ - **`.cursor/rules/repokg.mdc`** (Cursor, with `alwaysApply: true`) — created
81
+ if `.cursor/rules/` exists; falls back to legacy `.cursorrules`
82
+
83
+ Keep it fresh in CI:
84
+
85
+ ```yaml
86
+ - run: pipx run repokg check . || echo "::warning::KNOWLEDGE_GRAPH.md is stale"
87
+ ```
88
+
89
+ KNOWLEDGE_GRAPH.md itself also lists any agent-context files it found, so an agent landing
90
+ on the knowledge graph discovers your rules — and vice versa.
91
+
92
+ ## What gets extracted (all verified, never guessed)
93
+
94
+ | Area | How |
95
+ |---|---|
96
+ | Branch classification | `git for-each-ref` + `--merged` ancestry vs the integration branch (auto-detects `staging`/`develop`), cross-referenced with every PR's head ref via `gh` — distinguishes true merges from squash-merges from abandoned work |
97
+ | PR catalog | `gh pr list --state all` — open / merged / closed-unmerged, full appendix table |
98
+ | Module inventory | Filesystem walk with LOC per directory, language detection, generated-code flagging |
99
+ | Import graph | Go: `import` blocks resolved against `go.mod` module paths · Python: stdlib `ast` incl. relative imports · JS/TS: relative `import`/`require` resolution. Directory→directory edges with counts |
100
+ | Ops surface | CI workflow names, Dockerfiles, compose files, Helm charts, Makefile targets, config/docs/test/migration dirs |
101
+ | Timeline | Merged PRs grouped by month with conventional-commit scope frequencies (replaced by agent-written eras after enrichment) |
102
+
103
+ ## Why agent-first instead of calling an LLM API?
104
+
105
+ Because the enrichment quality depends on *reading the code*, and your coding agent
106
+ already has the repo open, tools to search it, and your permission model. A prompt it
107
+ can execute beats a second LLM integration with its own keys, costs, and context limits.
108
+ The contract between tool and agent is one JSON file (`narratives.json`) with a fixed
109
+ schema — everything else stays deterministic and reproducible.
110
+
111
+ ## Known limitations
112
+
113
+ - **JS/TS**: only relative imports are resolved; alias imports (`@/…`,
114
+ tsconfig `paths`) are ignored.
115
+ - **Fork PRs**: a fork PR whose head branch name matches a local branch will be
116
+ linked to it (GitHub's API reports bare head refs).
117
+ - **Python**: packages are discovered at the repo root and under `src/`;
118
+ deeper monorepo layouts (`packages/*/src/…`) get file-level edges only.
119
+ - Branch `ahead` counts use one batched git call on git ≥ 2.41, with a
120
+ per-branch fallback on older git.
121
+
122
+ ## Roadmap
123
+
124
+ - [ ] Rust / Java / Kotlin import graphs
125
+ - [ ] `--exclude` glob patterns
126
+ - [ ] `llms.txt` emission alongside KNOWLEDGE_GRAPH.md
127
+ - [ ] tsconfig `paths` alias resolution
128
+ - [ ] PyPI release + prebuilt GitHub Action
129
+
130
+ ## Development
131
+
132
+ ```sh
133
+ pip install -e .
134
+ python -m unittest discover -s tests -v
135
+ ```
136
+
137
+ No runtime dependencies — stdlib only.
138
+
139
+ ## License
140
+
141
+ MIT
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "repokg"
7
+ version = "0.1.0"
8
+ description = "Generate an AI-ready knowledge graph (KNOWLEDGE_GRAPH.md + kg.json) of any codebase: modules, dependency edges, branches, PRs, ops surface."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "Nihar Shah" }]
12
+ requires-python = ">=3.9"
13
+ keywords = ["knowledge-graph", "codebase", "ai", "agents", "documentation", "repo-map"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Software Development :: Documentation",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/NehharShah/repokg"
25
+
26
+ [project.scripts]
27
+ repokg = "repokg.cli:main"
28
+
29
+ [tool.setuptools.packages.find]
30
+ where = ["src"]
repokg-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """repokg: generate an AI-ready knowledge graph of any codebase."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())
@@ -0,0 +1,144 @@
1
+ """repokg CLI: scan | prompts | render | generate."""
2
+
3
+ import argparse
4
+ import datetime
5
+ import json
6
+ import os
7
+ import sys
8
+
9
+ from . import __version__, code, deps, github, gitinfo, inject, markdown, ops, prompts
10
+
11
+
12
+ def scan(repo, out, no_github, pr_limit):
13
+ info, branches = gitinfo.collect(repo)
14
+ if no_github:
15
+ prs, note = [], "GitHub lookup disabled (--no-github)"
16
+ else:
17
+ prs, note = github.collect(repo, pr_limit)
18
+ github.classify(branches, prs, info["trunk"], info["integration"])
19
+ tree = dict(code.walk(repo)) # single filesystem walk, shared by all collectors
20
+ languages, modules = code.collect(repo, tree)
21
+ kg = {
22
+ "repokg_version": 1,
23
+ "generated_at": datetime.date.today().isoformat(),
24
+ "repo": info,
25
+ "languages": languages,
26
+ "modules": modules,
27
+ "edges": deps.collect(repo, tree),
28
+ "branches": branches,
29
+ "prs": prs,
30
+ "github_note": note,
31
+ "ops": ops.collect(repo, tree),
32
+ }
33
+ os.makedirs(out, exist_ok=True)
34
+ path = os.path.join(out, "kg.json")
35
+ with open(path, "w", encoding="utf-8") as f:
36
+ json.dump(kg, f, indent=1)
37
+ print("wrote %s (%d modules, %d edges, %d branches, %d PRs)" %
38
+ (path, len(modules), len(kg["edges"]), len(branches), len(prs)))
39
+ return kg
40
+
41
+
42
+ def write_prompts(repo, out, md):
43
+ pdir = os.path.join(out, "prompts")
44
+ os.makedirs(pdir, exist_ok=True)
45
+ path = os.path.join(pdir, "enrich.md")
46
+ with open(path, "w", encoding="utf-8") as f:
47
+ f.write(prompts.render(repo, out, md))
48
+ print("wrote %s (hand this to your AI agent)" % path)
49
+
50
+
51
+ def render(out, md):
52
+ with open(os.path.join(out, "kg.json"), encoding="utf-8") as f:
53
+ kg = json.load(f)
54
+ narratives = {}
55
+ npath = os.path.join(out, "narratives.json")
56
+ if os.path.isfile(npath):
57
+ with open(npath, encoding="utf-8") as f:
58
+ narratives = json.load(f)
59
+ doc = markdown.render(kg, narratives)
60
+ with open(md, "w", encoding="utf-8") as f:
61
+ f.write(doc)
62
+ state = "enriched" if narratives else "structure-only; run .repokg/prompts/enrich.md to enrich"
63
+ print("wrote %s (%s)" % (md, state))
64
+
65
+
66
+ def do_inject(repo, md):
67
+ for path, status in inject.run(repo, md).items():
68
+ print("%s: %s" % (path, status))
69
+
70
+
71
+ def check(repo, out, md):
72
+ """Exit 0 if the knowledge graph matches HEAD, 1 if stale/missing. CI-friendly."""
73
+ apath = os.path.join(out, "kg.json")
74
+ if not os.path.isfile(apath) or not os.path.isfile(md):
75
+ print("stale: knowledge graph not generated (run `repokg generate`)")
76
+ return 1
77
+ with open(apath, encoding="utf-8") as f:
78
+ stored = json.load(f).get("repo", {}).get("head", "")
79
+ head = gitinfo.try_run(repo, "rev-parse", "HEAD")
80
+ if stored and head and stored != head:
81
+ print("stale: knowledge graph at %s, HEAD is %s (run `repokg generate`)"
82
+ % (stored[:12], head[:12]))
83
+ return 1
84
+ print("fresh: knowledge graph matches HEAD %s" % (head[:12] or "(unknown)"))
85
+ return 0
86
+
87
+
88
+ def main(argv=None):
89
+ ap = argparse.ArgumentParser(
90
+ prog="repokg",
91
+ description="Generate an AI-ready knowledge graph of a codebase.")
92
+ ap.add_argument("command", nargs="?", default="generate",
93
+ choices=["scan", "prompts", "render", "generate", "inject",
94
+ "check", "version"],
95
+ help="scan: extract structure to .repokg/kg.json | "
96
+ "prompts: write the AI enrichment prompt | "
97
+ "render: kg.json (+narratives.json) -> KNOWLEDGE_GRAPH.md | "
98
+ "generate: scan + prompts + render (default) | "
99
+ "inject: add knowledge-graph pointer to CLAUDE.md/AGENTS.md/cursor rules | "
100
+ "check: exit 1 if knowledge graph is stale vs HEAD")
101
+ ap.add_argument("path", nargs="?", default=".", help="repository path (default: .)")
102
+ ap.add_argument("--out", default=None, help="output dir (default: <repo>/.repokg)")
103
+ ap.add_argument("--md", default=None, help="markdown output (default: <repo>/KNOWLEDGE_GRAPH.md)")
104
+ ap.add_argument("--no-github", action="store_true", help="skip gh PR lookup")
105
+ ap.add_argument("--pr-limit", type=int, default=1000, help="max PRs to fetch (default 1000)")
106
+ args = ap.parse_args(argv)
107
+
108
+ if args.command == "version":
109
+ print("repokg %s" % __version__)
110
+ return 0
111
+
112
+ repo = os.path.abspath(args.path)
113
+ if not os.path.isdir(repo):
114
+ print("error: %s is not a directory" % repo, file=sys.stderr)
115
+ return 2
116
+ out = args.out or os.path.join(repo, ".repokg")
117
+ md = args.md or os.path.join(repo, "KNOWLEDGE_GRAPH.md")
118
+
119
+ try:
120
+ if args.command == "scan":
121
+ scan(repo, out, args.no_github, args.pr_limit)
122
+ elif args.command == "prompts":
123
+ write_prompts(repo, out, md)
124
+ elif args.command == "render":
125
+ render(out, md)
126
+ elif args.command == "inject":
127
+ do_inject(repo, md)
128
+ elif args.command == "check":
129
+ return check(repo, out, md)
130
+ else: # generate
131
+ scan(repo, out, args.no_github, args.pr_limit)
132
+ write_prompts(repo, out, md)
133
+ render(out, md)
134
+ except RuntimeError as e:
135
+ print("error: %s" % e, file=sys.stderr)
136
+ return 1
137
+ except FileNotFoundError as e:
138
+ print("error: %s (run `repokg scan` first?)" % e, file=sys.stderr)
139
+ return 1
140
+ return 0
141
+
142
+
143
+ if __name__ == "__main__":
144
+ sys.exit(main())
@@ -0,0 +1,104 @@
1
+ """Code collectors: language breakdown, module discovery, LOC."""
2
+
3
+ import os
4
+ import re
5
+
6
+ SKIP_DIRS = {
7
+ ".git", "node_modules", "vendor", "dist", "build", "out", "target", ".next",
8
+ ".venv", "venv", "__pycache__", ".idea", ".vscode", ".repokg", "coverage",
9
+ ".terraform", "third_party", ".tox", ".mypy_cache", ".ruff_cache",
10
+ "site-packages", ".pytest_cache", ".cache",
11
+ }
12
+
13
+ LANG_BY_EXT = {
14
+ ".go": "Go", ".py": "Python", ".ts": "TypeScript", ".tsx": "TypeScript",
15
+ ".js": "JavaScript", ".jsx": "JavaScript", ".mjs": "JavaScript",
16
+ ".rs": "Rust", ".java": "Java", ".kt": "Kotlin", ".rb": "Ruby",
17
+ ".php": "PHP", ".cs": "C#", ".c": "C", ".cpp": "C++", ".cc": "C++",
18
+ ".h": "C/C++", ".hpp": "C++", ".sol": "Solidity", ".swift": "Swift",
19
+ ".scala": "Scala", ".ex": "Elixir", ".exs": "Elixir", ".zig": "Zig",
20
+ ".lua": "Lua", ".dart": "Dart", ".vue": "Vue", ".svelte": "Svelte",
21
+ ".sql": "SQL", ".sh": "Shell", ".proto": "Protobuf", ".tf": "Terraform",
22
+ ".yaml": "YAML", ".yml": "YAML", ".html": "HTML", ".css": "CSS",
23
+ }
24
+
25
+ # Languages whose presence makes a directory a "module" worth graphing.
26
+ MODULE_LANGS = {
27
+ "Go", "Python", "TypeScript", "JavaScript", "Rust", "Java", "Kotlin",
28
+ "Ruby", "PHP", "C#", "C", "C++", "Solidity", "Swift", "Scala", "Elixir",
29
+ "Zig", "Lua", "Dart", "Vue", "Svelte",
30
+ }
31
+
32
+ ROOT_MARKERS = {
33
+ "go.mod", "package.json", "pyproject.toml", "setup.py", "Cargo.toml",
34
+ "pom.xml", "build.gradle", "composer.json", "Gemfile",
35
+ }
36
+
37
+ GENERATED_RE = re.compile(r"(generated|sqlcgen|_pb2|\.pb\.|/pb$|/pb/|bindings)")
38
+ MAX_FILE_BYTES = 2_000_000
39
+
40
+
41
+ def walk(repo):
42
+ """os.walk with skip-dir pruning; yields (rel_dir, filenames)."""
43
+ for dirpath, dirnames, filenames in os.walk(repo):
44
+ dirnames[:] = sorted(
45
+ d for d in dirnames
46
+ if d not in SKIP_DIRS and not (d.startswith(".") and d != ".github")
47
+ )
48
+ rel = os.path.relpath(dirpath, repo)
49
+ yield ("" if rel == "." else rel.replace(os.sep, "/")), filenames
50
+
51
+
52
+ def count_loc(path):
53
+ try:
54
+ if os.path.getsize(path) > MAX_FILE_BYTES:
55
+ return 0
56
+ with open(path, "rb") as f:
57
+ data = f.read()
58
+ if b"\0" in data[:1024]: # binary
59
+ return 0
60
+ return data.count(b"\n") + (1 if data and not data.endswith(b"\n") else 0)
61
+ except OSError:
62
+ return 0
63
+
64
+
65
+ def collect(repo, tree=None):
66
+ """Return (languages, modules).
67
+
68
+ languages: [{lang, files, loc}] sorted by loc desc
69
+ modules: [{path, lang, files, loc, root, generated}] one per dir containing code
70
+ tree: optional pre-built {rel_dir: filenames} to avoid re-walking the repo.
71
+ """
72
+ lang_stats = {}
73
+ modules = []
74
+ for rel, files in (tree.items() if tree is not None else walk(repo)):
75
+ if rel.startswith(".github"):
76
+ continue
77
+ per_lang = {}
78
+ is_root = any(f in ROOT_MARKERS for f in files)
79
+ for f in files:
80
+ ext = os.path.splitext(f)[1].lower()
81
+ lang = LANG_BY_EXT.get(ext)
82
+ if not lang:
83
+ continue
84
+ loc = count_loc(os.path.join(repo, rel, f))
85
+ ls = lang_stats.setdefault(lang, {"lang": lang, "files": 0, "loc": 0})
86
+ ls["files"] += 1
87
+ ls["loc"] += loc
88
+ if lang in MODULE_LANGS:
89
+ pl = per_lang.setdefault(lang, [0, 0])
90
+ pl[0] += 1
91
+ pl[1] += loc
92
+ if per_lang:
93
+ dominant = max(per_lang.items(), key=lambda kv: kv[1][1])
94
+ modules.append({
95
+ "path": rel or "(root)",
96
+ "lang": dominant[0],
97
+ "files": sum(v[0] for v in per_lang.values()),
98
+ "loc": sum(v[1] for v in per_lang.values()),
99
+ "root": is_root,
100
+ "generated": bool(GENERATED_RE.search("/" + rel)),
101
+ })
102
+ languages = sorted(lang_stats.values(), key=lambda x: -x["loc"])
103
+ modules.sort(key=lambda m: -m["loc"])
104
+ return languages, modules