ws-scout 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.
ws_scout-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hokuto Shimura
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.
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: ws-scout
3
+ Version: 0.1.0
4
+ Summary: Scan a workspace of git repos and rank the ones that need attention — triage CLI with LLM-friendly output
5
+ Author: Hokuto Shimura
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Hokuto Shimura
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/hoqqun/ws-scout
29
+ Project-URL: Repository, https://github.com/hoqqun/ws-scout
30
+ Project-URL: Issues, https://github.com/hoqqun/ws-scout/issues
31
+ Keywords: git,cli,workspace,multi-repo,triage,developer-tools
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Environment :: Console
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: POSIX
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Topic :: Software Development :: Version Control :: Git
39
+ Requires-Python: >=3.10
40
+ Description-Content-Type: text/markdown
41
+ License-File: LICENSE
42
+ Dynamic: license-file
43
+
44
+ # ws-scout
45
+
46
+ `ws-scout` is a small WSL-friendly CLI that scans `~/workspace`, finds git repositories, and ranks the projects that probably deserve attention.
47
+
48
+ Unlike most multi-repo status tools, it does not just list repository states: it scores each repository (dirty tree, unpushed commits, TODO notes, recent activity) and sorts them into a triage list, with a `--brief` mode designed to be pasted into an LLM coding assistant as context.
49
+
50
+ It is useful when the workspace has many active projects and you want a quick morning triage before opening Claude Code.
51
+
52
+ ## What It Checks
53
+
54
+ - Dirty git working trees
55
+ - Local commits ahead of the remote
56
+ - Branches behind the remote
57
+ - Recent commits
58
+ - TODO/FIXME/HACK notes
59
+ - Common `package.json` scripts
60
+
61
+ ## Usage
62
+
63
+ ```bash
64
+ ./ws_scout.py
65
+ ./ws_scout.py --brief
66
+ ./ws_scout.py --json
67
+ ./ws_scout.py ~/workspace --top 20 --depth 2
68
+ ```
69
+
70
+ Install a `ws-scout` command from PyPI:
71
+
72
+ ```bash
73
+ pipx install ws-scout
74
+ # or
75
+ uv tool install ws-scout
76
+ ```
77
+
78
+ Or install from a local checkout:
79
+
80
+ ```bash
81
+ ./install.sh
82
+ ws-scout --brief
83
+ ```
84
+
85
+ The `--brief` output is intentionally compact so it can be pasted into Claude Code as context.
86
+
87
+ ## Requirements
88
+
89
+ - Python 3.10+
90
+ - `git`
91
+ - [ripgrep](https://github.com/BurntSushi/ripgrep) (optional — used for TODO/FIXME scanning; without it the TODO column is simply empty)
92
+
93
+ ## License
94
+
95
+ [MIT](LICENSE)
@@ -0,0 +1,52 @@
1
+ # ws-scout
2
+
3
+ `ws-scout` is a small WSL-friendly CLI that scans `~/workspace`, finds git repositories, and ranks the projects that probably deserve attention.
4
+
5
+ Unlike most multi-repo status tools, it does not just list repository states: it scores each repository (dirty tree, unpushed commits, TODO notes, recent activity) and sorts them into a triage list, with a `--brief` mode designed to be pasted into an LLM coding assistant as context.
6
+
7
+ It is useful when the workspace has many active projects and you want a quick morning triage before opening Claude Code.
8
+
9
+ ## What It Checks
10
+
11
+ - Dirty git working trees
12
+ - Local commits ahead of the remote
13
+ - Branches behind the remote
14
+ - Recent commits
15
+ - TODO/FIXME/HACK notes
16
+ - Common `package.json` scripts
17
+
18
+ ## Usage
19
+
20
+ ```bash
21
+ ./ws_scout.py
22
+ ./ws_scout.py --brief
23
+ ./ws_scout.py --json
24
+ ./ws_scout.py ~/workspace --top 20 --depth 2
25
+ ```
26
+
27
+ Install a `ws-scout` command from PyPI:
28
+
29
+ ```bash
30
+ pipx install ws-scout
31
+ # or
32
+ uv tool install ws-scout
33
+ ```
34
+
35
+ Or install from a local checkout:
36
+
37
+ ```bash
38
+ ./install.sh
39
+ ws-scout --brief
40
+ ```
41
+
42
+ The `--brief` output is intentionally compact so it can be pasted into Claude Code as context.
43
+
44
+ ## Requirements
45
+
46
+ - Python 3.10+
47
+ - `git`
48
+ - [ripgrep](https://github.com/BurntSushi/ripgrep) (optional — used for TODO/FIXME scanning; without it the TODO column is simply empty)
49
+
50
+ ## License
51
+
52
+ [MIT](LICENSE)
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ws-scout"
7
+ version = "0.1.0"
8
+ description = "Scan a workspace of git repos and rank the ones that need attention — triage CLI with LLM-friendly output"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ authors = [{ name = "Hokuto Shimura" }]
12
+ requires-python = ">=3.10"
13
+ keywords = ["git", "cli", "workspace", "multi-repo", "triage", "developer-tools"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: POSIX",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Software Development :: Version Control :: Git",
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/hoqqun/ws-scout"
26
+ Repository = "https://github.com/hoqqun/ws-scout"
27
+ Issues = "https://github.com/hoqqun/ws-scout/issues"
28
+
29
+ [project.scripts]
30
+ ws-scout = "ws_scout:main"
31
+
32
+ [tool.setuptools]
33
+ py-modules = ["ws_scout"]
34
+
35
+ [tool.ruff]
36
+ line-length = 100
37
+ target-version = "py310"
38
+
39
+ [tool.ruff.lint]
40
+ select = ["E", "F", "W", "I", "UP", "B", "SIM"]
41
+
42
+ [tool.mypy]
43
+ python_version = "3.10"
44
+ strict = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: ws-scout
3
+ Version: 0.1.0
4
+ Summary: Scan a workspace of git repos and rank the ones that need attention — triage CLI with LLM-friendly output
5
+ Author: Hokuto Shimura
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Hokuto Shimura
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/hoqqun/ws-scout
29
+ Project-URL: Repository, https://github.com/hoqqun/ws-scout
30
+ Project-URL: Issues, https://github.com/hoqqun/ws-scout/issues
31
+ Keywords: git,cli,workspace,multi-repo,triage,developer-tools
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Environment :: Console
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: POSIX
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Topic :: Software Development :: Version Control :: Git
39
+ Requires-Python: >=3.10
40
+ Description-Content-Type: text/markdown
41
+ License-File: LICENSE
42
+ Dynamic: license-file
43
+
44
+ # ws-scout
45
+
46
+ `ws-scout` is a small WSL-friendly CLI that scans `~/workspace`, finds git repositories, and ranks the projects that probably deserve attention.
47
+
48
+ Unlike most multi-repo status tools, it does not just list repository states: it scores each repository (dirty tree, unpushed commits, TODO notes, recent activity) and sorts them into a triage list, with a `--brief` mode designed to be pasted into an LLM coding assistant as context.
49
+
50
+ It is useful when the workspace has many active projects and you want a quick morning triage before opening Claude Code.
51
+
52
+ ## What It Checks
53
+
54
+ - Dirty git working trees
55
+ - Local commits ahead of the remote
56
+ - Branches behind the remote
57
+ - Recent commits
58
+ - TODO/FIXME/HACK notes
59
+ - Common `package.json` scripts
60
+
61
+ ## Usage
62
+
63
+ ```bash
64
+ ./ws_scout.py
65
+ ./ws_scout.py --brief
66
+ ./ws_scout.py --json
67
+ ./ws_scout.py ~/workspace --top 20 --depth 2
68
+ ```
69
+
70
+ Install a `ws-scout` command from PyPI:
71
+
72
+ ```bash
73
+ pipx install ws-scout
74
+ # or
75
+ uv tool install ws-scout
76
+ ```
77
+
78
+ Or install from a local checkout:
79
+
80
+ ```bash
81
+ ./install.sh
82
+ ws-scout --brief
83
+ ```
84
+
85
+ The `--brief` output is intentionally compact so it can be pasted into Claude Code as context.
86
+
87
+ ## Requirements
88
+
89
+ - Python 3.10+
90
+ - `git`
91
+ - [ripgrep](https://github.com/BurntSushi/ripgrep) (optional — used for TODO/FIXME scanning; without it the TODO column is simply empty)
92
+
93
+ ## License
94
+
95
+ [MIT](LICENSE)
@@ -0,0 +1,9 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ ws_scout.py
5
+ ws_scout.egg-info/PKG-INFO
6
+ ws_scout.egg-info/SOURCES.txt
7
+ ws_scout.egg-info/dependency_links.txt
8
+ ws_scout.egg-info/entry_points.txt
9
+ ws_scout.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ws-scout = ws_scout:main
@@ -0,0 +1 @@
1
+ ws_scout
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env python3
2
+ """Fast triage dashboard for a workspace full of git projects."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import concurrent.futures
8
+ import json
9
+ import os
10
+ import subprocess
11
+ import sys
12
+ from collections.abc import Iterable
13
+ from dataclasses import asdict, dataclass
14
+ from datetime import datetime, timezone
15
+ from pathlib import Path
16
+
17
+ __version__ = "0.1.0"
18
+
19
+ DEFAULT_IGNORE = {
20
+ ".cache",
21
+ ".git",
22
+ ".next",
23
+ ".venv",
24
+ "dist",
25
+ "node_modules",
26
+ "target",
27
+ "tmp",
28
+ "vendor",
29
+ }
30
+
31
+
32
+ @dataclass
33
+ class RepoReport:
34
+ name: str
35
+ path: str
36
+ branch: str | None
37
+ dirty: bool
38
+ ahead: int
39
+ behind: int
40
+ changed_files: int
41
+ recent_commit: str | None
42
+ recent_commit_time: str | None
43
+ todos: list[str]
44
+ scripts: list[str]
45
+ score: int
46
+ reasons: list[str]
47
+
48
+
49
+ def run(cmd: list[str], cwd: Path, timeout: int = 6) -> str:
50
+ try:
51
+ proc = subprocess.run(
52
+ cmd,
53
+ cwd=str(cwd),
54
+ text=True,
55
+ capture_output=True,
56
+ timeout=timeout,
57
+ check=False,
58
+ )
59
+ except (OSError, subprocess.TimeoutExpired):
60
+ return ""
61
+ return proc.stdout.strip()
62
+
63
+
64
+ def parse_porcelain_v2(output: str) -> tuple[int, int, int]:
65
+ ahead = behind = changed = 0
66
+ for line in output.splitlines():
67
+ if line.startswith("# branch.ab "):
68
+ parts = line.split()
69
+ for part in parts[2:]:
70
+ if part.startswith("+"):
71
+ ahead = int(part[1:] or "0")
72
+ elif part.startswith("-"):
73
+ behind = int(part[1:] or "0")
74
+ elif line and not line.startswith("#"):
75
+ changed += 1
76
+ return ahead, behind, changed
77
+
78
+
79
+ def find_repos(root: Path, max_depth: int, ignore: set[str]) -> list[Path]:
80
+ repos: list[Path] = []
81
+ root = root.resolve()
82
+
83
+ def walk(path: Path, depth: int) -> None:
84
+ if depth > max_depth:
85
+ return
86
+ if (path / ".git").exists():
87
+ repos.append(path)
88
+ return
89
+ try:
90
+ children = sorted(path.iterdir(), key=lambda p: p.name.lower())
91
+ except OSError:
92
+ return
93
+ for child in children:
94
+ if child.name in ignore or child.name.startswith("."):
95
+ continue
96
+ if child.is_dir():
97
+ walk(child, depth + 1)
98
+
99
+ walk(root, 0)
100
+ return repos
101
+
102
+
103
+ def recent_todos(path: Path, limit: int) -> list[str]:
104
+ if limit <= 0:
105
+ return []
106
+ rg = [
107
+ "rg",
108
+ "--no-heading",
109
+ "--line-number",
110
+ "--max-count",
111
+ str(limit),
112
+ "--glob",
113
+ "!**/.venv/**",
114
+ "--glob",
115
+ "!**/venv/**",
116
+ "--glob",
117
+ "!**/node_modules/**",
118
+ "--glob",
119
+ "!**/dist/**",
120
+ "--glob",
121
+ "!**/*.ipynb",
122
+ "-S",
123
+ r"TODO|FIXME|HACK|XXX",
124
+ ".",
125
+ ]
126
+ out = run(rg, path, timeout=5)
127
+ lines = []
128
+ for raw in out.splitlines():
129
+ if any(part in raw for part in ("/node_modules/", "/.git/", "/dist/", "/venv/", "/.venv/")):
130
+ continue
131
+ lines.append(raw[:180])
132
+ if len(lines) >= limit:
133
+ break
134
+ return lines
135
+
136
+
137
+ def package_scripts(path: Path) -> list[str]:
138
+ pkg = path / "package.json"
139
+ if not pkg.exists():
140
+ return []
141
+ try:
142
+ data = json.loads(pkg.read_text(encoding="utf-8"))
143
+ except (OSError, json.JSONDecodeError, UnicodeDecodeError):
144
+ return []
145
+ scripts = data.get("scripts")
146
+ if not isinstance(scripts, dict):
147
+ return []
148
+ preferred = ["dev", "start", "build", "test", "lint", "typecheck"]
149
+ found = [key for key in preferred if key in scripts]
150
+ extras = sorted(k for k in scripts if k not in found)
151
+ return found + extras[:4]
152
+
153
+
154
+ def inspect_repo(path: Path, root: Path, todo_limit: int) -> RepoReport:
155
+ branch = run(["git", "branch", "--show-current"], path) or None
156
+ status = run(["git", "status", "--porcelain=v2", "--branch"], path)
157
+ ahead, behind, changed = parse_porcelain_v2(status)
158
+ dirty = changed > 0
159
+ commit = run(["git", "log", "-1", "--format=%h %s"], path) or None
160
+ commit_time = run(["git", "log", "-1", "--format=%cI"], path) or None
161
+ todos = recent_todos(path, todo_limit)
162
+ scripts = package_scripts(path)
163
+
164
+ score = 0
165
+ reasons: list[str] = []
166
+ if dirty:
167
+ score += min(40, 10 + changed * 2)
168
+ reasons.append(f"{changed} changed file(s)")
169
+ if ahead:
170
+ score += 18
171
+ reasons.append(f"{ahead} commit(s) ahead")
172
+ if behind:
173
+ score += 12
174
+ reasons.append(f"{behind} commit(s) behind")
175
+ if todos:
176
+ score += min(15, len(todos) * 3)
177
+ reasons.append(f"{len(todos)} TODO-like note(s)")
178
+ if commit_time:
179
+ try:
180
+ then = datetime.fromisoformat(commit_time.replace("Z", "+00:00"))
181
+ age_days = (datetime.now(timezone.utc) - then.astimezone(timezone.utc)).days
182
+ if age_days <= 2:
183
+ score += 10
184
+ reasons.append("recently touched")
185
+ except ValueError:
186
+ pass
187
+
188
+ return RepoReport(
189
+ name=path.name,
190
+ path=str(path.relative_to(root)),
191
+ branch=branch,
192
+ dirty=dirty,
193
+ ahead=ahead,
194
+ behind=behind,
195
+ changed_files=changed,
196
+ recent_commit=commit,
197
+ recent_commit_time=commit_time,
198
+ todos=todos,
199
+ scripts=scripts,
200
+ score=score,
201
+ reasons=reasons,
202
+ )
203
+
204
+
205
+ def render_table(reports: Iterable[RepoReport], root: Path) -> str:
206
+ reports = list(reports)
207
+ lines = [f"Workspace Scout: {root}", ""]
208
+ if not reports:
209
+ return "\n".join(lines + ["No git repositories found."])
210
+
211
+ name_w = min(28, max(len(r.name) for r in reports))
212
+ lines.append(f"{'repo'.ljust(name_w)} {'branch'.ljust(18)} score status")
213
+ lines.append(f"{'-' * name_w} {'-' * 18} ----- ------")
214
+ for r in reports:
215
+ branch = (r.branch or "-")[:18].ljust(18)
216
+ status = ", ".join(r.reasons) if r.reasons else "quiet"
217
+ lines.append(f"{r.name[:name_w].ljust(name_w)} {branch} {r.score:>5} {status}")
218
+ return "\n".join(lines)
219
+
220
+
221
+ def render_brief(reports: Iterable[RepoReport], root: Path, top: int) -> str:
222
+ reports = list(reports)[:top]
223
+ lines = [f"Top workspace items in {root}:"]
224
+ if not reports:
225
+ return "No git repositories found."
226
+ for idx, r in enumerate(reports, 1):
227
+ reason = ", ".join(r.reasons) if r.reasons else "quiet"
228
+ lines.append(f"{idx}. {r.name} ({r.branch or '-'}) - {reason}")
229
+ if r.recent_commit:
230
+ lines.append(f" last: {r.recent_commit}")
231
+ if r.scripts:
232
+ lines.append(f" scripts: {', '.join(r.scripts)}")
233
+ for todo in r.todos[:2]:
234
+ lines.append(f" note: {todo}")
235
+ return "\n".join(lines)
236
+
237
+
238
+ def main(argv: list[str] | None = None) -> int:
239
+ parser = argparse.ArgumentParser(
240
+ description="Scan a workspace and rank git repositories that need attention."
241
+ )
242
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
243
+ parser.add_argument("root", nargs="?", default="~/workspace", help="workspace root")
244
+ parser.add_argument("--top", type=int, default=12, help="number of repos to show")
245
+ parser.add_argument("--depth", type=int, default=2, help="directory search depth")
246
+ parser.add_argument("--todos", type=int, default=3, help="TODO lines per repo")
247
+ parser.add_argument("--json", action="store_true", help="emit JSON")
248
+ parser.add_argument(
249
+ "--brief",
250
+ action="store_true",
251
+ help="emit a compact summary suitable for pasting into Claude Code",
252
+ )
253
+ args = parser.parse_args(argv)
254
+
255
+ root = Path(os.path.expanduser(args.root)).resolve()
256
+ if not root.exists():
257
+ print(f"Root does not exist: {root}", file=sys.stderr)
258
+ return 2
259
+
260
+ repos = find_repos(root, args.depth, DEFAULT_IGNORE)
261
+ with concurrent.futures.ThreadPoolExecutor(max_workers=min(16, max(1, len(repos)))) as pool:
262
+ reports = list(pool.map(lambda p: inspect_repo(p, root, args.todos), repos))
263
+ reports.sort(key=lambda r: (r.score, r.changed_files, r.name.lower()), reverse=True)
264
+ selected = reports[: max(1, args.top)]
265
+
266
+ if args.json:
267
+ print(json.dumps([asdict(r) for r in selected], ensure_ascii=False, indent=2))
268
+ elif args.brief:
269
+ print(render_brief(selected, root, args.top))
270
+ else:
271
+ print(render_table(selected, root))
272
+ print("")
273
+ print("Tip: run with --brief to paste the summary into Claude Code.")
274
+ return 0
275
+
276
+
277
+ if __name__ == "__main__":
278
+ raise SystemExit(main())