@chappibunny/repolens 1.15.0 → 1.15.1
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 +9 -0
- package/README.md +7 -7
- package/llms-install.md +1 -0
- package/package.json +1 -1
- package/src/analyzers/complexity-analyzer.js +143 -8
- package/src/help.js +6 -3
- package/src/renderers/renderAnalysis.js +2 -2
- package/src/watch.js +14 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to RepoLens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 1.15.1
|
|
6
|
+
|
|
7
|
+
### Watch and code health
|
|
8
|
+
|
|
9
|
+
- `watch` exits 2 when `scan.include` matches no files, including after `init --quick` in an empty directory. It prints a hint to fix `scan.include` and `module_roots` and does not wait for changes
|
|
10
|
+
- Code health analyzes Python (`.py`, `.pyw`) and Go (`.go`) as well as JavaScript and TypeScript. A Python or Go repository no longer logs `Code health: no source files analyzed` when source files were scanned. Test modules (`test_*.py`, `*_test.py`, `*_test.go`, and `tests/`) are skipped
|
|
11
|
+
|
|
12
|
+
Exit codes stay 0 success, 1 runtime error or unknown command, and 2 validation failure (bad flags, missing config, missing target, doctor failure, 0 files scanned, nothing to watch, `--strict` credentials, feedback without a terminal).
|
|
13
|
+
|
|
5
14
|
## 1.15.0
|
|
6
15
|
|
|
7
16
|
### CLI usability
|
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ RepoLens scans your repository, generates living architecture documentation, and
|
|
|
29
29
|
|
|
30
30
|
## 🎬 Demo
|
|
31
31
|
|
|
32
|
-
> **Try it now** — no installation required. `npx @chappibunny/repolens demo` previews the repository you point it at. It detects common languages (Python, Go, Rust, Java, JavaScript/TypeScript, and others). If the scan matches no source files, demo exits 2 and does not write empty docs.
|
|
32
|
+
> **Try it now** — no installation required. `npx @chappibunny/repolens demo` previews the repository you point it at. It detects common languages (Python, Go, Rust, Java, JavaScript/TypeScript, and others). If the scan matches no source files, demo exits 2 and does not write empty docs. Code health scores JavaScript/TypeScript, Python, and Go.
|
|
33
33
|
|
|
34
34
|
**[See it on real repos](https://chapibunny.github.io/repolens/)** — architecture pages RepoLens generated for Express, FastAPI, Django, Gin, mdBook, and other public projects. Deterministic mode, no API keys.
|
|
35
35
|
|
|
@@ -181,7 +181,7 @@ Step-by-step setup for publishers, AI features, Notion, Confluence, GitHub Wiki,
|
|
|
181
181
|
| `npx @chappibunny/repolens publish --strict` | Exit 2 when a configured publisher is missing credentials |
|
|
182
182
|
| `npx @chappibunny/repolens demo` | Local preview — no API keys. Exits 2 when 0 files match |
|
|
183
183
|
| `npx @chappibunny/repolens doctor` | Validate your setup, including that scan globs match files |
|
|
184
|
-
| `npx @chappibunny/repolens watch` | Auto-regenerate docs on file changes. Exits 2 if
|
|
184
|
+
| `npx @chappibunny/repolens watch` | Auto-regenerate docs on file changes. Exits 2 if `module_roots` are missing or `scan.include` matches no files |
|
|
185
185
|
| `npx @chappibunny/repolens migrate` | Upgrade from v0.3.0 workflows ([details](docs/MIGRATION.md)) |
|
|
186
186
|
| `npx @chappibunny/repolens uninstall` | Remove all RepoLens files (config, docs, workflow) |
|
|
187
187
|
| `npx @chappibunny/repolens uninstall --force` | Remove without confirmation prompt |
|
|
@@ -197,7 +197,7 @@ Step-by-step setup for publishers, AI features, Notion, Confluence, GitHub Wiki,
|
|
|
197
197
|
|---|---|
|
|
198
198
|
| `0` | Success, including a cancelled wizard or help with no other work |
|
|
199
199
|
| `1` | Runtime error, or an unknown command |
|
|
200
|
-
| `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, `publish --strict` is missing publisher credentials, or `feedback` has no terminal |
|
|
200
|
+
| `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), `publish --strict` is missing publisher credentials, or `feedback` has no terminal |
|
|
201
201
|
|
|
202
202
|
`--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.
|
|
203
203
|
|
|
@@ -237,7 +237,7 @@ If `npx` reports `Unknown command: mcp`, an `.npmrc` with `min-release-age` (or
|
|
|
237
237
|
|
|
238
238
|
## GitHub Action
|
|
239
239
|
|
|
240
|
-
`action.yml` is a composite action named **RepoLens Architecture Docs**. It runs the published npm package (`npx --yes @chappibunny/repolens@1.15.
|
|
240
|
+
`action.yml` is a composite action named **RepoLens Architecture Docs**. It runs the published npm package (`npx --yes @chappibunny/repolens@1.15.1`). Inputs are `command` (`publish`, `demo`, or `doctor`), `config`, `node-version`, `pr-comment`, and `version`.
|
|
241
241
|
|
|
242
242
|
```yaml
|
|
243
243
|
name: Architecture docs
|
|
@@ -253,7 +253,7 @@ jobs:
|
|
|
253
253
|
runs-on: ubuntu-latest
|
|
254
254
|
steps:
|
|
255
255
|
- uses: actions/checkout@9f698171ed81b15d1823a05fc7211befd50c8ae0 # v6.0.3
|
|
256
|
-
- uses: CHAPIBUNNY/repolens@v1.15.
|
|
256
|
+
- uses: CHAPIBUNNY/repolens@v1.15.1
|
|
257
257
|
with:
|
|
258
258
|
command: publish
|
|
259
259
|
config: .repolens.yml
|
|
@@ -261,7 +261,7 @@ jobs:
|
|
|
261
261
|
pr-comment: ${{ github.event_name == 'pull_request' && 'true' || 'false' }}
|
|
262
262
|
```
|
|
263
263
|
|
|
264
|
-
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.15.
|
|
264
|
+
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.15.1` has to exist before `uses: CHAPIBUNNY/repolens@v1.15.1` resolves, and `@chappibunny/repolens@1.15.1` has to be on npm before the action can install it.
|
|
265
265
|
|
|
266
266
|
## Add the badge to your repo
|
|
267
267
|
|
|
@@ -281,7 +281,7 @@ Generated docs also link to the [gallery](https://chapibunny.github.io/repolens/
|
|
|
281
281
|
|
|
282
282
|
Published system maps are Unicode diagrams drawn from import edges. RepoLens does not emit Mermaid.
|
|
283
283
|
|
|
284
|
-
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. Layout and counts are in [ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
284
|
+
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. Layout and counts are in [ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
285
285
|
|
|
286
286
|
### System Overview (Technical Profile)
|
|
287
287
|
|
package/llms-install.md
CHANGED
|
@@ -12,3 +12,4 @@
|
|
|
12
12
|
5. Verify: the server should list six tools — `get_system_overview`, `list_modules`, `get_api_surface`, `get_dependency_graph`, `get_architecture_diff`, `generate_docs`. Call `get_system_overview` with no arguments.
|
|
13
13
|
6. If you see `Unknown command: mcp`, an older release was resolved (often an `.npmrc` with `min-release-age`). Use `@chappibunny/repolens@latest`.
|
|
14
14
|
7. CLI (1.15.0): `repolens` with no arguments prints help. `repolens <command> --help` prints that command's flags. `init --quick` writes language-specific globs and Markdown only. `demo` and `publish` exit 2 when zero source files match, instead of writing empty docs. `publish --strict` exits 2 when publisher credentials are missing.
|
|
15
|
+
8. CLI (1.15.1): `watch` exits 2 when `scan.include` matches no files (it does not print `Waiting for changes...` and hang). Code health scores Python and Go as well as JavaScript and TypeScript.
|
package/package.json
CHANGED
|
@@ -6,7 +6,17 @@ import fs from "node:fs/promises";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { info } from "../utils/logger.js";
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const LANGUAGE_BY_EXTENSION = {
|
|
10
|
+
".js": "javascript",
|
|
11
|
+
".mjs": "javascript",
|
|
12
|
+
".cjs": "javascript",
|
|
13
|
+
".ts": "javascript",
|
|
14
|
+
".tsx": "javascript",
|
|
15
|
+
".jsx": "javascript",
|
|
16
|
+
".py": "python",
|
|
17
|
+
".pyw": "python",
|
|
18
|
+
".go": "go",
|
|
19
|
+
};
|
|
10
20
|
|
|
11
21
|
// Skip test files, build outputs, etc.
|
|
12
22
|
const SKIP_PATTERNS = [
|
|
@@ -19,6 +29,10 @@ const SKIP_PATTERNS = [
|
|
|
19
29
|
/\.min\.[jt]s$/,
|
|
20
30
|
/\.d\.ts$/,
|
|
21
31
|
/\.repolens\//,
|
|
32
|
+
/__pycache__\//,
|
|
33
|
+
/(?:^|\/)test_[^/]*\.py$/,
|
|
34
|
+
/_test\.py$/,
|
|
35
|
+
/_test\.go$/,
|
|
22
36
|
];
|
|
23
37
|
|
|
24
38
|
function shouldSkip(filePath) {
|
|
@@ -66,6 +80,100 @@ export function computeComplexity(source) {
|
|
|
66
80
|
return complexity;
|
|
67
81
|
}
|
|
68
82
|
|
|
83
|
+
function blankExceptNewlines(match) {
|
|
84
|
+
return match.replace(/[^\n]/g, " ");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function stripPythonNoise(source) {
|
|
88
|
+
let stripped = source.replace(/"""[\s\S]*?"""/g, blankExceptNewlines);
|
|
89
|
+
stripped = stripped.replace(/'''[\s\S]*?'''/g, blankExceptNewlines);
|
|
90
|
+
stripped = stripped.replace(/"(?:[^"\\\n]|\\.)*"/g, '""');
|
|
91
|
+
stripped = stripped.replace(/'(?:[^'\\\n]|\\.)*'/g, "''");
|
|
92
|
+
stripped = stripped.replace(/#.*$/gm, "");
|
|
93
|
+
return stripped;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function stripGoNoise(source) {
|
|
97
|
+
let stripped = source.replace(/`[^`]*`/g, blankExceptNewlines);
|
|
98
|
+
stripped = stripped.replace(/"(?:[^"\\\n]|\\.)*"/g, '""');
|
|
99
|
+
stripped = stripped.replace(/'(?:[^'\\\n]|\\.)*'/g, "''");
|
|
100
|
+
stripped = stripped.replace(/\/\*[\s\S]*?\*\//g, blankExceptNewlines);
|
|
101
|
+
stripped = stripped.replace(/\/\/.*$/gm, "");
|
|
102
|
+
return stripped;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Cyclomatic complexity for Python. Regex approximation: if, elif, for,
|
|
107
|
+
* while, except, and, or. Comments and strings are ignored.
|
|
108
|
+
*/
|
|
109
|
+
export function computePythonComplexity(source) {
|
|
110
|
+
const stripped = stripPythonNoise(source);
|
|
111
|
+
let complexity = 1;
|
|
112
|
+
const patterns = [
|
|
113
|
+
/\bif\b/g,
|
|
114
|
+
/\belif\b/g,
|
|
115
|
+
/\bfor\b/g,
|
|
116
|
+
/\bwhile\b/g,
|
|
117
|
+
/\bexcept\b/g,
|
|
118
|
+
/\band\b/g,
|
|
119
|
+
/\bor\b/g,
|
|
120
|
+
];
|
|
121
|
+
for (const pattern of patterns) {
|
|
122
|
+
const matches = stripped.match(pattern);
|
|
123
|
+
if (matches) complexity += matches.length;
|
|
124
|
+
}
|
|
125
|
+
return complexity;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Cyclomatic complexity for Go. Regex approximation: if, for, case
|
|
130
|
+
* (switch and select branches), &&, and ||. Comments and strings are ignored.
|
|
131
|
+
*/
|
|
132
|
+
export function computeGoComplexity(source) {
|
|
133
|
+
const stripped = stripGoNoise(source);
|
|
134
|
+
let complexity = 1;
|
|
135
|
+
const patterns = [
|
|
136
|
+
/\bif\b/g,
|
|
137
|
+
/\bfor\b/g,
|
|
138
|
+
/\bcase\b/g,
|
|
139
|
+
/&&/g,
|
|
140
|
+
/\|\|/g,
|
|
141
|
+
];
|
|
142
|
+
for (const pattern of patterns) {
|
|
143
|
+
const matches = stripped.match(pattern);
|
|
144
|
+
if (matches) complexity += matches.length;
|
|
145
|
+
}
|
|
146
|
+
return complexity;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function languageForFile(file) {
|
|
150
|
+
return LANGUAGE_BY_EXTENSION[path.extname(file).toLowerCase()] || null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function complexityForLanguage(language, source) {
|
|
154
|
+
if (language === "python") return computePythonComplexity(source);
|
|
155
|
+
if (language === "go") return computeGoComplexity(source);
|
|
156
|
+
return computeComplexity(source);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function collectFunctions(source, pattern, nameGroup, complexityFn) {
|
|
160
|
+
const functions = [];
|
|
161
|
+
let match;
|
|
162
|
+
while ((match = pattern.exec(source)) !== null) {
|
|
163
|
+
const startLine = source.slice(0, match.index).split("\n").length;
|
|
164
|
+
functions.push({ name: match[nameGroup], line: startLine, index: match.index });
|
|
165
|
+
}
|
|
166
|
+
functions.sort((a, b) => a.index - b.index);
|
|
167
|
+
return functions.map((fn, i) => {
|
|
168
|
+
const end = i + 1 < functions.length ? functions[i + 1].index : source.length;
|
|
169
|
+
return {
|
|
170
|
+
name: fn.name,
|
|
171
|
+
line: fn.line,
|
|
172
|
+
complexity: complexityFn(source.slice(fn.index, end)),
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
69
177
|
/**
|
|
70
178
|
* Extract function-level complexity from source code.
|
|
71
179
|
* Returns per-function complexity for the top-level functions.
|
|
@@ -110,24 +218,50 @@ export function extractFunctions(source) {
|
|
|
110
218
|
return results;
|
|
111
219
|
}
|
|
112
220
|
|
|
221
|
+
/** Python `def` / `async def` names and their approximate complexity. */
|
|
222
|
+
export function extractPythonFunctions(source) {
|
|
223
|
+
return collectFunctions(
|
|
224
|
+
source,
|
|
225
|
+
/^[ \t]*(?:async[ \t]+)?def[ \t]+(\w+)[ \t]*\(/gm,
|
|
226
|
+
1,
|
|
227
|
+
computePythonComplexity,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Go `func Name` and `func (recv) Name` declarations. */
|
|
232
|
+
export function extractGoFunctions(source) {
|
|
233
|
+
return collectFunctions(
|
|
234
|
+
source,
|
|
235
|
+
/^func[ \t]+(?:\([^)\n]*\)[ \t]+)?(\w+)[ \t]*\(/gm,
|
|
236
|
+
1,
|
|
237
|
+
computeGoComplexity,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function functionsForLanguage(language, source) {
|
|
242
|
+
if (language === "python") return extractPythonFunctions(source);
|
|
243
|
+
if (language === "go") return extractGoFunctions(source);
|
|
244
|
+
return extractFunctions(source);
|
|
245
|
+
}
|
|
246
|
+
|
|
113
247
|
/**
|
|
114
|
-
* Analyze complexity for
|
|
248
|
+
* Analyze complexity for JavaScript/TypeScript, Python, and Go files.
|
|
115
249
|
* @param {string[]} files - File paths relative to repoRoot
|
|
116
250
|
* @param {string} repoRoot - Absolute path to repo root
|
|
117
251
|
* @returns {Promise<Object>} Complexity analysis results
|
|
118
252
|
*/
|
|
119
253
|
export async function analyzeComplexity(files, repoRoot) {
|
|
120
|
-
const
|
|
121
|
-
.filter(f =>
|
|
254
|
+
const sourceFiles = files
|
|
255
|
+
.filter(f => languageForFile(f))
|
|
122
256
|
.filter(f => !shouldSkip(f));
|
|
123
257
|
|
|
124
258
|
const result = {
|
|
125
259
|
files: [],
|
|
126
260
|
functions: [],
|
|
127
|
-
filesAnalyzed:
|
|
261
|
+
filesAnalyzed: sourceFiles.length,
|
|
128
262
|
};
|
|
129
263
|
|
|
130
|
-
for (const file of
|
|
264
|
+
for (const file of sourceFiles) {
|
|
131
265
|
let content;
|
|
132
266
|
try {
|
|
133
267
|
content = await fs.readFile(path.join(repoRoot, file), "utf8");
|
|
@@ -135,9 +269,10 @@ export async function analyzeComplexity(files, repoRoot) {
|
|
|
135
269
|
continue;
|
|
136
270
|
}
|
|
137
271
|
|
|
138
|
-
const
|
|
272
|
+
const language = languageForFile(file);
|
|
273
|
+
const fileComplexity = complexityForLanguage(language, content);
|
|
139
274
|
const lineCount = content.split("\n").length;
|
|
140
|
-
const funcs =
|
|
275
|
+
const funcs = functionsForLanguage(language, content);
|
|
141
276
|
|
|
142
277
|
result.files.push({
|
|
143
278
|
file,
|
package/src/help.js
CHANGED
|
@@ -3,7 +3,8 @@ const EXIT_CODES = `Exit codes:
|
|
|
3
3
|
1 runtime error, or an unknown command
|
|
4
4
|
2 invalid flags, missing config, target directory not found,
|
|
5
5
|
a failed doctor check (including 0 files matched),
|
|
6
|
-
demo or publish scanned 0 files, watch has nothing to watch
|
|
6
|
+
demo or publish scanned 0 files, watch has nothing to watch
|
|
7
|
+
(missing module_roots, or scan.include matches no files),
|
|
7
8
|
publish --strict is missing publisher credentials,
|
|
8
9
|
or feedback has no terminal`;
|
|
9
10
|
|
|
@@ -149,8 +150,10 @@ Options:
|
|
|
149
150
|
--verbose Print debug logs
|
|
150
151
|
--help, -h Show this help
|
|
151
152
|
|
|
152
|
-
Watches directories listed in module_roots
|
|
153
|
-
|
|
153
|
+
Watches directories listed in module_roots and regenerates Markdown when
|
|
154
|
+
files change. Exits 2 when none of those directories exist, and when
|
|
155
|
+
scan.include matches no files (including after init --quick in an empty
|
|
156
|
+
directory). It does not wait for changes in that case. Press Ctrl+C to stop
|
|
154
157
|
when a watch is running.
|
|
155
158
|
|
|
156
159
|
${EXIT_CODES}
|
|
@@ -593,7 +593,7 @@ export function renderCodeHealth(healthResult) {
|
|
|
593
593
|
lines.push("");
|
|
594
594
|
lines.push("| Factor | Weight | How It's Measured |");
|
|
595
595
|
lines.push("|--------|--------|-------------------|");
|
|
596
|
-
lines.push("| Complexity | –1/pt above 10, –2/pt above 30 | Cyclomatic complexity
|
|
596
|
+
lines.push("| Complexity | –1/pt above 10, –2/pt above 30 | Cyclomatic complexity for JavaScript/TypeScript, Python, and Go (regex, not AST) |");
|
|
597
597
|
lines.push("| Function complexity | –2/pt above 8 per function | Highest complexity function in the file |");
|
|
598
598
|
lines.push("| Coupling | –1/5 units above 20 | Fan-in × Fan-out (import connections) |");
|
|
599
599
|
lines.push("| Documentation | –10 if <50%, –5 if <80% | JSDoc/TSDoc coverage of exports |");
|
|
@@ -602,7 +602,7 @@ export function renderCodeHealth(healthResult) {
|
|
|
602
602
|
|
|
603
603
|
lines.push("---");
|
|
604
604
|
lines.push("");
|
|
605
|
-
lines.push("*Generated by RepoLens code health analysis. Complexity is regex-based and may differ from AST-based tools. Scores are relative indicators, not absolute quality measures.*");
|
|
605
|
+
lines.push("*Generated by RepoLens code health analysis. Complexity covers JavaScript/TypeScript, Python, and Go. It is regex-based and may differ from AST-based tools. Scores are relative indicators, not absolute quality measures.*");
|
|
606
606
|
lines.push("");
|
|
607
607
|
|
|
608
608
|
return lines.join("\n");
|
package/src/watch.js
CHANGED
|
@@ -37,9 +37,6 @@ async function rebuild(configPath) {
|
|
|
37
37
|
* @param {string} configPath - Resolved path to .repolens.yml
|
|
38
38
|
*/
|
|
39
39
|
export async function runWatch(configPath) {
|
|
40
|
-
info("Starting watch mode (Markdown only)...");
|
|
41
|
-
info("Press Ctrl+C to stop.\n");
|
|
42
|
-
|
|
43
40
|
const cfg = await loadConfig(configPath);
|
|
44
41
|
const repoRoot = cfg.__repoRoot || process.cwd();
|
|
45
42
|
const moduleRoots = cfg.module_roots || ["src", "app", "lib"];
|
|
@@ -62,6 +59,20 @@ export async function runWatch(configPath) {
|
|
|
62
59
|
return { ok: false, message: hint };
|
|
63
60
|
}
|
|
64
61
|
|
|
62
|
+
// A root can exist (including "." after init --quick in an empty directory)
|
|
63
|
+
// while scan.include still matches nothing. Do not wait forever in that case.
|
|
64
|
+
const scan = await scanRepo(cfg);
|
|
65
|
+
if ((scan?.filesCount || 0) === 0) {
|
|
66
|
+
const patterns = (cfg.scan?.include || []).join(", ") || "(none)";
|
|
67
|
+
const hint = `No files matched scan.include (${patterns}). Nothing to watch. Check scan.include and module_roots in .repolens.yml. Add source files or update the globs, then run watch again.`;
|
|
68
|
+
warn(hint);
|
|
69
|
+
error(hint);
|
|
70
|
+
return { ok: false, message: hint };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
info("Starting watch mode (Markdown only)...");
|
|
74
|
+
info("Press Ctrl+C to stop.\n");
|
|
75
|
+
|
|
65
76
|
// Initial build only when there is something to watch.
|
|
66
77
|
await rebuild(configPath);
|
|
67
78
|
|