@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 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**: v0.9.0 — Plugin System
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
- ⚠️ **Early Access Notice**: The CLI commands and configuration format may evolve until v1.0. We will provide migration guides for any breaking changes. v1.0 will guarantee stable CLI behavior and config schema.
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=trades@rabitaitrades.com
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/v0.9.0/chappibunny-repolens-0.9.0.tgz
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-4-turbo-preview, gpt-3.5-turbo)
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-4-turbo-preview
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-4-turbo-preview):
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=trades@rabitaitrades.com
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: `trades@rabitaitrades.com`
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 | No | Schema version (current: 1) for future migrations |
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 | Experimental feature flags |
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.9.0.tgz
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.9.0 → 0.9.1) - Bug fixes
1177
+ # Patch version (1.0.0 → 1.0.1) - Bug fixes
1167
1178
  npm run release:patch
1168
1179
 
1169
- # Minor version (0.9.0 → 0.10.0) - New features
1180
+ # Minor version (1.0.0 → 1.1.0) - New features
1170
1181
  npm run release:minor
1171
1182
 
1172
- # Major version (0.9.0 → 1.0.0) - Breaking changes
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
- - **Share feedback**: What works? What doesn't?
1207
+ - **Build plugins**: Extend RepoLens with custom renderers and publishers
1208
+ - **Share feedback**: `repolens feedback`
1199
1209
 
1200
1210
  ---
1201
1211
 
1202
- ## 🗺️ Roadmap to v1.0
1212
+ ## 🗺️ Roadmap
1203
1213
 
1204
- **Current Status:** v0.9.0 — Plugin System
1214
+ **Current Status:** v1.0.0 — Stable Release
1205
1215
 
1206
- ### Completed
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
- ### Planned for v1.0 🎯
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chappibunny/repolens",
3
- "version": "0.9.0",
3
+ "version": "1.0.0",
4
4
  "description": "AI-assisted documentation intelligence system for technical and non-technical audiences",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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 enabled = process.env.REPOLENS_AI_ENABLED === "true";
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 from environment
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-4-turbo-preview";
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: temperature ?? DEFAULT_TEMPERATURE,
51
- maxTokens: maxTokens ?? DEFAULT_MAX_TOKENS,
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-4-turbo-preview",
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 v0.4.0 format
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(1);
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(2);
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(1);
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(2);
294
+ process.exit(EXIT_VALIDATION);
284
295
  }
285
296
  await runWatch(configPath);
286
297
  return;
287
298
  }
288
299
 
289
- if (command === "publish" || !command || command.startsWith("--")) {
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(2);
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(2);
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(1);
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(1);
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(1);
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(1);
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(1);
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 !== undefined) {
44
- if (typeof config.configVersion !== "number") {
45
- errors.push("configVersion must be a number");
46
- } else if (config.configVersion > CURRENT_SCHEMA_VERSION) {
47
- errors.push(
48
- `Config schema version ${config.configVersion} is not supported. ` +
49
- `This version of RepoLens supports schema version ${CURRENT_SCHEMA_VERSION}. ` +
50
- `Please upgrade RepoLens or downgrade your config.`
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=trades@rabitaitrades.com
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-4-turbo-preview
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 v0.4.0'");
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 v0.4.0 format");
262
+ console.log(" Upgrading to latest format");
263
263
  console.log("=".repeat(60));
264
264
  }
@@ -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
  }
@@ -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
- console.warn(`Warning: markdownToNotionBlocks received invalid markdown: ${typeof markdown}`);
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
- console.log(`⚠️ Skipping ${page.key}: No content generated`);
26
+ warn(`Skipping ${page.key}: No content generated`);
26
27
  continue;
27
28
  }
28
29
 
@@ -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
- console.warn(
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
- console.warn(
226
+ warn(
225
227
  `AI API retry ${attempt}/${maxRetries} after ${Math.round(delay)}ms (${error.message})`
226
228
  );
227
229
  }
@@ -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: "https://082083dbf5899ed7e65dfd9b8dc72f90@o4511014913703936.ingest.de.sentry.io/4511014919209040", // TODO: Replace with actual 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: "https://082083dbf5899ed7e65dfd9b8dc72f90@o4511014913703936.ingest.de.sentry.io/4511014919209040",
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
- console.log("");
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
- console.log("");
121
+ info("");
122
122
  }
123
123
 
124
124
  export async function forceCheckForUpdates() {
@@ -103,13 +103,13 @@ function validateScanConfig(scan) {
103
103
  }
104
104
  }
105
105
 
106
- if (scan.exclude) {
107
- if (!Array.isArray(scan.exclude)) {
108
- errors.push("scan.exclude must be an array of glob patterns");
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.exclude) {
110
+ for (const pattern of scan.ignore) {
111
111
  if (typeof pattern !== "string") {
112
- errors.push(`Invalid pattern in scan.exclude: ${pattern} (must be string)`);
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 (!Array.isArray(domains)) {
216
- errors.push("domains must be an array");
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 (!domain.name || typeof domain.name !== "string") {
222
- errors.push("Each domain must have a 'name' field (string)");
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.patterns || !Array.isArray(domain.patterns)) {
226
- errors.push(`Domain '${domain.name}' must have 'patterns' array`);
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.patterns) {
229
+ for (const pattern of domain.match) {
229
230
  if (typeof pattern !== "string") {
230
- errors.push(`Invalid pattern in domain '${domain.name}': ${pattern}`);
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(`Domain '${domain.name}' description must be a string`);
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
- // Check domain patterns
323
- if (config.domains && Array.isArray(config.domains)) {
324
- config.domains.forEach((domain, i) => {
325
- if (domain.patterns && Array.isArray(domain.patterns)) {
326
- domain.patterns.forEach((pattern, j) => {
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[${i}].patterns[${j}]`);
337
+ checkString(pattern, `domains.${domainKey}.match[${j}]`);
329
338
  }
330
339
  });
331
340
  }
332
- });
341
+ }
333
342
  }
334
343
 
335
344
  return errors;