@chappibunny/repolens 0.9.0 → 1.0.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 +31 -0
- package/README.md +38 -25
- package/package.json +1 -1
- package/src/ai/provider.js +13 -8
- package/src/cli.js +35 -15
- package/src/core/config-schema.js +35 -11
- package/src/init.js +2 -2
- package/src/migrate.js +2 -2
- package/src/publishers/index.js +1 -1
- package/src/publishers/notion.js +2 -2
- package/src/publishers/publish.js +2 -1
- package/src/utils/rate-limit.js +4 -2
- package/src/utils/telemetry.js +6 -2
- package/src/utils/update-check.js +2 -2
- package/src/utils/validate.js +32 -23
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to RepoLens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 1.0.0
|
|
6
|
+
|
|
7
|
+
### 🎉 Stable Release
|
|
8
|
+
|
|
9
|
+
RepoLens v1.0.0 marks the first stable release with a frozen public API. All CLI commands, configuration schema, and plugin interfaces are now covered by semantic versioning guarantees. See [STABILITY.md](STABILITY.md) for the full contract.
|
|
10
|
+
|
|
11
|
+
### 🐛 Bug Fixes
|
|
12
|
+
- **Doctor false-success**: `repolens doctor` now correctly exits with code 2 when `runDoctor()` reports failures (previously always printed "validation passed")
|
|
13
|
+
- **Feedback exit code**: `repolens feedback` now exits with code 1 when feedback fails to send (previously exited 0)
|
|
14
|
+
- **Unknown flags**: `repolens --unknown-flag` now prints an error instead of silently running publish
|
|
15
|
+
- **scan.ignore security bypass**: Security validator now checks `scan.ignore` patterns (was incorrectly checking non-existent `scan.exclude`)
|
|
16
|
+
- **Domains type mismatch**: Both validators now expect `domains` as an object (was array in security validator, object in schema validator)
|
|
17
|
+
- **Plugin publisher crash isolation**: Plugin publisher errors are now caught and logged instead of crashing the pipeline
|
|
18
|
+
|
|
19
|
+
### ✅ Config Stability
|
|
20
|
+
- `configVersion: 1` is now **required** (was optional in schema validator)
|
|
21
|
+
- Added `confluence` config section validation (branches array)
|
|
22
|
+
- Added `ai.temperature` range validation (0–2)
|
|
23
|
+
- Added `ai.max_tokens` range validation (>0)
|
|
24
|
+
- AI config values from `.repolens.yml` are now used as fallbacks when env vars are not set
|
|
25
|
+
|
|
26
|
+
### 🔧 Improvements
|
|
27
|
+
- Standardized exit codes: `EXIT_SUCCESS=0`, `EXIT_ERROR=1`, `EXIT_VALIDATION=2` as named constants
|
|
28
|
+
- Replaced all `console.log`/`console.warn` in production code with logger utilities
|
|
29
|
+
- Removed stale "v0.4.0" references from CLI help text and migrate command
|
|
30
|
+
- Sentry DSN moved to `REPOLENS_SENTRY_DSN` env var (with backwards-compatible default)
|
|
31
|
+
- Hardcoded email in `init` scaffolding replaced with `your-email@example.com` placeholder
|
|
32
|
+
|
|
33
|
+
### 📄 Documentation
|
|
34
|
+
- Added [STABILITY.md](STABILITY.md): Complete public API contract (CLI, config schema, plugin interface, exit codes, env vars)
|
|
35
|
+
|
|
5
36
|
## 0.9.0
|
|
6
37
|
|
|
7
38
|
### ✨ New Features
|
package/README.md
CHANGED
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
|
|
17
17
|
AI-assisted documentation intelligence system that generates architecture docs for engineers AND readable system docs for stakeholders
|
|
18
18
|
|
|
19
|
-
**Current Status**:
|
|
19
|
+
**Current Status**: v1.0.0 — Stable Release
|
|
20
20
|
|
|
21
21
|
RepoLens automatically generates and maintains living architecture documentation by analyzing your repository structure, extracting meaningful insights from your package.json, and creating visual dependency graphs. Run it once, or let it auto-update on every push.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
The CLI, configuration schema, and plugin interface are **stable** as of v1.0. See [STABILITY.md](STABILITY.md) for the full API contract and semver guarantees.
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
@@ -49,7 +49,7 @@ For Confluence:
|
|
|
49
49
|
```bash
|
|
50
50
|
# Edit .env and add:
|
|
51
51
|
CONFLUENCE_URL=https://your-company.atlassian.net/wiki
|
|
52
|
-
CONFLUENCE_EMAIL=
|
|
52
|
+
CONFLUENCE_EMAIL=your-email@example.com
|
|
53
53
|
CONFLUENCE_API_TOKEN=your-token
|
|
54
54
|
CONFLUENCE_SPACE_KEY=DOCS
|
|
55
55
|
```
|
|
@@ -137,6 +137,8 @@ RepoLens automatically detects:
|
|
|
137
137
|
✅ **TypeScript Type Graph** - Map interfaces, classes, and type relationships (NEW in v0.8.0)
|
|
138
138
|
✅ **Dependency Graph** - Import analysis with circular dependency detection (NEW in v0.8.0)
|
|
139
139
|
✅ **Architecture Drift** - Track structural changes against a baseline (NEW in v0.8.0)
|
|
140
|
+
✅ **Plugin System** - Extend with custom renderers, publishers, and hooks (NEW in v0.9.0)
|
|
141
|
+
✅ **Stable API** - CLI, config, and plugin interface frozen with semver guarantees (v1.0.0)
|
|
140
142
|
|
|
141
143
|
---
|
|
142
144
|
|
|
@@ -228,7 +230,7 @@ npm link
|
|
|
228
230
|
Install from a specific version:
|
|
229
231
|
|
|
230
232
|
```bash
|
|
231
|
-
npm install https://github.com/CHAPIBUNNY/repolens/releases/download/
|
|
233
|
+
npm install https://github.com/CHAPIBUNNY/repolens/releases/download/v1.0.0/chappibunny-repolens-1.0.0.tgz
|
|
232
234
|
```
|
|
233
235
|
</details>
|
|
234
236
|
|
|
@@ -318,7 +320,7 @@ Maximum visibility: Notion for async collaboration, Confluence for enterprise do
|
|
|
318
320
|
**3.1: Choose an AI Provider**
|
|
319
321
|
|
|
320
322
|
RepoLens works with any OpenAI-compatible API:
|
|
321
|
-
- **OpenAI** (gpt-
|
|
323
|
+
- **OpenAI** (gpt-5-mini, gpt-5.4, gpt-5-nano)
|
|
322
324
|
- **Anthropic Claude** (via API gateway)
|
|
323
325
|
- **Azure OpenAI** (enterprise deployments)
|
|
324
326
|
- **Local Models** (Ollama, LM Studio, etc.)
|
|
@@ -333,7 +335,7 @@ REPOLENS_AI_API_KEY=sk-xxxxxxxxxxxxx
|
|
|
333
335
|
|
|
334
336
|
# Optional: Customize provider
|
|
335
337
|
REPOLENS_AI_BASE_URL=https://api.openai.com/v1
|
|
336
|
-
REPOLENS_AI_MODEL=gpt-
|
|
338
|
+
REPOLENS_AI_MODEL=gpt-5-mini
|
|
337
339
|
REPOLENS_AI_TEMPERATURE=0.3
|
|
338
340
|
REPOLENS_AI_MAX_TOKENS=2000
|
|
339
341
|
```
|
|
@@ -356,7 +358,7 @@ features:
|
|
|
356
358
|
change_impact: true # Architecture diff with context
|
|
357
359
|
```
|
|
358
360
|
|
|
359
|
-
**Cost Estimates** (with gpt-
|
|
361
|
+
**Cost Estimates** (with gpt-5-mini):
|
|
360
362
|
- Small repo (<50 files): $0.10-$0.30 per run
|
|
361
363
|
- Medium repo (50-200 files): $0.30-$0.80 per run
|
|
362
364
|
- Large repo (200+ files): $0.80-$2.00 per run
|
|
@@ -428,7 +430,7 @@ If using the Confluence publisher:
|
|
|
428
430
|
Create `.env` in your project root:
|
|
429
431
|
```bash
|
|
430
432
|
CONFLUENCE_URL=https://your-company.atlassian.net/wiki
|
|
431
|
-
CONFLUENCE_EMAIL=
|
|
433
|
+
CONFLUENCE_EMAIL=your-email@example.com
|
|
432
434
|
CONFLUENCE_API_TOKEN=your-api-token-here
|
|
433
435
|
CONFLUENCE_SPACE_KEY=DOCS
|
|
434
436
|
CONFLUENCE_PARENT_PAGE_ID=123456789 # Optional
|
|
@@ -449,7 +451,7 @@ Add as repository secrets:
|
|
|
449
451
|
2. Click **"New repository secret"**
|
|
450
452
|
3. Add:
|
|
451
453
|
- Name: `CONFLUENCE_URL`, Value: `https://your-company.atlassian.net/wiki`
|
|
452
|
-
- Name: `CONFLUENCE_EMAIL`, Value: `
|
|
454
|
+
- Name: `CONFLUENCE_EMAIL`, Value: `your-email@example.com`
|
|
453
455
|
- Name: `CONFLUENCE_API_TOKEN`, Value: `your-token`
|
|
454
456
|
- Name: `CONFLUENCE_SPACE_KEY`, Value: `DOCS`
|
|
455
457
|
- Name: `CONFLUENCE_PARENT_PAGE_ID`, Value: `123456789` (optional)
|
|
@@ -939,10 +941,11 @@ features:
|
|
|
939
941
|
|
|
940
942
|
| Field | Type | Required | Description |
|
|
941
943
|
|-------|------|----------|-------------|
|
|
942
|
-
| `configVersion` | number |
|
|
944
|
+
| `configVersion` | number | **Yes** | Schema version (must be `1`) |
|
|
943
945
|
| `project.name` | string | Yes | Project name |
|
|
944
946
|
| `project.docs_title_prefix` | string | No | Prefix for documentation titles (default: project name) |
|
|
945
|
-
| `publishers` | array | Yes | Output targets: `notion`, `confluence`, `markdown` |
|
|
947
|
+
| `publishers` | array | Yes | Output targets: `notion`, `confluence`, `markdown` (+ plugin publishers) |
|
|
948
|
+
| `plugins` | array | No | Plugin paths or npm package names |
|
|
946
949
|
| `notion.branches` | array | No | Branch whitelist for Notion publishing. Supports globs. |
|
|
947
950
|
| `notion.includeBranchInTitle` | boolean | No | Add `[branch-name]` to titles (default: `true`) |
|
|
948
951
|
| `confluence.branches` | array | No | Branch whitelist for Confluence publishing. Supports globs. |
|
|
@@ -956,7 +959,11 @@ features:
|
|
|
956
959
|
| `scan.ignore` | array | Yes | Glob patterns to exclude |
|
|
957
960
|
| `module_roots` | array | No | Root directories for module detection |
|
|
958
961
|
| `outputs.pages` | array | Yes | Documentation pages to generate |
|
|
959
|
-
| `features` | object | No |
|
|
962
|
+
| `features` | object | No | Feature flags (boolean values) |
|
|
963
|
+
| `ai.enabled` | boolean | No | Enable AI-powered documentation |
|
|
964
|
+
| `ai.mode` | string | No | AI mode: `hybrid`, `full`, or `off` |
|
|
965
|
+
| `ai.temperature` | number | No | Generation temperature (0\u20132) |
|
|
966
|
+
| `ai.max_tokens` | number | No | Max tokens per request (>0) |
|
|
960
967
|
|
|
961
968
|
---
|
|
962
969
|
|
|
@@ -1081,7 +1088,7 @@ Simulates the full user installation experience:
|
|
|
1081
1088
|
npm pack
|
|
1082
1089
|
|
|
1083
1090
|
# Install globally from tarball
|
|
1084
|
-
npm install -g chappibunny-repolens-0.
|
|
1091
|
+
npm install -g chappibunny-repolens-1.0.0.tgz
|
|
1085
1092
|
|
|
1086
1093
|
# Verify
|
|
1087
1094
|
repolens --version
|
|
@@ -1135,6 +1142,9 @@ repolens/
|
|
|
1135
1142
|
│ │ └── discord.js # Discord webhook notifications
|
|
1136
1143
|
│ ├── delivery/
|
|
1137
1144
|
│ │ └── comment.js # PR comment delivery
|
|
1145
|
+
│ ├── plugins/
|
|
1146
|
+
│ │ ├── loader.js # Plugin resolution and dynamic import
|
|
1147
|
+
│ │ └── manager.js # Plugin registry and lifecycle orchestration
|
|
1138
1148
|
│ └── utils/
|
|
1139
1149
|
│ ├── logger.js # Logging utilities
|
|
1140
1150
|
│ ├── retry.js # API retry logic (exponential backoff)
|
|
@@ -1150,6 +1160,7 @@ repolens/
|
|
|
1150
1160
|
├── .repolens.yml # Dogfooding config
|
|
1151
1161
|
├── package.json
|
|
1152
1162
|
├── CHANGELOG.md
|
|
1163
|
+
├── STABILITY.md
|
|
1153
1164
|
├── RELEASE.md
|
|
1154
1165
|
└── ROADMAP.md
|
|
1155
1166
|
```
|
|
@@ -1163,13 +1174,13 @@ RepoLens uses automated GitHub Actions releases.
|
|
|
1163
1174
|
### Creating a Release
|
|
1164
1175
|
|
|
1165
1176
|
```bash
|
|
1166
|
-
# Patch version (0.
|
|
1177
|
+
# Patch version (1.0.0 → 1.0.1) - Bug fixes
|
|
1167
1178
|
npm run release:patch
|
|
1168
1179
|
|
|
1169
|
-
# Minor version (0.
|
|
1180
|
+
# Minor version (1.0.0 → 1.1.0) - New features
|
|
1170
1181
|
npm run release:minor
|
|
1171
1182
|
|
|
1172
|
-
# Major version (0.
|
|
1183
|
+
# Major version (1.0.0 → 2.0.0) - Breaking changes
|
|
1173
1184
|
npm run release:major
|
|
1174
1185
|
|
|
1175
1186
|
# Push the tag to trigger workflow
|
|
@@ -1189,24 +1200,23 @@ See [RELEASE.md](./RELEASE.md) for detailed workflow.
|
|
|
1189
1200
|
|
|
1190
1201
|
## 🤝 Contributing
|
|
1191
1202
|
|
|
1192
|
-
RepoLens is currently in early access. v1.0 will open for community contributions.
|
|
1193
|
-
|
|
1194
1203
|
**Ways to help:**
|
|
1195
1204
|
- **Try it out**: Install and use in your projects
|
|
1196
1205
|
- **Report issues**: Share bugs, edge cases, or UX friction
|
|
1197
1206
|
- **Request features**: Tell us what's missing
|
|
1198
|
-
- **
|
|
1207
|
+
- **Build plugins**: Extend RepoLens with custom renderers and publishers
|
|
1208
|
+
- **Share feedback**: `repolens feedback`
|
|
1199
1209
|
|
|
1200
1210
|
---
|
|
1201
1211
|
|
|
1202
|
-
## 🗺️ Roadmap
|
|
1212
|
+
## 🗺️ Roadmap
|
|
1203
1213
|
|
|
1204
|
-
**Current Status:**
|
|
1214
|
+
**Current Status:** v1.0.0 — Stable Release
|
|
1205
1215
|
|
|
1206
|
-
###
|
|
1216
|
+
### v1.0 — Complete ✅
|
|
1207
1217
|
|
|
1208
1218
|
- [x] CLI commands: `init`, `doctor`, `publish`, `migrate`, `watch`, `feedback`, `version`, `help`
|
|
1209
|
-
- [x] Config schema v1 with validation
|
|
1219
|
+
- [x] Config schema v1 with validation (frozen)
|
|
1210
1220
|
- [x] Auto-discovery of `.repolens.yml`
|
|
1211
1221
|
- [x] Publishers: Notion + Confluence + Markdown
|
|
1212
1222
|
- [x] Branch-aware publishing with filtering
|
|
@@ -1235,11 +1245,14 @@ RepoLens is currently in early access. v1.0 will open for community contribution
|
|
|
1235
1245
|
- [x] Dependency graph with circular dependency detection
|
|
1236
1246
|
- [x] Architecture drift detection (baseline comparison)
|
|
1237
1247
|
- [x] Plugin system for custom renderers and publishers
|
|
1248
|
+
- [x] Stability audit: CLI, config schema, and plugin interface frozen
|
|
1249
|
+
- [x] [STABILITY.md](STABILITY.md): Public API contract with semver guarantees
|
|
1238
1250
|
|
|
1239
|
-
###
|
|
1251
|
+
### Future
|
|
1240
1252
|
|
|
1241
|
-
- [ ] Stability audit: freeze CLI flags and config schema
|
|
1242
1253
|
- [ ] Additional publishers (GitHub Wiki, Obsidian)
|
|
1254
|
+
- [ ] VS Code extension
|
|
1255
|
+
- [ ] GitHub App
|
|
1243
1256
|
|
|
1244
1257
|
See [ROADMAP.md](./ROADMAP.md) for detailed planning.
|
|
1245
1258
|
|
package/package.json
CHANGED
package/src/ai/provider.js
CHANGED
|
@@ -7,9 +7,10 @@ const DEFAULT_TIMEOUT_MS = 60000;
|
|
|
7
7
|
const DEFAULT_TEMPERATURE = 0.2;
|
|
8
8
|
const DEFAULT_MAX_TOKENS = 2500;
|
|
9
9
|
|
|
10
|
-
export async function generateText({ system, user, temperature, maxTokens }) {
|
|
11
|
-
// Check if AI is enabled
|
|
12
|
-
const
|
|
10
|
+
export async function generateText({ system, user, temperature, maxTokens, config }) {
|
|
11
|
+
// Check if AI is enabled (env var takes precedence, then config)
|
|
12
|
+
const aiConfig = config?.ai || {};
|
|
13
|
+
const enabled = process.env.REPOLENS_AI_ENABLED === "true" || aiConfig.enabled === true;
|
|
13
14
|
|
|
14
15
|
if (!enabled) {
|
|
15
16
|
return {
|
|
@@ -19,13 +20,17 @@ export async function generateText({ system, user, temperature, maxTokens }) {
|
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
// Get provider configuration
|
|
23
|
+
// Get provider configuration (env vars take precedence, then config, then defaults)
|
|
23
24
|
const provider = process.env.REPOLENS_AI_PROVIDER || "openai_compatible";
|
|
24
25
|
const baseUrl = process.env.REPOLENS_AI_BASE_URL;
|
|
25
26
|
const apiKey = process.env.REPOLENS_AI_API_KEY;
|
|
26
|
-
const model = process.env.REPOLENS_AI_MODEL || "gpt-
|
|
27
|
+
const model = process.env.REPOLENS_AI_MODEL || "gpt-5-mini";
|
|
27
28
|
const timeoutMs = parseInt(process.env.REPOLENS_AI_TIMEOUT_MS || DEFAULT_TIMEOUT_MS);
|
|
28
29
|
|
|
30
|
+
// Use config values as fallback for temperature/maxTokens
|
|
31
|
+
const resolvedTemp = temperature ?? aiConfig.temperature ?? DEFAULT_TEMPERATURE;
|
|
32
|
+
const resolvedMaxTokens = maxTokens ?? aiConfig.max_tokens ?? DEFAULT_MAX_TOKENS;
|
|
33
|
+
|
|
29
34
|
// Validate configuration
|
|
30
35
|
if (!apiKey) {
|
|
31
36
|
warn("REPOLENS_AI_API_KEY not set. AI features disabled.");
|
|
@@ -47,8 +52,8 @@ export async function generateText({ system, user, temperature, maxTokens }) {
|
|
|
47
52
|
model,
|
|
48
53
|
system,
|
|
49
54
|
user,
|
|
50
|
-
temperature:
|
|
51
|
-
maxTokens:
|
|
55
|
+
temperature: resolvedTemp,
|
|
56
|
+
maxTokens: resolvedMaxTokens,
|
|
52
57
|
timeoutMs
|
|
53
58
|
});
|
|
54
59
|
|
|
@@ -129,7 +134,7 @@ export function getAIConfig() {
|
|
|
129
134
|
return {
|
|
130
135
|
enabled: isAIEnabled(),
|
|
131
136
|
provider: process.env.REPOLENS_AI_PROVIDER || "openai_compatible",
|
|
132
|
-
model: process.env.REPOLENS_AI_MODEL || "gpt-
|
|
137
|
+
model: process.env.REPOLENS_AI_MODEL || "gpt-5-mini",
|
|
133
138
|
hasApiKey: !!process.env.REPOLENS_AI_API_KEY,
|
|
134
139
|
temperature: parseFloat(process.env.REPOLENS_AI_TEMPERATURE || DEFAULT_TEMPERATURE),
|
|
135
140
|
maxTokens: parseInt(process.env.REPOLENS_AI_MAX_TOKENS || DEFAULT_MAX_TOKENS)
|
package/src/cli.js
CHANGED
|
@@ -41,6 +41,11 @@ import {
|
|
|
41
41
|
} from "./utils/telemetry.js";
|
|
42
42
|
import { createInterface } from "node:readline";
|
|
43
43
|
|
|
44
|
+
// Standardized exit codes
|
|
45
|
+
const EXIT_SUCCESS = 0;
|
|
46
|
+
const EXIT_ERROR = 1;
|
|
47
|
+
const EXIT_VALIDATION = 2;
|
|
48
|
+
|
|
44
49
|
function formatDuration(ms) {
|
|
45
50
|
if (ms < 1000) return `${ms}ms`;
|
|
46
51
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
@@ -136,7 +141,7 @@ Usage:
|
|
|
136
141
|
Commands:
|
|
137
142
|
init Scaffold RepoLens files in your repository
|
|
138
143
|
doctor Validate your RepoLens setup
|
|
139
|
-
migrate Upgrade workflow files to
|
|
144
|
+
migrate Upgrade workflow files to current format
|
|
140
145
|
publish Scan, render, and publish documentation
|
|
141
146
|
watch Watch for file changes and regenerate docs
|
|
142
147
|
feedback Send feedback to the RepoLens team
|
|
@@ -213,7 +218,7 @@ async function main() {
|
|
|
213
218
|
error("Failed to initialize RepoLens:");
|
|
214
219
|
error(err.message);
|
|
215
220
|
await closeTelemetry();
|
|
216
|
-
process.exit(
|
|
221
|
+
process.exit(EXIT_ERROR);
|
|
217
222
|
}
|
|
218
223
|
return;
|
|
219
224
|
}
|
|
@@ -225,10 +230,16 @@ async function main() {
|
|
|
225
230
|
|
|
226
231
|
const timer = startTimer("doctor");
|
|
227
232
|
try {
|
|
228
|
-
await runDoctor(targetDir);
|
|
233
|
+
const result = await runDoctor(targetDir);
|
|
229
234
|
const duration = stopTimer(timer);
|
|
230
|
-
info("✓ RepoLens validation passed");
|
|
231
235
|
|
|
236
|
+
if (result && result.ok === false) {
|
|
237
|
+
trackUsage("doctor", "failure", { duration });
|
|
238
|
+
await closeTelemetry();
|
|
239
|
+
process.exit(EXIT_VALIDATION);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
info("✓ RepoLens validation passed");
|
|
232
243
|
trackUsage("doctor", "success", { duration });
|
|
233
244
|
await closeTelemetry();
|
|
234
245
|
} catch (err) {
|
|
@@ -238,7 +249,7 @@ async function main() {
|
|
|
238
249
|
error("Validation failed:");
|
|
239
250
|
error(err.message);
|
|
240
251
|
await closeTelemetry();
|
|
241
|
-
process.exit(
|
|
252
|
+
process.exit(EXIT_VALIDATION);
|
|
242
253
|
}
|
|
243
254
|
return;
|
|
244
255
|
}
|
|
@@ -267,7 +278,7 @@ async function main() {
|
|
|
267
278
|
error("Migration failed:");
|
|
268
279
|
error(err.message);
|
|
269
280
|
await closeTelemetry();
|
|
270
|
-
process.exit(
|
|
281
|
+
process.exit(EXIT_ERROR);
|
|
271
282
|
}
|
|
272
283
|
return;
|
|
273
284
|
}
|
|
@@ -280,13 +291,20 @@ async function main() {
|
|
|
280
291
|
info(`Using config: ${configPath}`);
|
|
281
292
|
} catch (err) {
|
|
282
293
|
error(err.message);
|
|
283
|
-
process.exit(
|
|
294
|
+
process.exit(EXIT_VALIDATION);
|
|
284
295
|
}
|
|
285
296
|
await runWatch(configPath);
|
|
286
297
|
return;
|
|
287
298
|
}
|
|
288
299
|
|
|
289
|
-
|
|
300
|
+
// Reject unknown flags/commands before falling through to publish
|
|
301
|
+
if (command && command.startsWith("--") && !["--help", "-h", "--version", "-v"].includes(command)) {
|
|
302
|
+
error(`Unknown option: ${command}`);
|
|
303
|
+
error("Run 'repolens help' for usage information.");
|
|
304
|
+
process.exit(EXIT_ERROR);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (command === "publish" || !command) {
|
|
290
308
|
await printBanner();
|
|
291
309
|
|
|
292
310
|
const commandTimer = startTimer("publish");
|
|
@@ -299,7 +317,7 @@ async function main() {
|
|
|
299
317
|
} catch (err) {
|
|
300
318
|
stopTimer(commandTimer);
|
|
301
319
|
error(err.message);
|
|
302
|
-
process.exit(
|
|
320
|
+
process.exit(EXIT_VALIDATION);
|
|
303
321
|
}
|
|
304
322
|
|
|
305
323
|
let cfg, scan;
|
|
@@ -312,7 +330,7 @@ async function main() {
|
|
|
312
330
|
trackUsage("publish", "failure", { step: "config-load" });
|
|
313
331
|
error(formatError("CONFIG_VALIDATION_FAILED", err));
|
|
314
332
|
await closeTelemetry();
|
|
315
|
-
process.exit(
|
|
333
|
+
process.exit(EXIT_VALIDATION);
|
|
316
334
|
}
|
|
317
335
|
|
|
318
336
|
// Load plugins
|
|
@@ -342,7 +360,7 @@ async function main() {
|
|
|
342
360
|
: err.message?.includes("limit") ? "SCAN_TOO_MANY_FILES" : null;
|
|
343
361
|
error(code ? formatError(code, err) : `Failed to scan repository:\n ${err.message}`);
|
|
344
362
|
await closeTelemetry();
|
|
345
|
-
process.exit(
|
|
363
|
+
process.exit(EXIT_ERROR);
|
|
346
364
|
}
|
|
347
365
|
|
|
348
366
|
const rawDiff = getGitDiff("origin/main");
|
|
@@ -399,7 +417,7 @@ async function main() {
|
|
|
399
417
|
error("Failed to publish documentation:");
|
|
400
418
|
error(err.message);
|
|
401
419
|
await closeTelemetry();
|
|
402
|
-
process.exit(
|
|
420
|
+
process.exit(EXIT_ERROR);
|
|
403
421
|
}
|
|
404
422
|
|
|
405
423
|
await closeTelemetry();
|
|
@@ -423,7 +441,7 @@ async function main() {
|
|
|
423
441
|
if (!message.trim()) {
|
|
424
442
|
error("Feedback message cannot be empty.");
|
|
425
443
|
await closeTelemetry();
|
|
426
|
-
process.exit(
|
|
444
|
+
process.exit(EXIT_ERROR);
|
|
427
445
|
}
|
|
428
446
|
|
|
429
447
|
info("\nSending feedback...");
|
|
@@ -437,6 +455,8 @@ async function main() {
|
|
|
437
455
|
info("✓ Thank you! Your feedback has been sent.");
|
|
438
456
|
} else {
|
|
439
457
|
error("Failed to send feedback. Please try again later.");
|
|
458
|
+
await closeTelemetry();
|
|
459
|
+
process.exit(EXIT_ERROR);
|
|
440
460
|
}
|
|
441
461
|
} catch (err) {
|
|
442
462
|
rl.close();
|
|
@@ -451,7 +471,7 @@ async function main() {
|
|
|
451
471
|
|
|
452
472
|
error(`Unknown command: ${command}`);
|
|
453
473
|
error("Available commands: init, doctor, migrate, publish, watch, feedback, version, help");
|
|
454
|
-
process.exit(
|
|
474
|
+
process.exit(EXIT_ERROR);
|
|
455
475
|
}
|
|
456
476
|
|
|
457
477
|
main().catch(async (err) => {
|
|
@@ -481,5 +501,5 @@ main().catch(async (err) => {
|
|
|
481
501
|
}
|
|
482
502
|
|
|
483
503
|
await closeTelemetry();
|
|
484
|
-
process.exit(
|
|
504
|
+
process.exit(EXIT_ERROR);
|
|
485
505
|
});
|
|
@@ -39,17 +39,17 @@ class ValidationError extends Error {
|
|
|
39
39
|
export function validateConfig(config) {
|
|
40
40
|
const errors = [];
|
|
41
41
|
|
|
42
|
-
// Check schema version
|
|
43
|
-
if (config.configVersion
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
// Check schema version (required for v1.0+)
|
|
43
|
+
if (config.configVersion === undefined || config.configVersion === null) {
|
|
44
|
+
errors.push("Missing required field: configVersion (must be 1)");
|
|
45
|
+
} else if (typeof config.configVersion !== "number") {
|
|
46
|
+
errors.push("configVersion must be a number");
|
|
47
|
+
} else if (config.configVersion > CURRENT_SCHEMA_VERSION) {
|
|
48
|
+
errors.push(
|
|
49
|
+
`Config schema version ${config.configVersion} is not supported. ` +
|
|
50
|
+
`This version of RepoLens supports schema version ${CURRENT_SCHEMA_VERSION}. ` +
|
|
51
|
+
`Please upgrade RepoLens or downgrade your config.`
|
|
52
|
+
);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// Validate project section
|
|
@@ -173,6 +173,26 @@ export function validateConfig(config) {
|
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
// Validate Confluence configuration (optional)
|
|
177
|
+
if (config.confluence !== undefined) {
|
|
178
|
+
if (typeof config.confluence !== "object" || Array.isArray(config.confluence)) {
|
|
179
|
+
errors.push("confluence must be an object");
|
|
180
|
+
} else {
|
|
181
|
+
// Validate branches filter
|
|
182
|
+
if (config.confluence.branches !== undefined) {
|
|
183
|
+
if (!Array.isArray(config.confluence.branches)) {
|
|
184
|
+
errors.push("confluence.branches must be an array");
|
|
185
|
+
} else {
|
|
186
|
+
config.confluence.branches.forEach((branch, idx) => {
|
|
187
|
+
if (typeof branch !== "string") {
|
|
188
|
+
errors.push(`confluence.branches[${idx}] must be a string`);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
176
196
|
// Validate Discord configuration (optional)
|
|
177
197
|
if (config.discord !== undefined) {
|
|
178
198
|
if (typeof config.discord !== "object" || Array.isArray(config.discord)) {
|
|
@@ -241,9 +261,13 @@ export function validateConfig(config) {
|
|
|
241
261
|
}
|
|
242
262
|
if (config.ai.temperature !== undefined && typeof config.ai.temperature !== "number") {
|
|
243
263
|
errors.push("ai.temperature must be a number");
|
|
264
|
+
} else if (typeof config.ai.temperature === "number" && (config.ai.temperature < 0 || config.ai.temperature > 2)) {
|
|
265
|
+
errors.push("ai.temperature must be between 0 and 2");
|
|
244
266
|
}
|
|
245
267
|
if (config.ai.max_tokens !== undefined && typeof config.ai.max_tokens !== "number") {
|
|
246
268
|
errors.push("ai.max_tokens must be a number");
|
|
269
|
+
} else if (typeof config.ai.max_tokens === "number" && config.ai.max_tokens <= 0) {
|
|
270
|
+
errors.push("ai.max_tokens must be greater than 0");
|
|
247
271
|
}
|
|
248
272
|
}
|
|
249
273
|
}
|
package/src/init.js
CHANGED
|
@@ -94,7 +94,7 @@ NOTION_VERSION=2022-06-28
|
|
|
94
94
|
|
|
95
95
|
# Confluence Publishing
|
|
96
96
|
CONFLUENCE_URL=https://your-company.atlassian.net/wiki
|
|
97
|
-
CONFLUENCE_EMAIL=
|
|
97
|
+
CONFLUENCE_EMAIL=your-email@example.com
|
|
98
98
|
CONFLUENCE_API_TOKEN=
|
|
99
99
|
CONFLUENCE_SPACE_KEY=DOCS
|
|
100
100
|
CONFLUENCE_PARENT_PAGE_ID=
|
|
@@ -104,7 +104,7 @@ CONFLUENCE_PARENT_PAGE_ID=
|
|
|
104
104
|
# REPOLENS_AI_ENABLED=true
|
|
105
105
|
# REPOLENS_AI_API_KEY=sk-...
|
|
106
106
|
# REPOLENS_AI_BASE_URL=https://api.openai.com/v1
|
|
107
|
-
# REPOLENS_AI_MODEL=gpt-
|
|
107
|
+
# REPOLENS_AI_MODEL=gpt-5-mini
|
|
108
108
|
# REPOLENS_AI_TEMPERATURE=0.3
|
|
109
109
|
# REPOLENS_AI_MAX_TOKENS=2000
|
|
110
110
|
`;
|
package/src/migrate.js
CHANGED
|
@@ -234,7 +234,7 @@ export async function runMigrate(targetDir = process.cwd(), options = {}) {
|
|
|
234
234
|
console.log("\n📝 Next steps:");
|
|
235
235
|
console.log(" 1. Review the changes: git diff .github/workflows/");
|
|
236
236
|
console.log(" 2. Test locally: npx @chappibunny/repolens@latest publish");
|
|
237
|
-
console.log(" 3. Commit: git add .github/workflows/ && git commit -m 'chore: migrate RepoLens workflow to
|
|
237
|
+
console.log(" 3. Commit: git add .github/workflows/ && git commit -m 'chore: migrate RepoLens workflow to latest format'");
|
|
238
238
|
console.log(" 4. Push: git push");
|
|
239
239
|
console.log("\n💡 Tip: Backups saved as *.backup - delete them once verified");
|
|
240
240
|
} else {
|
|
@@ -259,6 +259,6 @@ export async function runMigrate(targetDir = process.cwd(), options = {}) {
|
|
|
259
259
|
async function printMigrationBanner() {
|
|
260
260
|
console.log("\n" + "=".repeat(60));
|
|
261
261
|
console.log("🔄 RepoLens Workflow Migration Tool");
|
|
262
|
-
console.log(" Upgrading to
|
|
262
|
+
console.log(" Upgrading to latest format");
|
|
263
263
|
console.log("=".repeat(60));
|
|
264
264
|
}
|
package/src/publishers/index.js
CHANGED
|
@@ -90,8 +90,8 @@ export async function publishDocs(cfg, renderedPages, scanResult, pluginManager
|
|
|
90
90
|
await publisher.publish(cfg, renderedPages);
|
|
91
91
|
publishedTo.push(key);
|
|
92
92
|
} catch (err) {
|
|
93
|
+
warn(`Plugin publisher "${key}" failed: ${err.message}`);
|
|
93
94
|
publishStatus = "failure";
|
|
94
|
-
throw err;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
package/src/publishers/notion.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fetch from "node-fetch";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { log } from "../utils/logger.js";
|
|
4
|
+
import { log, warn } from "../utils/logger.js";
|
|
5
5
|
import { fetchWithRetry } from "../utils/retry.js";
|
|
6
6
|
import { executeNotionRequest } from "../utils/rate-limit.js";
|
|
7
7
|
import { createRepoLensError } from "../utils/errors.js";
|
|
@@ -243,7 +243,7 @@ function parseInlineRichText(text) {
|
|
|
243
243
|
function markdownToNotionBlocks(markdown) {
|
|
244
244
|
// Safety check: handle undefined/null markdown
|
|
245
245
|
if (!markdown || typeof markdown !== 'string') {
|
|
246
|
-
|
|
246
|
+
warn(`markdownToNotionBlocks received invalid markdown: ${typeof markdown}`);
|
|
247
247
|
return [];
|
|
248
248
|
}
|
|
249
249
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ensurePage, replacePageContent } from "./notion.js";
|
|
2
2
|
import { getCurrentBranch, getBranchQualifiedTitle } from "../utils/branch.js";
|
|
3
|
+
import { warn } from "../utils/logger.js";
|
|
3
4
|
|
|
4
5
|
export async function publishToNotion(cfg, renderedPages) {
|
|
5
6
|
const parentPageId = process.env.NOTION_PARENT_PAGE_ID;
|
|
@@ -22,7 +23,7 @@ export async function publishToNotion(cfg, renderedPages) {
|
|
|
22
23
|
|
|
23
24
|
// Skip if content not generated (e.g., disabled feature or generation error)
|
|
24
25
|
if (!markdown) {
|
|
25
|
-
|
|
26
|
+
warn(`Skipping ${page.key}: No content generated`);
|
|
26
27
|
continue;
|
|
27
28
|
}
|
|
28
29
|
|
package/src/utils/rate-limit.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* to prevent abuse and respect API limits.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { warn } from "./logger.js";
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Rate limiter class using token bucket algorithm
|
|
10
12
|
*/
|
|
@@ -193,7 +195,7 @@ export async function executeNotionRequest(fn, options = {}) {
|
|
|
193
195
|
if (options.onRetry) {
|
|
194
196
|
options.onRetry(attempt, maxRetries, delay, error);
|
|
195
197
|
} else {
|
|
196
|
-
|
|
198
|
+
warn(
|
|
197
199
|
`Notion API retry ${attempt}/${maxRetries} after ${Math.round(delay)}ms (${error.message})`
|
|
198
200
|
);
|
|
199
201
|
}
|
|
@@ -221,7 +223,7 @@ export async function executeAIRequest(fn, options = {}) {
|
|
|
221
223
|
if (options.onRetry) {
|
|
222
224
|
options.onRetry(attempt, maxRetries, delay, error);
|
|
223
225
|
} else {
|
|
224
|
-
|
|
226
|
+
warn(
|
|
225
227
|
`AI API retry ${attempt}/${maxRetries} after ${Math.round(delay)}ms (${error.message})`
|
|
226
228
|
);
|
|
227
229
|
}
|
package/src/utils/telemetry.js
CHANGED
|
@@ -33,8 +33,10 @@ export function initTelemetry() {
|
|
|
33
33
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
34
34
|
const version = packageJson.version || "unknown";
|
|
35
35
|
|
|
36
|
+
const dsn = process.env.REPOLENS_SENTRY_DSN || "https://082083dbf5899ed7e65dfd9b8dc72f90@o4511014913703936.ingest.de.sentry.io/4511014919209040";
|
|
37
|
+
|
|
36
38
|
Sentry.init({
|
|
37
|
-
dsn
|
|
39
|
+
dsn,
|
|
38
40
|
|
|
39
41
|
// Release tracking
|
|
40
42
|
release: `repolens@${version}`,
|
|
@@ -412,8 +414,10 @@ function ensureSentryForFeedback() {
|
|
|
412
414
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
413
415
|
const version = packageJson.version || "unknown";
|
|
414
416
|
|
|
417
|
+
const dsn = process.env.REPOLENS_SENTRY_DSN || "https://082083dbf5899ed7e65dfd9b8dc72f90@o4511014913703936.ingest.de.sentry.io/4511014919209040";
|
|
418
|
+
|
|
415
419
|
Sentry.init({
|
|
416
|
-
dsn
|
|
420
|
+
dsn,
|
|
417
421
|
release: `repolens@${version}`,
|
|
418
422
|
environment: process.env.NODE_ENV || "production",
|
|
419
423
|
sampleRate: 1.0, // Always send feedback
|
|
@@ -104,7 +104,7 @@ export async function checkForUpdates() {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
function showUpdateMessage(current, latest) {
|
|
107
|
-
|
|
107
|
+
info("");
|
|
108
108
|
warn("┌────────────────────────────────────────────────────────────┐");
|
|
109
109
|
warn("│ 📦 Update Available │");
|
|
110
110
|
warn("├────────────────────────────────────────────────────────────┤");
|
|
@@ -118,7 +118,7 @@ function showUpdateMessage(current, latest) {
|
|
|
118
118
|
warn("│ │");
|
|
119
119
|
warn("│ Release notes: https://github.com/CHAPIBUNNY/repolens │");
|
|
120
120
|
warn("└────────────────────────────────────────────────────────────┘");
|
|
121
|
-
|
|
121
|
+
info("");
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
export async function forceCheckForUpdates() {
|
package/src/utils/validate.js
CHANGED
|
@@ -103,13 +103,13 @@ function validateScanConfig(scan) {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
if (scan.
|
|
107
|
-
if (!Array.isArray(scan.
|
|
108
|
-
errors.push("scan.
|
|
106
|
+
if (scan.ignore) {
|
|
107
|
+
if (!Array.isArray(scan.ignore)) {
|
|
108
|
+
errors.push("scan.ignore must be an array of glob patterns");
|
|
109
109
|
} else {
|
|
110
|
-
for (const pattern of scan.
|
|
110
|
+
for (const pattern of scan.ignore) {
|
|
111
111
|
if (typeof pattern !== "string") {
|
|
112
|
-
errors.push(`Invalid pattern in scan.
|
|
112
|
+
errors.push(`Invalid pattern in scan.ignore: ${pattern} (must be string)`);
|
|
113
113
|
continue; // Skip further checks if not a string
|
|
114
114
|
}
|
|
115
115
|
}
|
|
@@ -207,33 +207,34 @@ function checkNotionWarnings(notion) {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
/**
|
|
210
|
-
* Validate domains configuration
|
|
210
|
+
* Validate domains configuration (object format: { key: { match: [], description? } })
|
|
211
211
|
*/
|
|
212
212
|
function validateDomains(domains) {
|
|
213
213
|
const errors = [];
|
|
214
214
|
|
|
215
|
-
if (
|
|
216
|
-
errors.push("domains must be an
|
|
215
|
+
if (typeof domains !== "object" || Array.isArray(domains)) {
|
|
216
|
+
errors.push("domains must be an object");
|
|
217
217
|
return errors;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
for (const domain of domains) {
|
|
221
|
-
if (
|
|
222
|
-
errors.push(
|
|
220
|
+
for (const [key, domain] of Object.entries(domains)) {
|
|
221
|
+
if (typeof domain !== "object" || Array.isArray(domain)) {
|
|
222
|
+
errors.push(`domains.${key} must be an object`);
|
|
223
|
+
continue;
|
|
223
224
|
}
|
|
224
225
|
|
|
225
|
-
if (!domain.
|
|
226
|
-
errors.push(`
|
|
226
|
+
if (!domain.match || !Array.isArray(domain.match)) {
|
|
227
|
+
errors.push(`domains.${key} must have a 'match' array`);
|
|
227
228
|
} else {
|
|
228
|
-
for (const pattern of domain.
|
|
229
|
+
for (const pattern of domain.match) {
|
|
229
230
|
if (typeof pattern !== "string") {
|
|
230
|
-
errors.push(`Invalid pattern in
|
|
231
|
+
errors.push(`Invalid pattern in domains.${key}.match: ${pattern}`);
|
|
231
232
|
}
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
235
|
|
|
235
236
|
if (domain.description && typeof domain.description !== "string") {
|
|
236
|
-
errors.push(`
|
|
237
|
+
errors.push(`domains.${key}.description must be a string`);
|
|
237
238
|
}
|
|
238
239
|
}
|
|
239
240
|
|
|
@@ -319,17 +320,25 @@ function checkForInjection(config) {
|
|
|
319
320
|
});
|
|
320
321
|
}
|
|
321
322
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
if (config.scan?.ignore && Array.isArray(config.scan.ignore)) {
|
|
324
|
+
config.scan.ignore.forEach((pattern, i) => {
|
|
325
|
+
if (typeof pattern === "string") {
|
|
326
|
+
checkString(pattern, `scan.ignore[${i}]`);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Check domain patterns (object format: { key: { match: [] } })
|
|
332
|
+
if (config.domains && typeof config.domains === "object" && !Array.isArray(config.domains)) {
|
|
333
|
+
for (const [domainKey, domain] of Object.entries(config.domains)) {
|
|
334
|
+
if (domain.match && Array.isArray(domain.match)) {
|
|
335
|
+
domain.match.forEach((pattern, j) => {
|
|
327
336
|
if (typeof pattern === "string") {
|
|
328
|
-
checkString(pattern, `domains
|
|
337
|
+
checkString(pattern, `domains.${domainKey}.match[${j}]`);
|
|
329
338
|
}
|
|
330
339
|
});
|
|
331
340
|
}
|
|
332
|
-
}
|
|
341
|
+
}
|
|
333
342
|
}
|
|
334
343
|
|
|
335
344
|
return errors;
|