@chappibunny/repolens 0.4.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.
@@ -0,0 +1,256 @@
1
+ export function renderSystemOverview(cfg, scan) {
2
+ const notes = [];
3
+
4
+ // Generate meaningful notes based on detected metadata
5
+ if (scan.metadata?.frameworks?.length) {
6
+ notes.push(`Tech Stack: ${scan.metadata.frameworks.join(", ")}`);
7
+ }
8
+
9
+ if (scan.metadata?.languages?.size) {
10
+ notes.push(`Languages: ${[...scan.metadata.languages].join(", ")}`);
11
+ }
12
+
13
+ if (scan.metadata?.buildTools?.length) {
14
+ notes.push(`Build Tools: ${scan.metadata.buildTools.join(", ")}`);
15
+ }
16
+
17
+ if (scan.metadata?.testFrameworks?.length) {
18
+ notes.push(`Testing: ${scan.metadata.testFrameworks.join(", ")}`);
19
+ }
20
+
21
+ // Add architectural insights
22
+ if (scan.modules.length > 50) {
23
+ notes.push(`Architecture: Large modular codebase with ${scan.modules.length} identified modules`);
24
+ } else if (scan.modules.length > 20) {
25
+ notes.push(`Architecture: Medium-sized modular structure with ${scan.modules.length} modules`);
26
+ } else if (scan.modules.length > 0) {
27
+ notes.push(`Architecture: Compact modular design with ${scan.modules.length} modules`);
28
+ }
29
+
30
+ if (scan.api?.length > 0) {
31
+ notes.push(`API Coverage: ${scan.api.length} API endpoints detected`);
32
+ }
33
+
34
+ if (scan.pages?.length > 0) {
35
+ notes.push(`UI Pages: ${scan.pages.length} application pages detected`);
36
+ }
37
+
38
+ // If no meaningful data, show default message
39
+ if (notes.length === 0) {
40
+ notes.push("This is an overview based on filesystem heuristics. Add a package.json to see framework and tooling details.");
41
+ }
42
+
43
+ return [
44
+ `# ${cfg.project.name} — System Overview`,
45
+ ``,
46
+ `What is this? This page provides a high-level snapshot of your codebase structure, showing what technologies you're using and how your code is organized.`,
47
+ ``,
48
+ `📊 Last Updated: ${new Date().toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" })}`,
49
+ ``,
50
+ `---`,
51
+ ``,
52
+ `## 📸 Quick Stats`,
53
+ ``,
54
+ `Here's what we found in your repository:`,
55
+ ``,
56
+ `- ${scan.filesCount} files scanned across your codebase`,
57
+ `- ${scan.modules.length} modules (major code sections) detected`,
58
+ `- ${scan.pages?.length || 0} pages in your application`,
59
+ `- ${scan.api.length} API endpoints for backend functionality`,
60
+ ``,
61
+ `## 📦 Largest Modules`,
62
+ ``,
63
+ `These are your biggest code modules (folders with the most files):`,
64
+ ``,
65
+ ...(scan.modules.slice(0, 10).map((m) => `- \`${m.key}\` contains ${m.fileCount} files`)),
66
+ ``,
67
+ `## 🔧 Technology Stack`,
68
+ ``,
69
+ `Your project uses these technologies:`,
70
+ ``,
71
+ ...notes.map(note => note),
72
+ ``,
73
+ `---`,
74
+ ``,
75
+ `💡 Tip: This documentation auto-updates on every push to your main branch!`,
76
+ ``
77
+ ].join("\n");
78
+ }
79
+
80
+ export function renderModuleCatalog(cfg, scan) {
81
+ const lines = [
82
+ `# 📁 Module Catalog`,
83
+ ``,
84
+ `What is this? This is a complete inventory of all code modules (folders) in your project, showing how your codebase is organized.`,
85
+ ``,
86
+ `Total modules found: ${scan.modules.length}`,
87
+ ``,
88
+ `---`,
89
+ ``,
90
+ `## All Modules`,
91
+ ``,
92
+ `Each module represents a major section of your codebase. The file count shows relative size:`,
93
+ ``
94
+ ];
95
+
96
+ if (!scan.modules.length) {
97
+ lines.push(`No modules detected. Configure \`module_roots\` in \`.repolens.yml\` to organize your code.`, ``);
98
+ return lines.join("\n");
99
+ }
100
+
101
+ for (const module of scan.modules.slice(0, 100)) {
102
+ lines.push(`- \`${module.key}\` — ${module.fileCount} files`);
103
+ }
104
+
105
+ if (scan.modules.length > 100) {
106
+ lines.push(``, `_(Showing top 100 of ${scan.modules.length} modules)_`);
107
+ }
108
+
109
+ lines.push(``, `---`, ``, `💡 Tip: Click any module name to see its location in your codebase.`, ``);
110
+
111
+ return lines.join("\n");
112
+ }
113
+
114
+ export function renderApiSurface(cfg, scan) {
115
+ const lines = [
116
+ `# 🔌 API Surface`,
117
+ ``,
118
+ `What is this? This page shows both the API endpoints your application provides AND the external APIs it integrates with.`,
119
+ ``,
120
+ `---`,
121
+ ``
122
+ ];
123
+
124
+ // Section 1: Internal API Endpoints (what we expose)
125
+ lines.push(
126
+ `## Internal API Endpoints`,
127
+ ``,
128
+ `These are the backend services your application provides to handle requests.`,
129
+ ``,
130
+ `Total endpoints: ${scan.api.length}`,
131
+ ``
132
+ );
133
+
134
+ if (!scan.api.length) {
135
+ lines.push(
136
+ `No API routes detected. Your project doesn't appear to have Next.js API routes yet.`,
137
+ ``
138
+ );
139
+ } else {
140
+ lines.push(
141
+ `Each line shows: HTTP Method → API Path → Implementation File`,
142
+ ``
143
+ );
144
+
145
+ for (const route of scan.api) {
146
+ lines.push(`- ${route.methods.join(", ")} \`${route.path}\` • \`${route.file}\``);
147
+ }
148
+
149
+ lines.push(``);
150
+ }
151
+
152
+ // Section 2: External API Integrations (what we call)
153
+ lines.push(
154
+ `---`,
155
+ ``,
156
+ `## External API Integrations`,
157
+ ``,
158
+ `These are third-party services your application connects to.`,
159
+ ``
160
+ );
161
+
162
+ if (!scan.externalApis || scan.externalApis.length === 0) {
163
+ lines.push(
164
+ `No external API integrations detected.`,
165
+ ``
166
+ );
167
+ } else {
168
+ // Group by category
169
+ const byCategory = {};
170
+ for (const api of scan.externalApis) {
171
+ if (!byCategory[api.category]) {
172
+ byCategory[api.category] = [];
173
+ }
174
+ byCategory[api.category].push(api);
175
+ }
176
+
177
+ for (const [category, apis] of Object.entries(byCategory)) {
178
+ lines.push(`### ${category}`, ``);
179
+ for (const api of apis) {
180
+ lines.push(`- **${api.name}** — detected in \`${api.detectedIn}\``);
181
+ }
182
+ lines.push(``);
183
+ }
184
+ }
185
+
186
+ lines.push(
187
+ `---`,
188
+ ``,
189
+ `💡 Tips:`,
190
+ `- **Internal endpoints** handle incoming requests from users/clients`,
191
+ `- **External integrations** connect your app to third-party services`,
192
+ `- HTTP methods: GET (retrieve), POST (create), PUT/PATCH (update), DELETE (remove)`,
193
+ ``
194
+ );
195
+
196
+ return lines.join("\n");
197
+ }
198
+
199
+ export function renderRouteMap(cfg, scan) {
200
+ const lines = [
201
+ `# 🗺️ Route Map`,
202
+ ``,
203
+ `What is this? This page shows all the pages (URLs) users can visit in your application, plus the backend API endpoints that power them.`,
204
+ ``,
205
+ `---`,
206
+ ``
207
+ ];
208
+
209
+ if (scan.pages?.length) {
210
+ lines.push(
211
+ `## 🏠 Application Pages (${scan.pages.length})`,
212
+ ``,
213
+ `These are the user-facing pages in your app:`,
214
+ ``
215
+ );
216
+
217
+ for (const page of scan.pages.slice(0, 200)) {
218
+ lines.push(`- \`${page.path}\` • \`${page.file}\``);
219
+ }
220
+
221
+ lines.push(``);
222
+ }
223
+
224
+ if (scan.api?.length) {
225
+ lines.push(
226
+ `## 🔌 API Endpoints (${scan.api.length})`,
227
+ ``,
228
+ `These are backend services that handle data operations:`,
229
+ ``
230
+ );
231
+
232
+ for (const route of scan.api.slice(0, 200)) {
233
+ lines.push(`- ${route.methods.join(", ")} \`${route.path}\` • \`${route.file}\``);
234
+ }
235
+
236
+ lines.push(``);
237
+ }
238
+
239
+ if (!scan.pages?.length && !scan.api?.length) {
240
+ lines.push(
241
+ `## No Routes Detected`,
242
+ ``,
243
+ `RepoLens looks for Next.js pages and API routes. If you're using a different framework, routes might not be auto-detected yet.`,
244
+ ``
245
+ );
246
+ }
247
+
248
+ lines.push(
249
+ `---`,
250
+ ``,
251
+ `💡 Tip: URL paths starting with \`/api/\` are backend endpoints, others are user-facing pages.`,
252
+ ``
253
+ );
254
+
255
+ return lines.join("\n");
256
+ }
@@ -0,0 +1,139 @@
1
+ function isRouteFile(file) {
2
+ return (
3
+ file.includes("/pages/api/") ||
4
+ file.endsWith("/route.ts") ||
5
+ file.endsWith("/route.js") ||
6
+ file.endsWith("/page.tsx") ||
7
+ file.endsWith("/page.jsx") ||
8
+ file.endsWith("/page.ts") ||
9
+ file.endsWith("/page.js")
10
+ );
11
+ }
12
+
13
+ function routePathFromFile(file) {
14
+ if (file.includes("/app/")) {
15
+ const appIndex = file.indexOf("/app/");
16
+ const relative = file.slice(appIndex + 5);
17
+
18
+ return "/" + relative
19
+ .replace(/\/page\.(ts|tsx|js|jsx)$/, "")
20
+ .replace(/\/route\.(ts|tsx|js|jsx)$/, "")
21
+ .replace(/\[(.*?)\]/g, ":$1");
22
+ }
23
+
24
+ if (file.includes("/pages/api/")) {
25
+ const apiIndex = file.indexOf("/pages/api/");
26
+ const relative = file.slice(apiIndex + 11);
27
+
28
+ return "/api/" + relative.replace(/\.(ts|tsx|js|jsx)$/, "");
29
+ }
30
+
31
+ return file;
32
+ }
33
+
34
+ function moduleFromFile(file) {
35
+ const normalized = file.replace(/\\/g, "/");
36
+ const parts = normalized.split("/");
37
+
38
+ const roots = ["app", "components", "lib", "hooks", "store", "services", "packages", "src"];
39
+
40
+ for (let i = 0; i < parts.length - 1; i++) {
41
+ if (roots.includes(parts[i])) {
42
+ return parts[i + 1] ? `${parts[i]}/${parts[i + 1]}` : parts[i];
43
+ }
44
+ }
45
+
46
+ return parts[0] || "root";
47
+ }
48
+
49
+ export function buildArchitectureDiffData(diff) {
50
+ const addedRoutes = diff.added.filter(isRouteFile).map(routePathFromFile);
51
+ const removedRoutes = diff.removed.filter(isRouteFile).map(routePathFromFile);
52
+
53
+ const impactedModules = [
54
+ ...new Set(
55
+ [...diff.added, ...diff.removed, ...diff.modified].map(moduleFromFile)
56
+ )
57
+ ].sort();
58
+
59
+ return {
60
+ ...diff,
61
+ addedRoutes,
62
+ removedRoutes,
63
+ impactedModules
64
+ };
65
+ }
66
+
67
+ export function renderArchitectureDiff(diff) {
68
+ const data = buildArchitectureDiffData(diff);
69
+
70
+ const lines = [
71
+ "# Architecture Diff",
72
+ "",
73
+ "RepoLens generated this from the current git diff versus the base branch.",
74
+ "",
75
+ "## Summary",
76
+ "",
77
+ `Added files: ${data.added.length}`,
78
+ `Removed files: ${data.removed.length}`,
79
+ `Modified files: ${data.modified.length}`,
80
+ `Added routes: ${data.addedRoutes.length}`,
81
+ `Removed routes: ${data.removedRoutes.length}`,
82
+ `Impacted modules: ${data.impactedModules.length}`,
83
+ ""
84
+ ];
85
+
86
+ if (data.addedRoutes.length) {
87
+ lines.push("## Added Routes", "");
88
+ for (const route of data.addedRoutes.slice(0, 25)) {
89
+ lines.push(`- \`${route}\``);
90
+ }
91
+ lines.push("");
92
+ }
93
+
94
+ if (data.removedRoutes.length) {
95
+ lines.push("## Removed Routes", "");
96
+ for (const route of data.removedRoutes.slice(0, 25)) {
97
+ lines.push(`- \`${route}\``);
98
+ }
99
+ lines.push("");
100
+ }
101
+
102
+ if (data.impactedModules.length) {
103
+ lines.push("## Impacted Modules", "");
104
+ for (const module of data.impactedModules.slice(0, 40)) {
105
+ lines.push(`- \`${module}\``);
106
+ }
107
+ lines.push("");
108
+ }
109
+
110
+ if (data.added.length) {
111
+ lines.push("## Added Files", "");
112
+ for (const file of data.added.slice(0, 25)) {
113
+ lines.push(`- \`${file}\``);
114
+ }
115
+ lines.push("");
116
+ }
117
+
118
+ if (data.removed.length) {
119
+ lines.push("## Removed Files", "");
120
+ for (const file of data.removed.slice(0, 25)) {
121
+ lines.push(`- \`${file}\``);
122
+ }
123
+ lines.push("");
124
+ }
125
+
126
+ if (data.modified.length) {
127
+ lines.push("## Modified Files", "");
128
+ for (const file of data.modified.slice(0, 25)) {
129
+ lines.push(`- \`${file}\``);
130
+ }
131
+ lines.push("");
132
+ }
133
+
134
+ if (!data.added.length && !data.removed.length && !data.modified.length) {
135
+ lines.push("No architecture-relevant changes detected.", "");
136
+ }
137
+
138
+ return lines.join("\n");
139
+ }
@@ -0,0 +1,224 @@
1
+ function sanitizeNodeId(value) {
2
+ return value.replace(/[^a-zA-Z0-9_]/g, "_");
3
+ }
4
+
5
+ function normalizeLabel(value) {
6
+ return value
7
+ .replace(/^src\//, "")
8
+ .replace(/^app\/\((.*?)\)/, "app/$1")
9
+ .replace(/\/\((.*?)\)/g, "/$1")
10
+ .replace(/\/+/g, "/");
11
+ }
12
+
13
+ function buildModuleGraph(modules) {
14
+ // Create nodes with module details
15
+ const nodes = modules.map(mod => ({
16
+ id: sanitizeNodeId(mod.key),
17
+ key: mod.key,
18
+ label: normalizeLabel(mod.key),
19
+ fileCount: mod.fileCount,
20
+ category: categorizeModule(mod.key)
21
+ }));
22
+
23
+ // Infer relationships based on common patterns
24
+ const relationships = [];
25
+
26
+ for (const source of nodes) {
27
+ for (const target of nodes) {
28
+ if (source.id === target.id) continue;
29
+
30
+ // CLI imports from core, publishers, renderers, utils
31
+ if (source.label === "bin" || source.label.startsWith("bin/")) {
32
+ if (target.label.startsWith("src/")) {
33
+ relationships.push({
34
+ from: source.id,
35
+ to: target.id,
36
+ type: "uses"
37
+ });
38
+ }
39
+ }
40
+
41
+ // Core modules are foundational - others depend on them
42
+ if (target.label.startsWith("src/core")) {
43
+ if (source.label.startsWith("src/publishers") ||
44
+ source.label.startsWith("src/renderers") ||
45
+ source.label.startsWith("src/delivery")) {
46
+ relationships.push({
47
+ from: source.id,
48
+ to: target.id,
49
+ type: "depends-on"
50
+ });
51
+ }
52
+ }
53
+
54
+ // Publishers use renderers
55
+ if (source.label.startsWith("src/publishers") && target.label.startsWith("src/renderers")) {
56
+ relationships.push({
57
+ from: source.id,
58
+ to: target.id,
59
+ type: "renders"
60
+ });
61
+ }
62
+
63
+ // Everything uses utils
64
+ if (target.label.startsWith("src/utils")) {
65
+ if (source.label.startsWith("src/") && source.label !== target.label) {
66
+ relationships.push({
67
+ from: source.id,
68
+ to: target.id,
69
+ type: "uses"
70
+ });
71
+ }
72
+ }
73
+
74
+ // Delivery uses publishers
75
+ if (source.label.startsWith("src/delivery") && target.label.startsWith("src/publishers")) {
76
+ relationships.push({
77
+ from: source.id,
78
+ to: target.id,
79
+ type: "publishes-via"
80
+ });
81
+ }
82
+
83
+ // Tests test everything
84
+ if (source.label.startsWith("tests/") || source.label.startsWith("test/")) {
85
+ if (!target.label.startsWith("tests/") && !target.label.startsWith("test/")) {
86
+ relationships.push({
87
+ from: source.id,
88
+ to: target.id,
89
+ type: "tests"
90
+ });
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ return { nodes, relationships };
97
+ }
98
+
99
+ function categorizeModule(key) {
100
+ const normalized = key.toLowerCase();
101
+ if (normalized.includes("core")) return "core";
102
+ if (normalized.includes("publisher")) return "integration";
103
+ if (normalized.includes("renderer")) return "business";
104
+ if (normalized.includes("delivery")) return "integration";
105
+ if (normalized.includes("util")) return "util";
106
+ if (normalized.includes("test")) return "test";
107
+ if (normalized.includes("bin")) return "cli";
108
+ return "other";
109
+ }
110
+
111
+ function generateUnicodeArchitectureDiagram(nodes, relationships) {
112
+ // Group nodes by category
113
+ const categories = {
114
+ cli: { icon: "🎯", label: "CLI Entry", nodes: [] },
115
+ core: { icon: "⚙️", label: "Core Logic", nodes: [] },
116
+ business: { icon: "📋", label: "Business Logic", nodes: [] },
117
+ integration: { icon: "🔌", label: "Integration", nodes: [] },
118
+ util: { icon: "🛠️", label: "Utilities", nodes: [] },
119
+ test: { icon: "✅", label: "Testing", nodes: [] },
120
+ other: { icon: "📦", label: "Other", nodes: [] }
121
+ };
122
+
123
+ // Organize nodes by category
124
+ for (const node of nodes) {
125
+ const category = categories[node.category] || categories.other;
126
+ category.nodes.push(node);
127
+ }
128
+
129
+ const lines = [];
130
+ lines.push("```");
131
+ lines.push("┌─────────────────────────────────────────────────────────────┐");
132
+ lines.push("│ 🏗️ SYSTEM ARCHITECTURE MAP │");
133
+ lines.push("└─────────────────────────────────────────────────────────────┘");
134
+ lines.push("");
135
+
136
+ // Build dependency map for annotations
137
+ const dependencyMap = new Map();
138
+ for (const rel of relationships) {
139
+ if (!dependencyMap.has(rel.from)) {
140
+ dependencyMap.set(rel.from, []);
141
+ }
142
+ dependencyMap.get(rel.from).push({ to: rel.to, type: rel.type });
143
+ }
144
+
145
+ // Render each category with its nodes
146
+ for (const [catKey, category] of Object.entries(categories)) {
147
+ if (category.nodes.length === 0) continue;
148
+
149
+ lines.push(`${category.icon} ${category.label.toUpperCase()}`);
150
+ lines.push("│");
151
+
152
+ category.nodes.forEach((node, idx) => {
153
+ const isLast = idx === category.nodes.length - 1;
154
+ const prefix = isLast ? "└──" : "├──";
155
+ const shortLabel = node.label.split('/').pop() || node.label;
156
+ const fileInfo = `(${node.fileCount} file${node.fileCount !== 1 ? 's' : ''})`;
157
+
158
+ lines.push(`${prefix} ${shortLabel} ${fileInfo}`);
159
+
160
+ // Show dependencies for this node
161
+ const deps = dependencyMap.get(node.id) || [];
162
+ if (deps.length > 0) {
163
+ const connector = isLast ? " " : "│ ";
164
+ deps.slice(0, 3).forEach((dep, depIdx) => {
165
+ const depNode = nodes.find(n => n.id === dep.to);
166
+ if (depNode) {
167
+ const depLabel = depNode.label.split('/').pop() || depNode.label;
168
+ const arrow = dep.type === "tests" ? "╌→" : "→";
169
+ lines.push(`${connector} ${arrow} ${depLabel}`);
170
+ }
171
+ });
172
+ if (deps.length > 3) {
173
+ lines.push(`${connector} ... +${deps.length - 3} more`);
174
+ }
175
+ }
176
+ });
177
+
178
+ lines.push("│");
179
+ }
180
+
181
+ lines.push("");
182
+ lines.push("Legend:");
183
+ lines.push(" → depends on / uses");
184
+ lines.push(" ╌→ tests");
185
+ lines.push("```");
186
+
187
+ return lines.join("\n");
188
+ }
189
+
190
+ export function renderSystemMap(scan) {
191
+ const modules = (scan.modules || []).slice(0, 30); // Limit for readability
192
+
193
+ if (modules.length === 0) {
194
+ return [
195
+ "# 🏗️ System Map",
196
+ "",
197
+ "> What is this? This page shows how different parts of your codebase connect and depend on each other.",
198
+ "",
199
+ "No modules detected. Configure `module_roots` in `.repolens.yml` to visualize your architecture.",
200
+ ""
201
+ ].join("\n");
202
+ }
203
+
204
+ const { nodes, relationships } = buildModuleGraph(modules);
205
+ const architectureDiagram = generateUnicodeArchitectureDiagram(nodes, relationships);
206
+
207
+ // Build markdown output
208
+ const lines = [
209
+ "# 🏗️ System Map",
210
+ "",
211
+ "> What is this? This visual diagram shows how different parts of your codebase connect to each other. Arrows indicate dependencies (which modules use which).",
212
+ "",
213
+ `Showing: ${nodes.length} modules and ${relationships.length} relationships`,
214
+ "",
215
+ "---",
216
+ "",
217
+ "## Architecture Diagram",
218
+ "",
219
+ architectureDiagram,
220
+ ""
221
+ ];
222
+
223
+ return lines.join("\n");
224
+ }
@@ -0,0 +1,93 @@
1
+ import { execSync } from "node:child_process";
2
+
3
+ /**
4
+ * Detect the current git branch from various sources
5
+ * Priority: CI environment → git command → fallback
6
+ */
7
+ export function getCurrentBranch() {
8
+ // 1. Check GitHub Actions environment
9
+ if (process.env.GITHUB_REF_NAME) {
10
+ return process.env.GITHUB_REF_NAME;
11
+ }
12
+
13
+ // 2. Check GitLab CI environment
14
+ if (process.env.CI_COMMIT_REF_NAME) {
15
+ return process.env.CI_COMMIT_REF_NAME;
16
+ }
17
+
18
+ // 3. Check CircleCI environment
19
+ if (process.env.CIRCLE_BRANCH) {
20
+ return process.env.CIRCLE_BRANCH;
21
+ }
22
+
23
+ // 4. Try git command
24
+ try {
25
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", {
26
+ encoding: "utf8",
27
+ stdio: ["pipe", "pipe", "ignore"]
28
+ }).trim();
29
+
30
+ if (branch && branch !== "HEAD") {
31
+ return branch;
32
+ }
33
+ } catch {
34
+ // Git command failed, continue to fallback
35
+ }
36
+
37
+ // 5. Fallback
38
+ return "unknown";
39
+ }
40
+
41
+ /**
42
+ * Normalize branch name for display (sanitize special characters)
43
+ */
44
+ export function normalizeBranchName(branch) {
45
+ return branch
46
+ .replace(/\//g, "-") // feature/auth → feature-auth
47
+ .replace(/[^a-zA-Z0-9-_]/g, "") // Remove special chars
48
+ .toLowerCase();
49
+ }
50
+
51
+ /**
52
+ * Check if current branch should publish to Notion
53
+ * Based on config.notion.branches setting
54
+ */
55
+ export function shouldPublishToNotion(config, currentBranch = getCurrentBranch()) {
56
+ // If no notion config, allow all branches (backward compatible)
57
+ if (!config.notion) {
58
+ return true;
59
+ }
60
+
61
+ // If branches not specified, allow all
62
+ if (!config.notion.branches || config.notion.branches.length === 0) {
63
+ return true;
64
+ }
65
+
66
+ // Check if current branch matches any allowed pattern
67
+ return config.notion.branches.some(pattern => {
68
+ // Exact match
69
+ if (pattern === currentBranch) {
70
+ return true;
71
+ }
72
+
73
+ // Wildcard pattern (simple glob)
74
+ if (pattern.includes("*")) {
75
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
76
+ return regex.test(currentBranch);
77
+ }
78
+
79
+ return false;
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Get branch-qualified page title
85
+ */
86
+ export function getBranchQualifiedTitle(baseTitle, branch = getCurrentBranch(), includeBranch = true) {
87
+ if (!includeBranch || branch === "main" || branch === "master") {
88
+ return baseTitle;
89
+ }
90
+
91
+ const normalizedBranch = normalizeBranchName(branch);
92
+ return `${baseTitle} [${normalizedBranch}]`;
93
+ }