@chappibunny/repolens 1.15.5 → 1.16.0
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.
- package/CHANGELOG.md +35 -0
- package/README.md +17 -14
- package/app.yml +23 -0
- package/github-app-manifest.json +22 -0
- package/llms-full.txt +1 -0
- package/llms-install.md +1 -0
- package/llms.txt +1 -0
- package/package.json +8 -6
- package/src/analyzers/context-builder.js +11 -3
- package/src/analyzers/dependency-graph.js +6 -6
- package/src/cli-args.js +4 -0
- package/src/cli.js +11 -0
- package/src/core/scan.js +62 -20
- package/src/docs/llms-text.js +2 -0
- package/src/github-app/auth.js +130 -0
- package/src/github-app/checkout.js +70 -0
- package/src/github-app/handler.js +170 -0
- package/src/github-app/manifest.js +36 -0
- package/src/github-app/run.js +67 -0
- package/src/github-app/server.js +120 -0
- package/src/github-app/signature.js +22 -0
- package/src/help.js +44 -0
- package/src/renderers/renderDiff.js +5 -3
- package/src/renderers/renderMap.js +7 -5
- package/src/utils/route-params.js +50 -0
- package/src/utils/yaml.js +11 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to RepoLens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 1.16.0
|
|
6
|
+
|
|
7
|
+
### GitHub App
|
|
8
|
+
|
|
9
|
+
- `repolens app` self-hosts a GitHub App webhook. It checks `X-Hub-Signature-256`, acknowledges `installation` and `installation_repositories`, and on `pull_request` `opened`, `synchronize`, `reopened`, and `ready_for_review` runs the existing architecture diff. It posts a Check Run named RepoLens (the same summary as the sticky comment) and updates that comment. A finished diff is a successful check. The check fails only when the analysis throws
|
|
10
|
+
- Manifest: `app.yml` and `github-app-manifest.json`, also `repolens app --manifest`. Permissions are checks write, pull requests write, contents read, and metadata read. The hook URL in the repo is a placeholder (`https://example.com/api/github/webhooks`). This release does not register an app and CI does not need new secrets
|
|
11
|
+
- The server answers 202 before the clone and scoring finish, because GitHub allows about 10 seconds for the HTTP response. Setup is in [docs/GITHUB_APP.md](docs/GITHUB_APP.md)
|
|
12
|
+
|
|
13
|
+
### Security
|
|
14
|
+
|
|
15
|
+
- Route parameters in `src/renderers/renderDiff.js` no longer use `/\[(.*?)\]/g`. That lazy pattern is polynomial on a long string of `[` with no `]` (CodeQL `js/polynomial-redos`, alert #6). The replacement is a linear scan, including the same pattern in `src/core/scan.js` and the route-group labels in `src/renderers/renderMap.js`. A regression test feeds 100,000 brackets and expects a fast result
|
|
16
|
+
|
|
17
|
+
### Output accuracy
|
|
18
|
+
|
|
19
|
+
Hands-on `repolens demo` on expressjs/express, pallets/flask, and spf13/cobra:
|
|
20
|
+
|
|
21
|
+
- Express 5 has no `main` field. A description that says library, SDK, parser, toolkit, or framework is a library. Express reads as a library, not an API service
|
|
22
|
+
- `app.get('query parser fn')` is a setting lookup, not a route. Comment lines are skipped. Routes in tests, examples, and docs are not the product API. The Express route map is skipped
|
|
23
|
+
- A flat package at the repository root is named after the directory (`cobra`, not `root`). The system map connects that package to `doc/` (one relationship). Files with no directory belong to that flat module
|
|
24
|
+
- External imports that appear only in tests or examples are development tooling even when there is no `package.json`. Flask no longer lists `site_app` and the other test fixtures as runtime packages. Cobra lists `pflag` and `mousetrap` as runtime, and `go-md2man` and `yaml` as doc tooling
|
|
25
|
+
- Flask hubs are `globals.py`, `helpers.py`, `typing.py`, and `signals.py`. The three cycles (`__init__` → `app` → `cli`, and the templating / sansio chains) are real import cycles
|
|
26
|
+
|
|
27
|
+
### Dependencies
|
|
28
|
+
|
|
29
|
+
- `js-yaml` 4.3.2 → 5.5.0. Call the named `load` export. The YAML 1.2 Core schema still treats `true`/`false` as booleans and `yes`/`no` as strings. The merge key (`<<`) is enabled. `maxAliases` caps alias expansion. An empty file still returns `undefined`
|
|
30
|
+
- `@sentry/node` 10.76.2 → 11.9.0 (`^11.8.0`). It is still optional. Its own engines exclude Node 18 (`>=20.19` or `>=22.12`). `npm ci` on Node 18 warns and continues, and a failed import still disables telemetry instead of crashing the CLI
|
|
31
|
+
- Vitest 4.1.12 → 5.0.5. Its declared engines are `^22.12 || ^24 || >=26`. The runner starts on Node 20 and does not start on Node 18 (`util.styleText`). CI still runs `npm test` on Node 20 and 22, and `scripts/node18-runtime.js` on Node 18. `engines` stays `>=18`
|
|
32
|
+
- `dotenv` 18.0.8 → 18.0.9 and `@modelcontextprotocol/sdk` 1.32.1 → 1.32.2
|
|
33
|
+
- GitHub Actions: `actions/checkout` pin is the v6.0.3 release commit `df4cb1c`, `actions/setup-node` v7.1.0 (`949feb2`), `softprops/action-gh-release` v3.0.3 (`efb3536`). `npm audit` reports no vulnerabilities
|
|
34
|
+
|
|
35
|
+
### Docs
|
|
36
|
+
|
|
37
|
+
- Roadmap item 1 (GitHub App) is shipped in 1.16.0. Java and C# language support is now What's Next item 1
|
|
38
|
+
- The note that a chat answer only covers what someone thought to ask, and that grounded AI Q&A sits on top of the full map, stays in the roadmap, README, known issues, and copilot instructions
|
|
39
|
+
|
|
5
40
|
## 1.15.5
|
|
6
41
|
|
|
7
42
|
Fixes twelve output problems found by running 1.15.4 on mattiasw/ExifReader (a plain-JavaScript library with a flat `src/` and a build-helper `bin/`), and the two leftovers from 1.15.4.
|
package/README.md
CHANGED
|
@@ -119,6 +119,7 @@ Run `npx @chappibunny/repolens migrate` to automatically update your workflow fi
|
|
|
119
119
|
| 🤖 **Autonomous** | Runs on every push via GitHub Actions — docs stay evergreen |
|
|
120
120
|
| 👥 **Multi-Audience** | Technical docs + stakeholder-readable overviews from one scan |
|
|
121
121
|
| 📤 **Multi-Publisher** | Notion, Confluence, GitHub Wiki, Obsidian, Markdown — together or one at a time |
|
|
122
|
+
| 🗺️ **Shows what you didn't ask** | An LLM only answers the question it is given, and a newcomer doesn't know what to ask. RepoLens publishes the whole map (every module, cycle and hub) on every PR, the same way each time, with no tokens spent per question |
|
|
122
123
|
| 🧠 **AI-Assisted** | Optional AI with zero-hallucination policy (structured context only) |
|
|
123
124
|
| 🔍 **Smart Detection** | Frameworks, domains, data flows, dependencies, drift — all automatic |
|
|
124
125
|
| 🔌 **Extensible** | Plugin system for custom renderers, publishers, and hooks |
|
|
@@ -189,6 +190,7 @@ Step-by-step setup for publishers, AI features, Notion, Confluence, GitHub Wiki,
|
|
|
189
190
|
| `npx @chappibunny/repolens mcp` | Start the stdio MCP server (`repolens-mcp` is the same server) |
|
|
190
191
|
| `npx @chappibunny/repolens badge` | shields.io code-health JSON, SVG, or markdown. Exits 2 when nothing is scored |
|
|
191
192
|
| `npx @chappibunny/repolens comment --base <ref> --head <ref>` | Architecture diff (files, modules, dependencies, health delta, new hotspots) |
|
|
193
|
+
| `npx @chappibunny/repolens app` | Self-host the GitHub App webhook. `--manifest` prints the manifest |
|
|
192
194
|
|
|
193
195
|
> **Note:** If you installed globally with `npm install -g @chappibunny/repolens`, you can use the shorter `repolens <command>` form. Init's next-steps text uses that shorter form when the command on your PATH is the global install.
|
|
194
196
|
> **Warning:** Do not run bare `npx repolens` — there is an unrelated `repolens@0.0.1` placeholder package on npm. Always use the scoped name `@chappibunny/repolens`.
|
|
@@ -199,7 +201,7 @@ Step-by-step setup for publishers, AI features, Notion, Confluence, GitHub Wiki,
|
|
|
199
201
|
|---|---|
|
|
200
202
|
| `0` | Success, including a cancelled wizard or help with no other work |
|
|
201
203
|
| `1` | Runtime error, or an unknown command |
|
|
202
|
-
| `2` | Invalid flags, missing config, target directory not found, a failed `doctor` check (including 0 files matched), `demo` or `publish` scanned 0 files, `watch` has nothing to watch (missing `module_roots`, or `scan.include` matches no files), `badge` scored 0 source files, `comment` is missing a base or head ref, `publish --strict` is missing publisher credentials, or `feedback` has no terminal |
|
|
204
|
+
| `2` | Invalid flags, missing config, target directory not found, a failed `doctor` check (including 0 files matched), `demo` or `publish` scanned 0 files, `watch` has nothing to watch (missing `module_roots`, or `scan.include` matches no files), `badge` scored 0 source files, `comment` is missing a base or head ref, `publish --strict` is missing publisher credentials, `app` is missing its id, webhook secret, or private key, or `feedback` has no terminal |
|
|
203
205
|
|
|
204
206
|
`--target` must already exist. A missing path exits 2 with `Target directory not found: <path>` before any scan. `repolens <command> --help` prints that command's flags. The ASCII banner is omitted when stdout is not a terminal or `CI` is set.
|
|
205
207
|
|
|
@@ -241,9 +243,9 @@ If `npx` reports `Unknown command: mcp`, an `.npmrc` with `min-release-age` (or
|
|
|
241
243
|
|
|
242
244
|
## GitHub Action
|
|
243
245
|
|
|
244
|
-
`action.yml` is a composite action named **RepoLens Architecture Docs**. `publish`, `demo`, and `doctor` run the published npm package (`npx --yes @chappibunny/repolens@1.
|
|
246
|
+
`action.yml` is a composite action named **RepoLens Architecture Docs**. `publish`, `demo`, and `doctor` run the published npm package (`npx --yes @chappibunny/repolens@1.16.0`). `comment` and `badge` run from the action checkout. Inputs are `command` (`publish`, `demo`, `doctor`, `comment`, or `badge`), `config`, `node-version`, `pr-comment`, `version`, `badge-out`, `badge-url`, and `badge-link`.
|
|
245
247
|
|
|
246
|
-
Publishing docs (the npm package at tag `v1.
|
|
248
|
+
Publishing docs (the npm package at tag `v1.16.0`):
|
|
247
249
|
|
|
248
250
|
```yaml
|
|
249
251
|
name: Architecture docs
|
|
@@ -258,8 +260,8 @@ jobs:
|
|
|
258
260
|
docs:
|
|
259
261
|
runs-on: ubuntu-latest
|
|
260
262
|
steps:
|
|
261
|
-
- uses: actions/checkout@
|
|
262
|
-
- uses: CHAPIBUNNY/repolens@v1.
|
|
263
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
264
|
+
- uses: CHAPIBUNNY/repolens@v1.16.0
|
|
263
265
|
with:
|
|
264
266
|
command: publish
|
|
265
267
|
config: .repolens.yml
|
|
@@ -267,7 +269,7 @@ jobs:
|
|
|
267
269
|
pr-comment: ${{ github.event_name == 'pull_request' && 'true' || 'false' }}
|
|
268
270
|
```
|
|
269
271
|
|
|
270
|
-
On `v1.15.2`, `pr-comment: true` runs the architecture diff from the action checkout and turns the published package's older short summary off. The diff lists modules, files, dependencies, the code-health delta, and new hotspots. It edits one sticky comment (`<!-- repolens:architecture-diff -->`) instead of adding a new comment each push. The footer is "Generated by RepoLens" and links to this repository and the [gallery](https://chapibunny.github.io/repolens/). Tag `v1.15.1` does not contain `command: comment`. Pin `v1.
|
|
272
|
+
On `v1.15.2`, `pr-comment: true` runs the architecture diff from the action checkout and turns the published package's older short summary off. The diff lists modules, files, dependencies, the code-health delta, and new hotspots. It edits one sticky comment (`<!-- repolens:architecture-diff -->`) instead of adding a new comment each push. The footer is "Generated by RepoLens" and links to this repository and the [gallery](https://chapibunny.github.io/repolens/). Tag `v1.15.1` does not contain `command: comment`. Pin `v1.16.0` or a commit SHA in production.
|
|
271
273
|
|
|
272
274
|
```yaml
|
|
273
275
|
name: RepoLens architecture comment
|
|
@@ -280,10 +282,10 @@ jobs:
|
|
|
280
282
|
architecture:
|
|
281
283
|
runs-on: ubuntu-latest
|
|
282
284
|
steps:
|
|
283
|
-
- uses: actions/checkout@
|
|
285
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
284
286
|
with:
|
|
285
287
|
fetch-depth: 0
|
|
286
|
-
- uses: CHAPIBUNNY/repolens@v1.
|
|
288
|
+
- uses: CHAPIBUNNY/repolens@v1.16.0
|
|
287
289
|
with:
|
|
288
290
|
command: comment
|
|
289
291
|
```
|
|
@@ -292,7 +294,7 @@ jobs:
|
|
|
292
294
|
|
|
293
295
|
`pr-comment: true` on `publish`, `demo`, or `doctor` (when the action ref contains this change) runs the same comment after the package and turns the package's older comment off, so the pull request still gets one comment.
|
|
294
296
|
|
|
295
|
-
The action is listed on the GitHub Marketplace only after a release is published with **Publish this Action to the GitHub Marketplace** checked. Those steps are in [docs/RELEASE.md](docs/RELEASE.md). The tag `v1.
|
|
297
|
+
The action is listed on the GitHub Marketplace only after a release is published with **Publish this Action to the GitHub Marketplace** checked. Those steps are in [docs/RELEASE.md](docs/RELEASE.md). The tag `v1.16.0` has to exist before `uses: CHAPIBUNNY/repolens@v1.16.0` resolves, and `@chappibunny/repolens@1.16.0` has to be on npm before `publish` can install it. `command: comment` and `command: badge` do not install that package.
|
|
296
298
|
|
|
297
299
|
## Add the badge to your repo
|
|
298
300
|
|
|
@@ -327,8 +329,8 @@ jobs:
|
|
|
327
329
|
badge:
|
|
328
330
|
runs-on: ubuntu-latest
|
|
329
331
|
steps:
|
|
330
|
-
- uses: actions/checkout@
|
|
331
|
-
- uses: CHAPIBUNNY/repolens@v1.
|
|
332
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
333
|
+
- uses: CHAPIBUNNY/repolens@v1.16.0
|
|
332
334
|
with:
|
|
333
335
|
command: badge
|
|
334
336
|
badge-url: https://USER.github.io/REPO/repolens-badge.json
|
|
@@ -357,7 +359,7 @@ Gallery repositories already publish this. Each one has a [score page](https://c
|
|
|
357
359
|
|
|
358
360
|
Published system maps are Unicode diagrams drawn from import edges. RepoLens does not emit Mermaid.
|
|
359
361
|
|
|
360
|
-
The publisher set is Notion, Confluence, GitHub Wiki, Obsidian, and Markdown (`src/publishers`, 7 files). 1.14.0 adds the MCP server, the Marketplace action, and the docs badge. 1.14.1 sets the registry name to `io.github.CHAPIBUNNY/repolens`. 1.14.2 adds the gallery link to the badge line, `glama.json`, and `CITATION.cff`. 1.14.3 adds Cline setup docs and `llms-install.md`. 1.15.0 detects repository languages in `init --quick`, refuses empty scans, and adds per-command help. 1.15.1 makes `watch` exit 2 when those globs match nothing, and scores code health for Python and Go as well as JavaScript and TypeScript. 1.15.2 reads Python and Go imports in the dependency graph, skips comment and type-only imports, adds `repolens badge` and `repolens comment`, and adds gallery explained pages plus `llms.txt`. 1.15.3 skips Python `TYPE_CHECKING` imports, omits Node.js built-ins from external packages, groups Go hubs by package, and ignores test files as architecture-pattern evidence. 1.15.4 names the project from `package.json`, scores maturity from version, history, tests, CI, and docs, reports circular dependencies as findings, drops self-imports and noise data flows, and stops classifying `index` and `core` files by name. 1.15.5 reads a flat `src/` as one module, tells a library from a CLI by its `package.json`, splits runtime from development packages, keeps tests, examples, and build scripts out of hubs and security severity, and skips empty Route Map and GraphQL pages. Layout and counts are in [ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
362
|
+
The publisher set is Notion, Confluence, GitHub Wiki, Obsidian, and Markdown (`src/publishers`, 7 files). 1.14.0 adds the MCP server, the Marketplace action, and the docs badge. 1.14.1 sets the registry name to `io.github.CHAPIBUNNY/repolens`. 1.14.2 adds the gallery link to the badge line, `glama.json`, and `CITATION.cff`. 1.14.3 adds Cline setup docs and `llms-install.md`. 1.15.0 detects repository languages in `init --quick`, refuses empty scans, and adds per-command help. 1.15.1 makes `watch` exit 2 when those globs match nothing, and scores code health for Python and Go as well as JavaScript and TypeScript. 1.15.2 reads Python and Go imports in the dependency graph, skips comment and type-only imports, adds `repolens badge` and `repolens comment`, and adds gallery explained pages plus `llms.txt`. 1.15.3 skips Python `TYPE_CHECKING` imports, omits Node.js built-ins from external packages, groups Go hubs by package, and ignores test files as architecture-pattern evidence. 1.15.4 names the project from `package.json`, scores maturity from version, history, tests, CI, and docs, reports circular dependencies as findings, drops self-imports and noise data flows, and stops classifying `index` and `core` files by name. 1.15.5 reads a flat `src/` as one module, tells a library from a CLI by its `package.json`, splits runtime from development packages, keeps tests, examples, and build scripts out of hubs and security severity, and skips empty Route Map and GraphQL pages. 1.16.0 adds `repolens app` (a self-hosted GitHub App webhook), names a flat package after its directory, treats a framework description as a library when `main` is absent, and keeps test and example routes out of the product API. Layout and counts are in [ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
361
363
|
|
|
362
364
|
### System Overview (Technical Profile)
|
|
363
365
|
|
|
@@ -419,6 +421,7 @@ When you open a pull request, RepoLens posts:
|
|
|
419
421
|
| [Migration](docs/MIGRATION.md) | Upgrading from v0.3.0 or earlier |
|
|
420
422
|
| [Stability](docs/STABILITY.md) | API contract and semver guarantees |
|
|
421
423
|
| [MCP server](docs/MCP.md) | stdio server for Cursor, Claude, and VS Code |
|
|
424
|
+
| [GitHub App](docs/GITHUB_APP.md) | Self-host the webhook: manifest, env vars, check run |
|
|
422
425
|
|
|
423
426
|
---
|
|
424
427
|
|
|
@@ -445,11 +448,11 @@ v1.0+ features complete — CLI, config schema, and plugin interface are frozen.
|
|
|
445
448
|
- [x] MCP server (`repolens mcp`) for Cursor, Claude, and VS Code
|
|
446
449
|
- [x] GitHub Action `RepoLens Architecture Docs` (`action.yml`)
|
|
447
450
|
- [x] Docs by RepoLens badge on generated Markdown
|
|
451
|
+
- [x] GitHub App webhook (`repolens app`) for a check run and the architecture comment
|
|
448
452
|
|
|
449
453
|
**Next:**
|
|
450
|
-
- [ ] GitHub App for automated setup and status checks
|
|
451
454
|
- [ ] Java and C# language support
|
|
452
|
-
- [ ] Grounded AI explanations and Q&A (`repolens ask`, PR comment, MCP, VS Code hover)
|
|
455
|
+
- [ ] Grounded AI explanations and Q&A (`repolens ask`, PR comment, MCP, VS Code hover), layered on the always-published map and not a replacement for it
|
|
453
456
|
- [ ] Slack notifications
|
|
454
457
|
- [ ] Custom webhook support
|
|
455
458
|
- [ ] Cross-repository architecture analysis
|
package/app.yml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# GitHub App manifest for RepoLens.
|
|
2
|
+
# Register from github-app-manifest.json (same fields) after you replace
|
|
3
|
+
# hook_attributes.url with the public URL of `repolens app`.
|
|
4
|
+
# Permissions stay minimal: checks write, pull requests write, contents read,
|
|
5
|
+
# metadata read. This file does not create the app.
|
|
6
|
+
|
|
7
|
+
name: RepoLens
|
|
8
|
+
description: Posts an architecture diff as a check run and one sticky pull request comment.
|
|
9
|
+
url: https://github.com/CHAPIBUNNY/repolens
|
|
10
|
+
hook_attributes:
|
|
11
|
+
url: https://example.com/api/github/webhooks
|
|
12
|
+
active: true
|
|
13
|
+
redirect_url: https://github.com/CHAPIBUNNY/repolens
|
|
14
|
+
public: false
|
|
15
|
+
default_permissions:
|
|
16
|
+
checks: write
|
|
17
|
+
pull_requests: write
|
|
18
|
+
contents: read
|
|
19
|
+
metadata: read
|
|
20
|
+
default_events:
|
|
21
|
+
- installation
|
|
22
|
+
- installation_repositories
|
|
23
|
+
- pull_request
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "RepoLens",
|
|
3
|
+
"description": "Posts an architecture diff as a check run and one sticky pull request comment.",
|
|
4
|
+
"url": "https://github.com/CHAPIBUNNY/repolens",
|
|
5
|
+
"hook_attributes": {
|
|
6
|
+
"url": "https://example.com/api/github/webhooks",
|
|
7
|
+
"active": true
|
|
8
|
+
},
|
|
9
|
+
"redirect_url": "https://github.com/CHAPIBUNNY/repolens",
|
|
10
|
+
"public": false,
|
|
11
|
+
"default_permissions": {
|
|
12
|
+
"checks": "write",
|
|
13
|
+
"pull_requests": "write",
|
|
14
|
+
"contents": "read",
|
|
15
|
+
"metadata": "read"
|
|
16
|
+
},
|
|
17
|
+
"default_events": [
|
|
18
|
+
"installation",
|
|
19
|
+
"installation_repositories",
|
|
20
|
+
"pull_request"
|
|
21
|
+
]
|
|
22
|
+
}
|
package/llms-full.txt
CHANGED
|
@@ -24,6 +24,7 @@ Agent-oriented install steps, including the Cline MCP snippet, are in llms-insta
|
|
|
24
24
|
- `repolens watch` regenerates docs on change and exits 2 when nothing matches.
|
|
25
25
|
- `repolens badge` prints a shields.io endpoint JSON document for the code-health score (0–100). `--format svg` prints a square SVG. `--format markdown --url https://host/repolens-badge.json` prints a README snippet. `--format all --out repolens-badge.json` writes JSON and SVG. Exit 2 when no JavaScript, TypeScript, Python, or Go files were scored. The score is not reported as 100 when zero files match.
|
|
26
26
|
- `repolens comment --base <ref> --head <ref>` compares those commits. It prints markdown locally. In GitHub Actions, with `GITHUB_TOKEN`, `GITHUB_REPOSITORY`, and a pull_request event, it creates or updates one comment.
|
|
27
|
+
- `repolens app` self-hosts a GitHub App webhook. It verifies `X-Hub-Signature-256`, posts a Check Run named RepoLens, and updates the sticky architecture comment. `repolens app --manifest` prints the manifest and needs no secrets. Listening needs `REPOLENS_APP_ID`, `REPOLENS_WEBHOOK_SECRET`, and a private key. See docs/GITHUB_APP.md.
|
|
27
28
|
- `repolens mcp` starts the stdio MCP server. `repolens-mcp` is the same entry.
|
|
28
29
|
|
|
29
30
|
Exit codes: 0 success, 1 runtime error or unknown command, 2 validation failure (bad flags, missing config, empty scan, and the other cases in the README).
|
package/llms-install.md
CHANGED
|
@@ -21,3 +21,4 @@
|
|
|
21
21
|
12. CLI (1.15.3): Python imports under `if TYPE_CHECKING:` (including `typing.TYPE_CHECKING` and `import typing as t`) are not cycle edges. Node.js built-ins are not external packages. Go hub rows are packages. Test files are not architecture-pattern evidence, and "API route pattern" requires a route directory.
|
|
22
22
|
13. CLI (1.15.4): the project name comes from `package.json` or the real directory (never `.`). Maturity uses version, history, tests, CI, and docs. Circular dependencies are findings, not architectural patterns. A package's own name is not an external dependency. `index` and `main` files are not classified by name. Noise data flows are dropped.
|
|
23
23
|
14. CLI (1.15.5): a flat `src/` is one module. A package with `main`, `module`, or `exports` is a library, not a CLI. Hubs and security severity ignore tests, examples, and build scripts. External packages are split into runtime and development tooling. Route Map and GraphQL Schema are skipped when empty.
|
|
24
|
+
15. CLI (1.16.0): `repolens app` self-hosts a GitHub App webhook (manifest in `app.yml`, check run plus the sticky architecture comment). A framework description is a library even without `main`. A flat package is named after its directory. Test and example routes are not the product API. Test-only imports are dev tooling on Python and Go.
|
package/llms.txt
CHANGED
|
@@ -17,6 +17,7 @@ RepoLens is the npm package `@chappibunny/repolens` (Node.js 18+). It is a CLI,
|
|
|
17
17
|
- [publish](https://github.com/CHAPIBUNNY/repolens/blob/main/docs/INSTALLATION.md): `npx @chappibunny/repolens publish` scans and publishes
|
|
18
18
|
- [badge](https://github.com/CHAPIBUNNY/repolens/blob/main/README.md): `npx @chappibunny/repolens badge` prints shields.io code-health JSON, SVG, or markdown
|
|
19
19
|
- [comment](https://github.com/CHAPIBUNNY/repolens/blob/main/README.md): `npx @chappibunny/repolens comment --base <ref> --head <ref>` prints an architecture diff
|
|
20
|
+
- [app](https://github.com/CHAPIBUNNY/repolens/blob/main/docs/GITHUB_APP.md): `npx @chappibunny/repolens app` self-hosts the GitHub App webhook. `--manifest` prints the manifest. No credentials are required to print it
|
|
20
21
|
- [doctor](https://github.com/CHAPIBUNNY/repolens/blob/main/docs/INSTALLATION.md): `npx @chappibunny/repolens doctor` validates setup
|
|
21
22
|
- [mcp](https://github.com/CHAPIBUNNY/repolens/blob/main/docs/MCP.md): `npx -y @chappibunny/repolens mcp` starts the stdio server
|
|
22
23
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chappibunny/repolens",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"mcpName": "io.github.CHAPIBUNNY/repolens",
|
|
5
5
|
"description": "AI-assisted documentation intelligence system for technical and non-technical audiences",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
"repolens-mcp": "bin/repolens-mcp.js"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
|
+
"app.yml",
|
|
21
22
|
"bin",
|
|
23
|
+
"github-app-manifest.json",
|
|
22
24
|
"src",
|
|
23
25
|
"package.json",
|
|
24
26
|
"README.md",
|
|
@@ -86,18 +88,18 @@
|
|
|
86
88
|
"release:major": "npm version major"
|
|
87
89
|
},
|
|
88
90
|
"dependencies": {
|
|
89
|
-
"@modelcontextprotocol/sdk": "^1.32.
|
|
90
|
-
"dotenv": "^18.0.
|
|
91
|
+
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
92
|
+
"dotenv": "^18.0.9",
|
|
91
93
|
"fast-glob": "^3.3.3",
|
|
92
|
-
"js-yaml": "^
|
|
94
|
+
"js-yaml": "^5.5.0",
|
|
93
95
|
"zod": "4.6.7"
|
|
94
96
|
},
|
|
95
97
|
"optionalDependencies": {
|
|
96
|
-
"@sentry/node": "^
|
|
98
|
+
"@sentry/node": "^11.8.0"
|
|
97
99
|
},
|
|
98
100
|
"devDependencies": {
|
|
99
101
|
"tinyexec": "^1.3.1",
|
|
100
|
-
"vitest": "^
|
|
102
|
+
"vitest": "^5.0.5"
|
|
101
103
|
},
|
|
102
104
|
"overrides": {
|
|
103
105
|
"yauzl": ">=3.2.1",
|
|
@@ -30,20 +30,28 @@ export function computeModuleDepMetrics(depGraph) {
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Find the module a file belongs to: the module with the longest key that is a path
|
|
33
|
-
* prefix of the file. Files at the repository root belong to
|
|
33
|
+
* prefix of the file. Files at the repository root belong to the flat module
|
|
34
|
+
* (the directory name, or the legacy key `root`).
|
|
34
35
|
*/
|
|
36
|
+
function flatModuleKey(modules) {
|
|
37
|
+
const marked = modules.find((mod) => mod.flat);
|
|
38
|
+
if (marked) return marked.key;
|
|
39
|
+
return modules.some((mod) => mod.key === "root") ? "root" : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
function buildModuleLookup(modules) {
|
|
36
43
|
const keys = modules.map((m) => m.key).sort((x, y) => y.length - x.length);
|
|
44
|
+
const flatKey = flatModuleKey(modules);
|
|
37
45
|
const cache = new Map();
|
|
38
46
|
return (file) => {
|
|
39
47
|
const norm = String(file || "").replace(/\\/g, "/");
|
|
40
48
|
if (cache.has(norm)) return cache.get(norm);
|
|
41
49
|
let found = null;
|
|
42
50
|
for (const key of keys) {
|
|
43
|
-
if (key ===
|
|
51
|
+
if (key === flatKey) continue;
|
|
44
52
|
if (norm === key || norm.startsWith(`${key}/`)) { found = key; break; }
|
|
45
53
|
}
|
|
46
|
-
if (!found && !norm.includes("/") &&
|
|
54
|
+
if (!found && !norm.includes("/") && flatKey) found = flatKey;
|
|
47
55
|
cache.set(norm, found);
|
|
48
56
|
return found;
|
|
49
57
|
};
|
|
@@ -314,21 +314,21 @@ export async function analyzeDependencyGraph(files, repoRoot) {
|
|
|
314
314
|
* Split external packages into runtime dependencies and development tooling.
|
|
315
315
|
* A package declared in `devDependencies` is development tooling. One declared in
|
|
316
316
|
* `dependencies`, `optionalDependencies`, or `peerDependencies` is runtime. An undeclared
|
|
317
|
-
* package is runtime when a product file (not a test, example, or build script) imports it
|
|
318
|
-
*
|
|
317
|
+
* package is runtime when a product file (not a test, example, or build script) imports it,
|
|
318
|
+
* and development tooling when only tests or examples import it. Python and Go have no
|
|
319
|
+
* package.json, so that import split is the whole decision.
|
|
319
320
|
*/
|
|
320
321
|
async function splitExternalDeps(result, repoRoot) {
|
|
321
322
|
const raw = await readFileSafe(path.join(repoRoot, "package.json"));
|
|
322
323
|
let pkg = null;
|
|
323
324
|
try { pkg = raw ? JSON.parse(raw) : null; } catch { pkg = null; }
|
|
324
|
-
if (!pkg) return { runtime: [...result.externalDeps], dev: [] };
|
|
325
325
|
|
|
326
|
-
const declaredRuntime = new Set([
|
|
326
|
+
const declaredRuntime = new Set(pkg ? [
|
|
327
327
|
...Object.keys(pkg.dependencies || {}),
|
|
328
328
|
...Object.keys(pkg.optionalDependencies || {}),
|
|
329
329
|
...Object.keys(pkg.peerDependencies || {}),
|
|
330
|
-
]);
|
|
331
|
-
const declaredDev = new Set(Object.keys(pkg.devDependencies || {}));
|
|
330
|
+
] : []);
|
|
331
|
+
const declaredDev = new Set(pkg ? Object.keys(pkg.devDependencies || {}) : []);
|
|
332
332
|
|
|
333
333
|
const productImports = new Set();
|
|
334
334
|
for (const node of result.nodes) {
|
package/src/cli-args.js
CHANGED
|
@@ -8,6 +8,9 @@ const FLAGS_WITH_VALUE = new Set([
|
|
|
8
8
|
"--url",
|
|
9
9
|
"--link",
|
|
10
10
|
"--label",
|
|
11
|
+
"--port",
|
|
12
|
+
"--host",
|
|
13
|
+
"--webhook-path",
|
|
11
14
|
]);
|
|
12
15
|
const BOOLEAN_FLAGS = new Set([
|
|
13
16
|
"--quick",
|
|
@@ -16,6 +19,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
16
19
|
"--force",
|
|
17
20
|
"--strict",
|
|
18
21
|
"--verbose",
|
|
22
|
+
"--manifest",
|
|
19
23
|
"--help",
|
|
20
24
|
"-h",
|
|
21
25
|
"--version",
|
package/src/cli.js
CHANGED
|
@@ -311,6 +311,17 @@ async function main() {
|
|
|
311
311
|
process.exit(code);
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
if (command === "app") {
|
|
315
|
+
const { runGitHubApp } = await import("./github-app/run.js");
|
|
316
|
+
const code = await runGitHubApp({
|
|
317
|
+
manifest: Boolean(parsed.flags["--manifest"]),
|
|
318
|
+
port: parsed.flags["--port"],
|
|
319
|
+
host: parsed.flags["--host"],
|
|
320
|
+
webhookPath: parsed.flags["--webhook-path"],
|
|
321
|
+
});
|
|
322
|
+
process.exit(code);
|
|
323
|
+
}
|
|
324
|
+
|
|
314
325
|
if (command === "mcp") {
|
|
315
326
|
process.env.REPOLENS_LOG_STDERR = "1";
|
|
316
327
|
const { startMcpServer } = await import("./mcp/server.js");
|
package/src/core/scan.js
CHANGED
|
@@ -5,7 +5,8 @@ import { info, warn } from "../utils/logger.js";
|
|
|
5
5
|
import { trackScan } from "../utils/telemetry.js";
|
|
6
6
|
import { detectMonorepo } from "../analyzers/monorepo-detector.js";
|
|
7
7
|
import { isInsideRoot } from "../utils/safe-path.js";
|
|
8
|
-
import { isTestPath } from "../utils/path-roles.js";
|
|
8
|
+
import { isNonProductPath, isTestPath } from "../utils/path-roles.js";
|
|
9
|
+
import { replaceBracketParams } from "../utils/route-params.js";
|
|
9
10
|
|
|
10
11
|
const norm = (p) => p.replace(/\\/g, "/");
|
|
11
12
|
|
|
@@ -93,7 +94,13 @@ async function readFileSafe(file) {
|
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
function
|
|
97
|
+
function rootModuleLabel(repoRoot) {
|
|
98
|
+
const base = path.basename(path.resolve(repoRoot || ""));
|
|
99
|
+
if (!base || base === "." || base === "..") return "root";
|
|
100
|
+
return base;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function moduleKeyForFile(file, moduleRoots, rootLabel = "root") {
|
|
97
104
|
const normalized = norm(file);
|
|
98
105
|
|
|
99
106
|
const sortedRoots = [...moduleRoots].sort((a, b) => b.length - a.length);
|
|
@@ -111,7 +118,9 @@ function moduleKeyForFile(file, moduleRoots) {
|
|
|
111
118
|
}
|
|
112
119
|
|
|
113
120
|
const parts = normalized.split("/");
|
|
114
|
-
|
|
121
|
+
// A file next to go.mod or package.json is one module, named for the directory.
|
|
122
|
+
// Calling it "root" hid that package on the system map.
|
|
123
|
+
return parts.length > 1 ? parts[0] : rootLabel;
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
function routePathFromFile(file) {
|
|
@@ -121,10 +130,9 @@ function routePathFromFile(file) {
|
|
|
121
130
|
const appIndex = f.indexOf("/app/");
|
|
122
131
|
const relative = f.slice(appIndex + 5);
|
|
123
132
|
|
|
124
|
-
const cleaned = relative
|
|
133
|
+
const cleaned = replaceBracketParams(relative
|
|
125
134
|
.replace(/\/page\.(ts|tsx|js|jsx)$/, "")
|
|
126
|
-
.replace(/\/route\.(ts|tsx|js|jsx)$/, "")
|
|
127
|
-
.replace(/\[(.*?)\]/g, ":$1")
|
|
135
|
+
.replace(/\/route\.(ts|tsx|js|jsx)$/, ""))
|
|
128
136
|
.replace(/\/$/, "");
|
|
129
137
|
|
|
130
138
|
return cleaned ? `/${cleaned}` : "/";
|
|
@@ -141,10 +149,9 @@ function routePathFromFile(file) {
|
|
|
141
149
|
const pagesIndex = f.indexOf("/pages/");
|
|
142
150
|
const relative = f.slice(pagesIndex + 7);
|
|
143
151
|
|
|
144
|
-
const cleaned = relative
|
|
152
|
+
const cleaned = replaceBracketParams(relative
|
|
145
153
|
.replace(/\.(ts|tsx|js|jsx)$/, "")
|
|
146
|
-
.replace(/\/index$/, "")
|
|
147
|
-
.replace(/\[(.*?)\]/g, ":$1")
|
|
154
|
+
.replace(/\/index$/, ""))
|
|
148
155
|
.replace(/\/$/, "");
|
|
149
156
|
|
|
150
157
|
return cleaned ? `/${cleaned}` : "/";
|
|
@@ -316,11 +323,13 @@ async function extractRepoMetadata(repoRoot, files = []) {
|
|
|
316
323
|
const hasLibraryEntry = Boolean(pkg.main || pkg.module || pkg.exports || pkg.types || pkg.typings || pkg.browser);
|
|
317
324
|
const isShippedApp = Boolean(allDeps["electron"] || allDeps["next"]);
|
|
318
325
|
metadata.packageEntryFiles = packageEntryFiles(pkg);
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
326
|
+
const description = String(pkg.description || "");
|
|
327
|
+
const keywords = Array.isArray(pkg.keywords) ? pkg.keywords.join(" ") : "";
|
|
328
|
+
const saysLibrary = /\b(library|sdk|parser|toolkit|framework)\b/i.test(`${description} ${keywords}`);
|
|
329
|
+
const saysCli = /\bcli\b|command[- ]line/i.test(`${description} ${keywords}`);
|
|
330
|
+
// Express 5 publishes index.js with no `main` field. A description that says
|
|
331
|
+
// library or framework is enough; Node's default entry is that index.js.
|
|
332
|
+
if ((hasLibraryEntry || saysLibrary) && !isShippedApp) {
|
|
324
333
|
metadata.projectKind = hasBin && !saysLibrary && saysCli ? "cli" : "library";
|
|
325
334
|
} else if (hasBin && !hasLibraryEntry) {
|
|
326
335
|
metadata.projectKind = "cli";
|
|
@@ -408,16 +417,37 @@ async function extractRepoMetadata(repoRoot, files = []) {
|
|
|
408
417
|
return metadata;
|
|
409
418
|
}
|
|
410
419
|
|
|
420
|
+
function isCommentLine(line) {
|
|
421
|
+
const trimmed = String(line || "").trim();
|
|
422
|
+
return trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function lineAt(content, index) {
|
|
426
|
+
const start = content.lastIndexOf("\n", Math.max(0, index - 1)) + 1;
|
|
427
|
+
const end = content.indexOf("\n", index);
|
|
428
|
+
return content.slice(start, end === -1 ? content.length : end);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** Express route paths start with `/`. `app.get('query parser fn')` is a setting. */
|
|
432
|
+
function isExpressPath(routePath) {
|
|
433
|
+
return typeof routePath === "string"
|
|
434
|
+
&& routePath.length > 0
|
|
435
|
+
&& routePath.length <= 200
|
|
436
|
+
&& (routePath.startsWith("/") || routePath === "*");
|
|
437
|
+
}
|
|
438
|
+
|
|
411
439
|
function extractExpressRoutes(content) {
|
|
412
440
|
const routes = [];
|
|
413
441
|
const seenRoutes = new Set();
|
|
414
442
|
|
|
415
|
-
// Match app.METHOD(path) or router.METHOD(path)
|
|
443
|
+
// Match app.METHOD(path) or router.METHOD(path). Skip comments and setting lookups.
|
|
416
444
|
const methodPattern = /(?:app|router)\.(get|post|put|patch|delete|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
|
|
417
445
|
let match;
|
|
418
446
|
|
|
419
447
|
while ((match = methodPattern.exec(content)) !== null) {
|
|
448
|
+
if (isCommentLine(lineAt(content, match.index))) continue;
|
|
420
449
|
const [, method, path] = match;
|
|
450
|
+
if (!isExpressPath(path)) continue;
|
|
421
451
|
const routeKey = `${method.toUpperCase()}:${path}`;
|
|
422
452
|
|
|
423
453
|
if (!seenRoutes.has(routeKey)) {
|
|
@@ -440,7 +470,9 @@ function extractExpressRoutes(content) {
|
|
|
440
470
|
// Match app.route(path).METHOD()
|
|
441
471
|
const routePattern = /(?:app|router)\.route\s*\(\s*['"`]([^'"`]+)['"`]\s*\)([.\s\w()]*)/gi;
|
|
442
472
|
while ((match = routePattern.exec(content)) !== null) {
|
|
473
|
+
if (isCommentLine(lineAt(content, match.index))) continue;
|
|
443
474
|
const [, path, chain] = match;
|
|
475
|
+
if (!isExpressPath(path)) continue;
|
|
444
476
|
const methods = [];
|
|
445
477
|
|
|
446
478
|
if (chain.includes(".get(")) methods.push("GET");
|
|
@@ -556,21 +588,28 @@ export async function scanRepo(cfg) {
|
|
|
556
588
|
}
|
|
557
589
|
|
|
558
590
|
const moduleCounts = new Map();
|
|
591
|
+
const flatKeys = new Set();
|
|
592
|
+
const rootLabel = rootModuleLabel(repoRoot);
|
|
559
593
|
|
|
560
594
|
for (const file of files) {
|
|
561
595
|
// Go test files belong to the package they exercise. Counting each
|
|
562
596
|
// *_test.go as its own module (the filename under module_roots) splits
|
|
563
597
|
// one package into a source module and a test module.
|
|
564
598
|
if (/_test\.go$/.test(norm(file))) continue;
|
|
565
|
-
const key = moduleKeyForFile(file, cfg.module_roots || []);
|
|
599
|
+
const key = moduleKeyForFile(file, cfg.module_roots || [], rootLabel);
|
|
600
|
+
if (!norm(file).includes("/")) flatKeys.add(key);
|
|
566
601
|
moduleCounts.set(key, (moduleCounts.get(key) || 0) + 1);
|
|
567
602
|
}
|
|
568
603
|
|
|
569
604
|
const modules = [...moduleCounts.entries()]
|
|
570
|
-
.map(([key, fileCount]) => ({
|
|
605
|
+
.map(([key, fileCount]) => ({
|
|
606
|
+
key,
|
|
607
|
+
fileCount,
|
|
608
|
+
...(flatKeys.has(key) ? { flat: true } : {}),
|
|
609
|
+
}))
|
|
571
610
|
.sort((a, b) => b.fileCount - a.fileCount);
|
|
572
611
|
|
|
573
|
-
const apiFiles = files.filter(isNextRoute);
|
|
612
|
+
const apiFiles = files.filter((file) => isNextRoute(file) && !isNonProductPath(file));
|
|
574
613
|
const api = [];
|
|
575
614
|
|
|
576
615
|
// Extract Next.js API routes
|
|
@@ -593,8 +632,9 @@ export async function scanRepo(cfg) {
|
|
|
593
632
|
});
|
|
594
633
|
}
|
|
595
634
|
|
|
596
|
-
// Extract Express.js routes
|
|
635
|
+
// Extract Express.js routes. Tests and examples are not the product API.
|
|
597
636
|
for (const file of files) {
|
|
637
|
+
if (isNonProductPath(file)) continue;
|
|
598
638
|
if (file.endsWith(".ts") || file.endsWith(".js")) {
|
|
599
639
|
const absoluteFile = path.join(repoRoot, file);
|
|
600
640
|
const content = await readFileSafe(absoluteFile);
|
|
@@ -613,7 +653,7 @@ export async function scanRepo(cfg) {
|
|
|
613
653
|
}
|
|
614
654
|
}
|
|
615
655
|
|
|
616
|
-
const pageFiles = files.filter(isNextPage);
|
|
656
|
+
const pageFiles = files.filter((file) => isNextPage(file) && !isNonProductPath(file));
|
|
617
657
|
const pages = pageFiles.map((file) => ({
|
|
618
658
|
file,
|
|
619
659
|
path: routePathFromFile(file),
|
|
@@ -622,6 +662,7 @@ export async function scanRepo(cfg) {
|
|
|
622
662
|
|
|
623
663
|
// Extract React Router routes
|
|
624
664
|
for (const file of files) {
|
|
665
|
+
if (isNonProductPath(file)) continue;
|
|
625
666
|
if (file.endsWith(".tsx") || file.endsWith(".jsx")) {
|
|
626
667
|
const absoluteFile = path.join(repoRoot, file);
|
|
627
668
|
const content = await readFileSafe(absoluteFile);
|
|
@@ -635,6 +676,7 @@ export async function scanRepo(cfg) {
|
|
|
635
676
|
|
|
636
677
|
// Extract Vue Router routes
|
|
637
678
|
for (const file of files) {
|
|
679
|
+
if (isNonProductPath(file)) continue;
|
|
638
680
|
if (file.endsWith(".ts") || file.endsWith(".js") || file.endsWith(".vue")) {
|
|
639
681
|
const absoluteFile = path.join(repoRoot, file);
|
|
640
682
|
const content = await readFileSafe(absoluteFile);
|
package/src/docs/llms-text.js
CHANGED
|
@@ -34,6 +34,7 @@ RepoLens is the npm package \`@chappibunny/repolens\` (Node.js 18+). It is a CLI
|
|
|
34
34
|
- [publish](${REPO_URL}/blob/main/docs/INSTALLATION.md): \`npx @chappibunny/repolens publish\` scans and publishes
|
|
35
35
|
- [badge](${REPO_URL}/blob/main/README.md): \`npx @chappibunny/repolens badge\` prints shields.io code-health JSON, SVG, or markdown
|
|
36
36
|
- [comment](${REPO_URL}/blob/main/README.md): \`npx @chappibunny/repolens comment --base <ref> --head <ref>\` prints an architecture diff
|
|
37
|
+
- [app](${REPO_URL}/blob/main/docs/GITHUB_APP.md): \`npx @chappibunny/repolens app\` self-hosts the GitHub App webhook. \`--manifest\` prints the manifest. No credentials are required to print it
|
|
37
38
|
- [doctor](${REPO_URL}/blob/main/docs/INSTALLATION.md): \`npx @chappibunny/repolens doctor\` validates setup
|
|
38
39
|
- [mcp](${REPO_URL}/blob/main/docs/MCP.md): \`npx -y @chappibunny/repolens mcp\` starts the stdio server
|
|
39
40
|
|
|
@@ -95,6 +96,7 @@ Agent-oriented install steps, including the Cline MCP snippet, are in llms-insta
|
|
|
95
96
|
- \`repolens watch\` regenerates docs on change and exits 2 when nothing matches.
|
|
96
97
|
- \`repolens badge\` prints a shields.io endpoint JSON document for the code-health score (0–100). \`--format svg\` prints a square SVG. \`--format markdown --url https://host/repolens-badge.json\` prints a README snippet. \`--format all --out repolens-badge.json\` writes JSON and SVG. Exit 2 when no JavaScript, TypeScript, Python, or Go files were scored. The score is not reported as 100 when zero files match.
|
|
97
98
|
- \`repolens comment --base <ref> --head <ref>\` compares those commits. It prints markdown locally. In GitHub Actions, with \`GITHUB_TOKEN\`, \`GITHUB_REPOSITORY\`, and a pull_request event, it creates or updates one comment.
|
|
99
|
+
- \`repolens app\` self-hosts a GitHub App webhook. It verifies \`X-Hub-Signature-256\`, posts a Check Run named RepoLens, and updates the sticky architecture comment. \`repolens app --manifest\` prints the manifest and needs no secrets. Listening needs \`REPOLENS_APP_ID\`, \`REPOLENS_WEBHOOK_SECRET\`, and a private key. See docs/GITHUB_APP.md.
|
|
98
100
|
- \`repolens mcp\` starts the stdio MCP server. \`repolens-mcp\` is the same entry.
|
|
99
101
|
|
|
100
102
|
Exit codes: 0 success, 1 runtime error or unknown command, 2 validation failure (bad flags, missing config, empty scan, and the other cases in the README).
|