project-librarian 0.3.0 → 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,303 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runCodeIndexMode = runCodeIndexMode;
37
+ exports.runCodeQueryMode = runCodeQueryMode;
38
+ exports.runCodeReportMode = runCodeReportMode;
39
+ exports.runCodeStatusMode = runCodeStatusMode;
40
+ exports.runCodeIndexHealthMode = runCodeIndexHealthMode;
41
+ exports.runCodeFilesMode = runCodeFilesMode;
42
+ exports.runCodeImpactMode = runCodeImpactMode;
43
+ exports.runCodeContextPackMode = runCodeContextPackMode;
44
+ exports.runCodeSearchSymbolMode = runCodeSearchSymbolMode;
45
+ exports.isCodeEvidenceModeFor = isCodeEvidenceModeFor;
46
+ exports.isCodeEvidenceMode = isCodeEvidenceMode;
47
+ const fs = __importStar(require("node:fs"));
48
+ const args_1 = require("../args");
49
+ const code_index_file_policy_1 = require("../code-index-file-policy");
50
+ const code_index_sql_1 = require("../code-index-sql");
51
+ const reports_1 = require("./reports");
52
+ const schema_1 = require("./schema");
53
+ const search_1 = require("./search");
54
+ function printRows(rows) {
55
+ console.log(JSON.stringify(rows, null, 2));
56
+ }
57
+ function printJson(value) {
58
+ console.log(JSON.stringify(value, null, 2));
59
+ }
60
+ function requireCompatibleDatabase(database, runtime) {
61
+ const schemaVersion = (0, schema_1.readMetaValue)(database, "schema_version");
62
+ if (schemaVersion !== schema_1.codeIndexSchemaVersion) {
63
+ runtime.fail(`code evidence index schema version ${schemaVersion || "(missing)"} is incompatible with ${schema_1.codeIndexSchemaVersion}; rebuild with --code-index`);
64
+ }
65
+ }
66
+ function runCodeIndexMode(runtime) {
67
+ const databasePath = runtime.codeEvidenceDatabasePath();
68
+ const scopes = runtime.codeScopes();
69
+ const parserMode = runtime.selectedCodeParserMode();
70
+ // Scale gate before ANY write or database work: below the measured threshold
71
+ // the build halts with the evidence-citing warning unless --acknowledge-small-repo
72
+ // was passed (2026-06-12 scale-aware guidance decision).
73
+ const discoveredFiles = (0, code_index_file_policy_1.discoverCodeFiles)(scopes);
74
+ const scaleGate = (0, code_index_file_policy_1.smallRepoCodeIndexGate)(discoveredFiles.length, args_1.acknowledgeSmallRepoMode);
75
+ if (!scaleGate.proceed)
76
+ runtime.fail(scaleGate.warning);
77
+ const existingIndex = fs.existsSync(databasePath.absolutePath);
78
+ if (args_1.codeIndexIncrementalMode && !existingIndex) {
79
+ runtime.fail(`--incremental requires an existing compatible code evidence index: ${databasePath.relativePath}`);
80
+ }
81
+ let incremental = false;
82
+ if (existingIndex && !args_1.codeIndexFullMode) {
83
+ let compatibility = { compatible: false, reason: "compatibility was not checked" };
84
+ const existingDatabase = runtime.openDatabase(databasePath.absolutePath);
85
+ try {
86
+ compatibility = (0, schema_1.incrementalCompatibility)(existingDatabase, scopes, parserMode);
87
+ }
88
+ finally {
89
+ existingDatabase.close();
90
+ }
91
+ incremental = !args_1.codeIndexFullMode && compatibility.compatible;
92
+ if (args_1.codeIndexIncrementalMode && !compatibility.compatible)
93
+ runtime.fail(`--incremental cannot update ${databasePath.relativePath}: ${compatibility.reason}`);
94
+ }
95
+ runtime.prepareOutputPath();
96
+ if (!incremental)
97
+ runtime.removeDatabaseFiles(databasePath.absolutePath);
98
+ const database = runtime.openDatabase(databasePath.absolutePath);
99
+ try {
100
+ if (!incremental)
101
+ (0, schema_1.setupDatabase)(database);
102
+ const statements = (0, schema_1.createIndexStatements)(database);
103
+ const currentFingerprints = discoveredFiles.map((filePath) => runtime.readCodeFileFingerprint(filePath));
104
+ let reindexedFiles;
105
+ let deletedPaths;
106
+ let indexedPaths = new Set();
107
+ let unchangedFiles = 0;
108
+ if (incremental) {
109
+ const indexedRows = database.prepare("SELECT path, hash, mtime_ms, size FROM files").all();
110
+ indexedPaths = new Set(indexedRows.map((row) => String(row.path)));
111
+ const indexed = new Map(indexedRows.map((row) => [String(row.path), {
112
+ hash: String(row.hash),
113
+ mtimeMs: Number(row.mtime_ms),
114
+ size: Number(row.size),
115
+ }]));
116
+ const currentPaths = new Set(currentFingerprints.map((file) => file.path));
117
+ deletedPaths = indexedRows.map((row) => String(row.path)).filter((filePath) => !currentPaths.has(filePath));
118
+ reindexedFiles = [];
119
+ for (const file of currentFingerprints) {
120
+ const existing = indexed.get(file.path);
121
+ if (existing && existing.mtimeMs === file.mtimeMs && existing.size === file.size) {
122
+ unchangedFiles += 1;
123
+ continue;
124
+ }
125
+ reindexedFiles.push(runtime.readCodeFile(file.path, parserMode));
126
+ }
127
+ }
128
+ else {
129
+ deletedPaths = [];
130
+ reindexedFiles = discoveredFiles.map((filePath) => runtime.readCodeFile(filePath, parserMode));
131
+ }
132
+ database.exec("BEGIN");
133
+ if (!incremental)
134
+ statements.insertMeta.run("created_at", new Date().toISOString());
135
+ (0, schema_1.writeIndexMetadata)(scopes, parserMode, statements);
136
+ for (const filePath of deletedPaths)
137
+ (0, schema_1.removeIndexedFile)(filePath, statements);
138
+ for (const file of reindexedFiles) {
139
+ if (incremental && indexedPaths.has(file.path))
140
+ (0, schema_1.removeIndexedFile)(file.path, statements);
141
+ runtime.indexCodeFile(file, statements);
142
+ }
143
+ database.exec("COMMIT");
144
+ console.log("Project wiki code evidence index complete.");
145
+ console.log(`database: ${databasePath.relativePath}`);
146
+ console.log(`mode: ${incremental ? "incremental" : "full"}`);
147
+ console.log(`parser_mode: ${parserMode}`);
148
+ console.log(`scopes: ${scopes.join(", ")}`);
149
+ console.log(`files: ${currentFingerprints.length}`);
150
+ console.log(`reindexed_files: ${reindexedFiles.length}`);
151
+ console.log(`deleted_files: ${deletedPaths.length}`);
152
+ console.log(`unchanged_files: ${unchangedFiles}`);
153
+ }
154
+ catch (error) {
155
+ try {
156
+ database.exec("ROLLBACK");
157
+ }
158
+ catch {
159
+ // Ignore rollback failures after setup errors.
160
+ }
161
+ throw error;
162
+ }
163
+ finally {
164
+ database.close();
165
+ }
166
+ }
167
+ function runCodeQueryMode(runtime) {
168
+ if (!args_1.codeQuerySql.trim()) {
169
+ console.error("missing SQL: use --code-query \"select ...\"");
170
+ process.exit(1);
171
+ }
172
+ runtime.requireExistingIndex();
173
+ if (!(0, code_index_sql_1.isReadOnlySql)(args_1.codeQuerySql)) {
174
+ console.error("code queries must be read-only SQL starting with SELECT or WITH");
175
+ process.exit(1);
176
+ }
177
+ const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
178
+ try {
179
+ database.exec("PRAGMA query_only = ON");
180
+ requireCompatibleDatabase(database, runtime);
181
+ runtime.warnIfCodeIndexStale(database);
182
+ printRows(database.prepare(args_1.codeQuerySql).all());
183
+ }
184
+ finally {
185
+ database.close();
186
+ }
187
+ }
188
+ function runCodeReportMode(runtime) {
189
+ runtime.requireExistingIndex();
190
+ const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
191
+ try {
192
+ requireCompatibleDatabase(database, runtime);
193
+ const staleness = runtime.codeIndexStaleness(database);
194
+ runtime.warnIfCodeIndexStale(database, staleness);
195
+ const report = runtime.codeReportForRequestedSection(database, args_1.codeReportSection, { staleness });
196
+ if (!report)
197
+ runtime.fail((0, reports_1.invalidCodeReportSectionMessage)(args_1.codeReportSection));
198
+ printJson(report);
199
+ }
200
+ finally {
201
+ database.close();
202
+ }
203
+ }
204
+ function runCodeStatusMode(runtime) {
205
+ runtime.requireExistingIndex();
206
+ const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
207
+ try {
208
+ requireCompatibleDatabase(database, runtime);
209
+ const rows = database.prepare(`
210
+ SELECT 'files' AS metric, count(*) AS value FROM files
211
+ UNION ALL SELECT 'symbols', count(*) FROM symbols
212
+ UNION ALL SELECT 'imports', count(*) FROM imports
213
+ UNION ALL SELECT 'routes', count(*) FROM routes
214
+ UNION ALL SELECT 'edges', count(*) FROM edges
215
+ UNION ALL SELECT 'configs', count(*) FROM configs
216
+ `).all();
217
+ const staleness = runtime.codeIndexStaleness(database);
218
+ rows.push({ metric: "stale_files", value: staleness.added + staleness.changed + staleness.deleted }, { metric: "stale_changed_files", value: staleness.changed }, { metric: "stale_added_files", value: staleness.added }, { metric: "stale_deleted_files", value: staleness.deleted });
219
+ printRows(rows);
220
+ }
221
+ finally {
222
+ database.close();
223
+ }
224
+ }
225
+ function runCodeIndexHealthMode(runtime) {
226
+ printJson(runtime.codeIndexHealth());
227
+ }
228
+ function runCodeFilesMode(runtime) {
229
+ runtime.requireExistingIndex();
230
+ const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
231
+ try {
232
+ requireCompatibleDatabase(database, runtime);
233
+ runtime.warnIfCodeIndexStale(database);
234
+ printRows(database.prepare("SELECT path, language, profile, kind, lines, bytes FROM files ORDER BY path").all());
235
+ }
236
+ finally {
237
+ database.close();
238
+ }
239
+ }
240
+ function runCodeImpactMode(runtime) {
241
+ if (!args_1.codeImpactTarget.trim()) {
242
+ console.error("missing impact target: use --code-impact \"path-or-symbol-or-module\"");
243
+ process.exit(1);
244
+ }
245
+ runtime.requireExistingIndex();
246
+ const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
247
+ try {
248
+ requireCompatibleDatabase(database, runtime);
249
+ const staleness = runtime.codeIndexStaleness(database);
250
+ runtime.warnIfCodeIndexStale(database, staleness);
251
+ printJson(runtime.codeImpact(database, args_1.codeImpactTarget.trim(), { staleness }));
252
+ }
253
+ finally {
254
+ database.close();
255
+ }
256
+ }
257
+ function runCodeContextPackMode(runtime) {
258
+ if (!args_1.codeContextPackTarget.trim()) {
259
+ console.error("missing context pack query: use --code-context-pack \"path-or-symbol-or-route\"");
260
+ process.exit(1);
261
+ }
262
+ runtime.requireExistingIndex();
263
+ const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
264
+ try {
265
+ requireCompatibleDatabase(database, runtime);
266
+ const staleness = runtime.codeIndexStaleness(database);
267
+ runtime.warnIfCodeIndexStale(database, staleness);
268
+ console.log(runtime.codeContextPack(database, args_1.codeContextPackTarget.trim(), { staleness }));
269
+ }
270
+ finally {
271
+ database.close();
272
+ }
273
+ }
274
+ function runCodeSearchSymbolMode(runtime) {
275
+ if (!args_1.codeSearchSymbol.trim()) {
276
+ console.error("missing symbol search term: use --code-search-symbol \"term\"");
277
+ process.exit(1);
278
+ }
279
+ runtime.requireExistingIndex();
280
+ const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
281
+ try {
282
+ requireCompatibleDatabase(database, runtime);
283
+ runtime.warnIfCodeIndexStale(database);
284
+ printRows((0, search_1.searchSymbols)(database, args_1.codeSearchSymbol.trim()));
285
+ }
286
+ finally {
287
+ database.close();
288
+ }
289
+ }
290
+ function isCodeEvidenceModeFor(flags) {
291
+ return Boolean(flags.codeContextPackTarget)
292
+ || Boolean(flags.codeIndexHealthMode)
293
+ || flags.codeIndexMode
294
+ || Boolean(flags.codeQuerySql)
295
+ || flags.codeReportMode
296
+ || flags.codeStatusMode
297
+ || flags.codeFilesMode
298
+ || flags.codeImpactMode
299
+ || Boolean(flags.codeSearchSymbol);
300
+ }
301
+ function isCodeEvidenceMode() {
302
+ return isCodeEvidenceModeFor({ codeContextPackTarget: args_1.codeContextPackTarget, codeFilesMode: args_1.codeFilesMode, codeImpactMode: args_1.codeImpactMode, codeIndexHealthMode: args_1.codeIndexHealthMode, codeIndexMode: args_1.codeIndexMode, codeQuerySql: args_1.codeQuerySql, codeReportMode: args_1.codeReportMode, codeSearchSymbol: args_1.codeSearchSymbol, codeStatusMode: args_1.codeStatusMode });
303
+ }
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.readJsonObject = readJsonObject;
37
+ exports.workspacePackages = workspacePackages;
38
+ exports.matchingWorkspace = matchingWorkspace;
39
+ exports.codeownerRules = codeownerRules;
40
+ exports.matchedCodeownerRules = matchedCodeownerRules;
41
+ exports.ownershipContext = ownershipContext;
42
+ exports.ownershipInfo = ownershipInfo;
43
+ const fs = __importStar(require("node:fs"));
44
+ const path = __importStar(require("node:path"));
45
+ const workspace_1 = require("../workspace");
46
+ function pathOwnerKey(filePath) {
47
+ const parts = (0, workspace_1.normalizePath)(filePath).split("/").filter(Boolean);
48
+ if (parts.length === 0)
49
+ return ".";
50
+ if (["apps", "libs", "packages", "services"].includes(parts[0] ?? "") && parts[1])
51
+ return `${parts[0]}/${parts[1]}`;
52
+ return parts[0] ?? ".";
53
+ }
54
+ function readJsonObject(relativePath) {
55
+ try {
56
+ const parsed = JSON.parse(fs.readFileSync((0, workspace_1.abs)(relativePath), "utf8"));
57
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ function workspacePatternsFromRootPackage() {
64
+ const rootPackage = readJsonObject("package.json");
65
+ const workspaces = rootPackage?.workspaces;
66
+ if (Array.isArray(workspaces))
67
+ return workspaces.filter((value) => typeof value === "string");
68
+ if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
69
+ const packages = workspaces.packages;
70
+ if (Array.isArray(packages))
71
+ return packages.filter((value) => typeof value === "string");
72
+ }
73
+ return [];
74
+ }
75
+ function workspacePatternCandidates(pattern) {
76
+ const normalized = (0, workspace_1.normalizePath)(pattern).replace(/^\/+/, "").replace(/\/+$/, "");
77
+ if (!normalized || normalized.includes(".."))
78
+ return [];
79
+ if (!normalized.includes("*"))
80
+ return [normalized];
81
+ const starIndex = normalized.indexOf("*");
82
+ const prefix = normalized.slice(0, starIndex).replace(/\/+$/, "");
83
+ const suffix = normalized.slice(starIndex + 1).replace(/^\/+/, "");
84
+ const base = prefix || ".";
85
+ const basePath = (0, workspace_1.abs)(base);
86
+ if (!fs.existsSync(basePath) || !fs.statSync(basePath).isDirectory())
87
+ return [];
88
+ return fs.readdirSync(basePath, { withFileTypes: true })
89
+ .filter((entry) => entry.isDirectory())
90
+ .map((entry) => (0, workspace_1.normalizePath)(path.join(base, entry.name, suffix)))
91
+ .filter((candidate) => fs.existsSync((0, workspace_1.abs)(candidate)) && fs.statSync((0, workspace_1.abs)(candidate)).isDirectory());
92
+ }
93
+ function workspacePackages() {
94
+ const packages = new Map();
95
+ for (const pattern of workspacePatternsFromRootPackage()) {
96
+ for (const candidate of workspacePatternCandidates(pattern)) {
97
+ const packageJsonPath = (0, workspace_1.normalizePath)(path.join(candidate, "package.json"));
98
+ if (!fs.existsSync((0, workspace_1.abs)(packageJsonPath)))
99
+ continue;
100
+ const packageJson = readJsonObject(packageJsonPath);
101
+ const packageName = typeof packageJson?.name === "string" ? packageJson.name : candidate;
102
+ packages.set(candidate, {
103
+ name: packageName,
104
+ root: candidate,
105
+ source: "package.json workspaces",
106
+ workspace_pattern: pattern,
107
+ });
108
+ }
109
+ }
110
+ return Array.from(packages.values()).sort((left, right) => left.root.localeCompare(right.root));
111
+ }
112
+ function matchingWorkspace(filePath, workspaces) {
113
+ const normalized = (0, workspace_1.normalizePath)(filePath);
114
+ return workspaces
115
+ .filter((workspace) => normalized === workspace.root || normalized.startsWith(`${workspace.root}/`))
116
+ .sort((left, right) => right.root.length - left.root.length)[0] ?? null;
117
+ }
118
+ function codeownerRules() {
119
+ const files = [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"];
120
+ const rules = [];
121
+ for (const filePath of files) {
122
+ if (!fs.existsSync((0, workspace_1.abs)(filePath)))
123
+ continue;
124
+ const lines = fs.readFileSync((0, workspace_1.abs)(filePath), "utf8").split(/\r?\n/);
125
+ lines.forEach((lineText, index) => {
126
+ const trimmed = lineText.trim();
127
+ if (!trimmed || trimmed.startsWith("#"))
128
+ return;
129
+ const parts = trimmed.split(/\s+/);
130
+ const pattern = parts[0] ?? "";
131
+ const owners = parts.slice(1);
132
+ if (!pattern || owners.length === 0)
133
+ return;
134
+ rules.push({ file_path: filePath, line: index + 1, owners, pattern });
135
+ });
136
+ }
137
+ return rules;
138
+ }
139
+ function codeownerPatternRegex(pattern) {
140
+ const normalized = (0, workspace_1.normalizePath)(pattern).replace(/^\/+/, "");
141
+ const source = normalized
142
+ .replace(/[.+?^${}()|[\]\\]/g, "\\$&")
143
+ .replace(/\*\*/g, ".*")
144
+ .replace(/\*/g, "[^/]*");
145
+ if (normalized.endsWith("/"))
146
+ return new RegExp(`^${source}.*$`);
147
+ return new RegExp(`^${source}(?:/.*)?$`);
148
+ }
149
+ function codeownerPatternMatches(pattern, filePath) {
150
+ const normalized = (0, workspace_1.normalizePath)(pattern).replace(/^\/+/, "");
151
+ const target = (0, workspace_1.normalizePath)(filePath);
152
+ if (normalized === "*")
153
+ return true;
154
+ if (normalized.startsWith("*."))
155
+ return path.basename(target).endsWith(normalized.slice(1));
156
+ return codeownerPatternRegex(normalized).test(target);
157
+ }
158
+ function matchingCodeowners(filePath, rules) {
159
+ const matches = rules.filter((rule) => codeownerPatternMatches(rule.pattern, filePath));
160
+ return matches[matches.length - 1]?.owners ?? [];
161
+ }
162
+ // Return every CODEOWNERS rule that matches a path, in file order. The last entry
163
+ // is the effective owner under last-match-wins; earlier entries are overridden.
164
+ // Reuses the same matcher as matchingCodeowners so precedence answers stay
165
+ // consistent with --code-report / --code-impact.
166
+ function matchedCodeownerRules(filePath, rules) {
167
+ return rules.filter((rule) => codeownerPatternMatches(rule.pattern, filePath));
168
+ }
169
+ function ownershipContext() {
170
+ return {
171
+ codeownerRules: codeownerRules(),
172
+ workspaces: workspacePackages(),
173
+ };
174
+ }
175
+ function ownershipInfo(filePath, context) {
176
+ const workspace = matchingWorkspace(filePath, context.workspaces);
177
+ const owners = matchingCodeowners(filePath, context.codeownerRules);
178
+ return {
179
+ codeowners: owners.join(", "),
180
+ owner: workspace?.root ?? pathOwnerKey(filePath),
181
+ owner_source: workspace ? "workspace" : "path",
182
+ };
183
+ }