@kb-labs/policy-core 0.5.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/dist/index.js ADDED
@@ -0,0 +1,652 @@
1
+ import fs from 'fs';
2
+ import path4 from 'path';
3
+ import { spawnSync } from 'child_process';
4
+ import semver from 'semver';
5
+ import { combinePermissions, defineCommandFlags } from '@kb-labs/sdk';
6
+
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropNames = Object.getOwnPropertyNames;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+
17
+ // src/core/category-resolver.ts
18
+ var category_resolver_exports = {};
19
+ __export(category_resolver_exports, {
20
+ detectCategories: () => detectCategories,
21
+ detectCategory: () => detectCategory
22
+ });
23
+ function matchesPattern(repoPath, pattern) {
24
+ const normalizedPath = repoPath.replace(/\/$/, "");
25
+ const normalizedPattern = pattern.replace(/\/$/, "");
26
+ if (normalizedPattern === normalizedPath) {
27
+ return true;
28
+ }
29
+ if (normalizedPattern.endsWith("/*")) {
30
+ const prefix = normalizedPattern.slice(0, -2);
31
+ const pathPrefix = normalizedPath.split("/").slice(0, prefix.split("/").length).join("/");
32
+ return pathPrefix === prefix;
33
+ }
34
+ if (normalizedPattern.includes("*")) {
35
+ const regexStr = normalizedPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]+");
36
+ return new RegExp(`^${regexStr}$`).test(normalizedPath);
37
+ }
38
+ return false;
39
+ }
40
+ function detectCategory(repoPath, config) {
41
+ for (const [categoryName, categoryConfig] of Object.entries(config.categories)) {
42
+ for (const pattern of categoryConfig.paths) {
43
+ if (matchesPattern(repoPath, pattern)) {
44
+ return {
45
+ path: repoPath,
46
+ category: categoryName,
47
+ rules: categoryConfig.rules
48
+ };
49
+ }
50
+ }
51
+ }
52
+ return {
53
+ path: repoPath,
54
+ category: null,
55
+ rules: ["boundary-check"]
56
+ // default fallback rule
57
+ };
58
+ }
59
+ function detectCategories(repoPaths, config) {
60
+ return repoPaths.map((p) => detectCategory(p, config));
61
+ }
62
+ var init_category_resolver = __esm({
63
+ "src/core/category-resolver.ts"() {
64
+ }
65
+ });
66
+
67
+ // src/core/index.ts
68
+ init_category_resolver();
69
+
70
+ // src/core/workspace-scanner.ts
71
+ init_category_resolver();
72
+ function buildPackageMap(workspaceRoot, config) {
73
+ const packages = scanAllPackages(workspaceRoot, config);
74
+ const map = /* @__PURE__ */ new Map();
75
+ for (const pkg of packages) {
76
+ map.set(pkg.name, pkg.category);
77
+ }
78
+ return map;
79
+ }
80
+ function getRepoPackageNames(workspaceRoot, repoPath) {
81
+ const absRepoPath = path4.resolve(workspaceRoot, repoPath);
82
+ const names = [];
83
+ for (const subdir of ["packages", "apps"]) {
84
+ const subdirPath = path4.join(absRepoPath, subdir);
85
+ if (!fs.existsSync(subdirPath)) {
86
+ continue;
87
+ }
88
+ for (const entry of fs.readdirSync(subdirPath)) {
89
+ const pkgJsonPath = path4.join(subdirPath, entry, "package.json");
90
+ if (!fs.existsSync(pkgJsonPath)) {
91
+ continue;
92
+ }
93
+ try {
94
+ const json = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
95
+ if (json.name) {
96
+ names.push(json.name);
97
+ }
98
+ } catch {
99
+ }
100
+ }
101
+ }
102
+ return names;
103
+ }
104
+ function scanAllPackages(workspaceRoot, config) {
105
+ const results = [];
106
+ const topLevelDirs = ["platform", "plugins", "infra", "templates", "installer", "sites"];
107
+ for (const topDir of topLevelDirs) {
108
+ const topDirPath = path4.join(workspaceRoot, topDir);
109
+ if (!fs.existsSync(topDirPath)) {
110
+ continue;
111
+ }
112
+ for (const repoEntry of fs.readdirSync(topDirPath)) {
113
+ const repoPath = path4.join(topDirPath, repoEntry);
114
+ const repoRelPath = `${topDir}/${repoEntry}`;
115
+ let isDir;
116
+ try {
117
+ isDir = fs.statSync(repoPath).isDirectory();
118
+ } catch {
119
+ continue;
120
+ }
121
+ if (!isDir) {
122
+ continue;
123
+ }
124
+ const categoryResult = detectCategory(repoRelPath, config);
125
+ for (const subdir of ["packages", "apps"]) {
126
+ const subdirPath = path4.join(repoPath, subdir);
127
+ if (!fs.existsSync(subdirPath)) {
128
+ continue;
129
+ }
130
+ for (const pkgEntry of fs.readdirSync(subdirPath)) {
131
+ const pkgJsonPath = path4.join(subdirPath, pkgEntry, "package.json");
132
+ if (!fs.existsSync(pkgJsonPath)) {
133
+ continue;
134
+ }
135
+ try {
136
+ const json = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
137
+ if (json.name) {
138
+ results.push({
139
+ name: json.name,
140
+ repoPath: repoRelPath,
141
+ category: categoryResult.category,
142
+ packageJsonPath: pkgJsonPath
143
+ });
144
+ }
145
+ } catch {
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ return results;
152
+ }
153
+ async function checkSdkOnlyDeps(repoPath, _config, workspaceRoot) {
154
+ const violations = [];
155
+ const absRepoPath = path4.resolve(workspaceRoot, repoPath);
156
+ const internalPackageNames = new Set(getRepoPackageNames(workspaceRoot, repoPath));
157
+ const packageJsonPaths = findPackageJsonPaths(absRepoPath);
158
+ for (const pkgJsonPath of packageJsonPaths) {
159
+ let json;
160
+ try {
161
+ json = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
162
+ } catch {
163
+ continue;
164
+ }
165
+ const deps = Object.keys(json.dependencies ?? {});
166
+ const packageName = json.name ?? path4.basename(path4.dirname(pkgJsonPath));
167
+ const relPath = path4.relative(workspaceRoot, pkgJsonPath);
168
+ for (const dep of deps) {
169
+ if (!dep.startsWith("@kb-labs/")) {
170
+ continue;
171
+ }
172
+ if (dep === "@kb-labs/sdk") {
173
+ continue;
174
+ }
175
+ if (internalPackageNames.has(dep)) {
176
+ continue;
177
+ }
178
+ violations.push({
179
+ rule: "sdk-only-deps",
180
+ severity: "error",
181
+ message: `${packageName} imports ${dep} directly`,
182
+ package: packageName,
183
+ detail: `Plugins must depend only on @kb-labs/sdk. Move needed types to SDK or use SDK re-exports.`,
184
+ file: relPath
185
+ });
186
+ }
187
+ }
188
+ return violations;
189
+ }
190
+ function findPackageJsonPaths(absRepoPath) {
191
+ const results = [];
192
+ for (const subdir of ["packages", "apps"]) {
193
+ const subdirPath = path4.join(absRepoPath, subdir);
194
+ if (!fs.existsSync(subdirPath)) {
195
+ continue;
196
+ }
197
+ for (const entry of fs.readdirSync(subdirPath)) {
198
+ const pkgJsonPath = path4.join(subdirPath, entry, "package.json");
199
+ if (fs.existsSync(pkgJsonPath)) {
200
+ results.push(pkgJsonPath);
201
+ }
202
+ }
203
+ }
204
+ return results;
205
+ }
206
+ async function checkBoundary(repoPath, config, workspaceRoot) {
207
+ const violations = [];
208
+ const { detectCategory: detectCategory2 } = await Promise.resolve().then(() => (init_category_resolver(), category_resolver_exports));
209
+ const categoryResult = detectCategory2(repoPath, config);
210
+ const category = categoryResult.category;
211
+ if (!category) {
212
+ return violations;
213
+ }
214
+ const boundaryRuleConfig = config.rules["boundary-check"]?.config;
215
+ const allowedCategories = boundaryRuleConfig?.allowed?.[category] ?? [];
216
+ if (allowedCategories.includes("sdk-only")) {
217
+ return checkSdkOnlyDeps(repoPath, config, workspaceRoot);
218
+ }
219
+ const packageMap = buildPackageMap(workspaceRoot, config);
220
+ const internalPackageNames = new Set(getRepoPackageNames(workspaceRoot, repoPath));
221
+ const absRepoPath = path4.resolve(workspaceRoot, repoPath);
222
+ for (const pkgJsonPath of findPackageJsonPaths2(absRepoPath)) {
223
+ let json;
224
+ try {
225
+ json = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
226
+ } catch {
227
+ continue;
228
+ }
229
+ const deps = Object.keys(json.dependencies ?? {});
230
+ const packageName = json.name ?? path4.basename(path4.dirname(pkgJsonPath));
231
+ const relPath = path4.relative(workspaceRoot, pkgJsonPath);
232
+ for (const dep of deps) {
233
+ if (!dep.startsWith("@kb-labs/")) {
234
+ continue;
235
+ }
236
+ if (internalPackageNames.has(dep)) {
237
+ continue;
238
+ }
239
+ const depCategory = packageMap.get(dep);
240
+ if (depCategory === void 0 || depCategory === null) {
241
+ continue;
242
+ }
243
+ if (!allowedCategories.includes(depCategory)) {
244
+ violations.push({
245
+ rule: "boundary-check",
246
+ severity: "error",
247
+ message: `${packageName} depends on ${dep} (category: ${depCategory ?? "unknown"})`,
248
+ package: packageName,
249
+ detail: `Category "${category}" may only depend on: ${allowedCategories.join(", ")}`,
250
+ file: relPath
251
+ });
252
+ }
253
+ }
254
+ }
255
+ return violations;
256
+ }
257
+ function findPackageJsonPaths2(absRepoPath) {
258
+ const results = [];
259
+ for (const subdir of ["packages", "apps"]) {
260
+ const subdirPath = path4.join(absRepoPath, subdir);
261
+ if (!fs.existsSync(subdirPath)) {
262
+ continue;
263
+ }
264
+ for (const entry of fs.readdirSync(subdirPath)) {
265
+ const pkgJsonPath = path4.join(subdirPath, entry, "package.json");
266
+ if (fs.existsSync(pkgJsonPath)) {
267
+ results.push(pkgJsonPath);
268
+ }
269
+ }
270
+ }
271
+ return results;
272
+ }
273
+
274
+ // src/checks/symbol-extractor.ts
275
+ var RegexSymbolExtractor = class {
276
+ extract(dtsContent) {
277
+ const symbols = /* @__PURE__ */ new Set();
278
+ for (const m of dtsContent.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)/gm)) {
279
+ symbols.add(m[1]);
280
+ }
281
+ for (const m of dtsContent.matchAll(/^export\s+(?:abstract\s+)?class\s+(\w+)/gm)) {
282
+ symbols.add(m[1]);
283
+ }
284
+ for (const m of dtsContent.matchAll(/^export\s+(?:type\s+|interface\s+)(\w+)/gm)) {
285
+ symbols.add(m[1]);
286
+ }
287
+ for (const m of dtsContent.matchAll(/^export\s+(?:const|let|var)\s+(\w+)/gm)) {
288
+ symbols.add(m[1]);
289
+ }
290
+ for (const m of dtsContent.matchAll(/^export\s+(?:const\s+)?enum\s+(\w+)/gm)) {
291
+ symbols.add(m[1]);
292
+ }
293
+ for (const m of dtsContent.matchAll(/^export\s+\{([^}]+)\}/gm)) {
294
+ const entries = m[1].split(",").map((s) => s.trim());
295
+ for (const entry of entries) {
296
+ const asMatch = entry.match(/\w+\s+as\s+(\w+)/);
297
+ if (asMatch) {
298
+ symbols.add(asMatch[1]);
299
+ } else {
300
+ const nameMatch = entry.match(/^(\w+)/);
301
+ if (nameMatch) {
302
+ symbols.add(nameMatch[1]);
303
+ }
304
+ }
305
+ }
306
+ }
307
+ if (/^export\s+default\s+/m.test(dtsContent)) {
308
+ symbols.add("default");
309
+ }
310
+ return symbols;
311
+ }
312
+ };
313
+ var defaultSymbolExtractor = new RegexSymbolExtractor();
314
+
315
+ // src/checks/api-compat-check.ts
316
+ var SNAPSHOTS_DIR = ".kb/api-snapshots";
317
+ async function checkApiCompat(repoPath, _config, workspaceRoot) {
318
+ const violations = [];
319
+ const absRepoPath = path4.resolve(workspaceRoot, repoPath);
320
+ const packageJsonPaths = findPackageJsonPaths3(absRepoPath);
321
+ for (const pkgJsonPath of packageJsonPaths) {
322
+ let json;
323
+ try {
324
+ json = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
325
+ } catch {
326
+ continue;
327
+ }
328
+ if (!json.name || !json.version) {
329
+ continue;
330
+ }
331
+ const pkgName = json.name;
332
+ const currentVersion = json.version;
333
+ const snapshotPath = getSnapshotPath(workspaceRoot, pkgName);
334
+ const pkgDir = path4.dirname(pkgJsonPath);
335
+ const currentSymbols = extractSymbolsFromDist(pkgDir);
336
+ if (!fs.existsSync(snapshotPath)) {
337
+ console.warn(
338
+ `[policy] api-compat: No snapshot for ${pkgName} \u2014 run 'policy update-snapshots' to create one.`
339
+ );
340
+ continue;
341
+ }
342
+ let snapshot;
343
+ try {
344
+ snapshot = JSON.parse(fs.readFileSync(snapshotPath, "utf-8"));
345
+ } catch {
346
+ console.warn(`[policy] api-compat: Could not read snapshot for ${pkgName}, skipping.`);
347
+ continue;
348
+ }
349
+ const snapshotSymbols = new Set(snapshot.symbols);
350
+ const relPath = path4.relative(workspaceRoot, pkgJsonPath);
351
+ const removed = [];
352
+ for (const sym of snapshotSymbols) {
353
+ if (!currentSymbols.has(sym)) {
354
+ removed.push(sym);
355
+ }
356
+ }
357
+ if (removed.length > 0) {
358
+ const currentMajor = parseMajor(currentVersion);
359
+ const snapshotMajor = parseMajor(snapshot.version);
360
+ if (currentMajor <= snapshotMajor) {
361
+ violations.push({
362
+ rule: "api-compat-check",
363
+ severity: "error",
364
+ message: `${pkgName} removed exported symbols without major version bump`,
365
+ package: pkgName,
366
+ detail: `Removed: ${removed.join(", ")}. Bump major version (current: ${currentVersion}, snapshot: ${snapshot.version}) or restore symbols.`,
367
+ file: relPath
368
+ });
369
+ }
370
+ }
371
+ }
372
+ return violations;
373
+ }
374
+ function extractSymbolsFromDist(pkgDir) {
375
+ const distDir = path4.join(pkgDir, "dist");
376
+ const symbols = /* @__PURE__ */ new Set();
377
+ if (!fs.existsSync(distDir)) {
378
+ return symbols;
379
+ }
380
+ const dtsFiles = findDtsFiles(distDir);
381
+ for (const dtsFile of dtsFiles) {
382
+ const content = fs.readFileSync(dtsFile, "utf-8");
383
+ for (const sym of defaultSymbolExtractor.extract(content)) {
384
+ symbols.add(sym);
385
+ }
386
+ }
387
+ return symbols;
388
+ }
389
+ function findDtsFiles(dir) {
390
+ const results = [];
391
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
392
+ const fullPath = path4.join(dir, entry.name);
393
+ if (entry.isDirectory()) {
394
+ results.push(...findDtsFiles(fullPath));
395
+ } else if (entry.name.endsWith(".d.ts")) {
396
+ results.push(fullPath);
397
+ }
398
+ }
399
+ return results;
400
+ }
401
+ function getSnapshotPath(workspaceRoot, packageName) {
402
+ const safeName = packageName.replace(/\//g, "__").replace(/@/g, "");
403
+ const snapshotsDir = path4.join(workspaceRoot, SNAPSHOTS_DIR);
404
+ if (!fs.existsSync(snapshotsDir)) {
405
+ fs.mkdirSync(snapshotsDir, { recursive: true });
406
+ }
407
+ return path4.join(snapshotsDir, `${safeName}.json`);
408
+ }
409
+ function parseMajor(version) {
410
+ return parseInt(version.split(".")[0] ?? "0", 10);
411
+ }
412
+ function findPackageJsonPaths3(absRepoPath) {
413
+ const results = [];
414
+ for (const subdir of ["packages", "apps"]) {
415
+ const subdirPath = path4.join(absRepoPath, subdir);
416
+ if (!fs.existsSync(subdirPath)) {
417
+ continue;
418
+ }
419
+ for (const entry of fs.readdirSync(subdirPath)) {
420
+ const pkgJsonPath = path4.join(subdirPath, entry, "package.json");
421
+ if (fs.existsSync(pkgJsonPath)) {
422
+ results.push(pkgJsonPath);
423
+ }
424
+ }
425
+ }
426
+ return results;
427
+ }
428
+ async function checkNoRollback(repoPath, _config, workspaceRoot) {
429
+ const violations = [];
430
+ const absRepoPath = path4.resolve(workspaceRoot, repoPath);
431
+ const packageJsonPaths = findPackageJsonPaths4(absRepoPath);
432
+ for (const pkgJsonPath of packageJsonPaths) {
433
+ let json;
434
+ try {
435
+ json = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
436
+ } catch {
437
+ continue;
438
+ }
439
+ if (!json.name || !json.version) {
440
+ continue;
441
+ }
442
+ if (json.private) {
443
+ continue;
444
+ }
445
+ const packageName = json.name;
446
+ const currentVersion = json.version;
447
+ const relPath = path4.relative(workspaceRoot, pkgJsonPath);
448
+ const publishedVersion = getPublishedVersion(packageName);
449
+ if (publishedVersion === null) {
450
+ continue;
451
+ }
452
+ if (!semver.gte(currentVersion, publishedVersion)) {
453
+ violations.push({
454
+ rule: "no-rollback",
455
+ severity: "error",
456
+ message: `${packageName} version ${currentVersion} is less than published ${publishedVersion}`,
457
+ package: packageName,
458
+ detail: `Cannot decrease version once published to npm. Restore to ${publishedVersion} or higher.`,
459
+ file: relPath
460
+ });
461
+ }
462
+ }
463
+ return violations;
464
+ }
465
+ function getPublishedVersion(packageName) {
466
+ const result = spawnSync("npm", ["show", packageName, "version", "--silent"], {
467
+ timeout: 1e4,
468
+ encoding: "utf-8",
469
+ stdio: ["ignore", "pipe", "ignore"]
470
+ });
471
+ if (result.error || result.status !== 0) {
472
+ return null;
473
+ }
474
+ return semver.valid(result.stdout.trim()) ?? null;
475
+ }
476
+ function findPackageJsonPaths4(absRepoPath) {
477
+ const results = [];
478
+ for (const subdir of ["packages", "apps"]) {
479
+ const subdirPath = path4.join(absRepoPath, subdir);
480
+ if (!fs.existsSync(subdirPath)) {
481
+ continue;
482
+ }
483
+ for (const entry of fs.readdirSync(subdirPath)) {
484
+ const pkgJsonPath = path4.join(subdirPath, entry, "package.json");
485
+ if (fs.existsSync(pkgJsonPath)) {
486
+ results.push(pkgJsonPath);
487
+ }
488
+ }
489
+ }
490
+ return results;
491
+ }
492
+
493
+ // src/core/policy-runner.ts
494
+ var RULE_CHECKS = {
495
+ "sdk-only-deps": checkSdkOnlyDeps,
496
+ "boundary-check": checkBoundary,
497
+ "no-breaking-without-major": checkApiCompat,
498
+ "no-rollback": checkNoRollback,
499
+ "api-compat-check": checkApiCompat
500
+ };
501
+ async function runChecks(repos, config, workspaceRoot) {
502
+ const repoResults = [];
503
+ for (const repo of repos) {
504
+ const violations = [];
505
+ const passed = [];
506
+ const rulesToRun = repo.rules;
507
+ for (const rule of rulesToRun) {
508
+ const checkFn = RULE_CHECKS[rule];
509
+ if (!checkFn) {
510
+ console.warn(`[policy] Unknown rule: ${rule} (skipping)`);
511
+ continue;
512
+ }
513
+ let ruleViolations;
514
+ try {
515
+ ruleViolations = await checkFn(repo.path, config, workspaceRoot);
516
+ } catch (err) {
517
+ console.warn(`[policy] Rule '${rule}' threw an unexpected error (skipping): ${err.message}`);
518
+ continue;
519
+ }
520
+ if (ruleViolations.length === 0) {
521
+ passed.push(rule);
522
+ } else {
523
+ violations.push(...ruleViolations.map((v) => ({ ...v, rule })));
524
+ }
525
+ }
526
+ repoResults.push({
527
+ path: repo.path,
528
+ category: repo.category,
529
+ violations,
530
+ passed
531
+ });
532
+ }
533
+ const totalViolations = repoResults.reduce((sum, r) => sum + r.violations.length, 0);
534
+ const failedRepos = repoResults.filter((r) => r.violations.length > 0).length;
535
+ return {
536
+ passed: totalViolations === 0,
537
+ repos: repoResults,
538
+ summary: {
539
+ total: repos.length,
540
+ passed: repos.length - failedRepos,
541
+ failed: failedRepos,
542
+ violations: totalViolations
543
+ }
544
+ };
545
+ }
546
+ var pluginPermissions = combinePermissions().withFs({
547
+ mode: "read",
548
+ allow: [".", ".kb/api-snapshots/**"]
549
+ }).withQuotas({
550
+ timeoutMs: 6e4,
551
+ memoryMb: 256
552
+ }).build();
553
+ var detectPermissions = combinePermissions().withFs({ mode: "read", allow: ["."] }).withQuotas({ timeoutMs: 15e3, memoryMb: 128 }).build();
554
+ var checkPermissions = combinePermissions().withFs({
555
+ mode: "read",
556
+ allow: [".", ".kb/api-snapshots/**"]
557
+ }).withFs({
558
+ mode: "readWrite",
559
+ allow: [".kb/api-snapshots/**"]
560
+ }).withQuotas({ timeoutMs: 6e4, memoryMb: 256 }).build();
561
+ var snapshotPermissions = combinePermissions().withFs({ mode: "read", allow: ["."] }).withFs({ mode: "readWrite", allow: [".kb/api-snapshots/**"] }).withQuotas({ timeoutMs: 3e4, memoryMb: 128 }).build();
562
+ var manifest = {
563
+ schema: "kb.plugin/3",
564
+ id: "@kb-labs/policy",
565
+ version: "0.1.0",
566
+ configSection: "policy",
567
+ display: {
568
+ name: "Policy Enforcer",
569
+ description: "Enforces workspace-level development policies per category. Detects repo categories, resolves applicable rules, and validates compliance.",
570
+ tags: ["policy", "governance", "boundaries", "sdk-only", "api-compat"]
571
+ },
572
+ cli: {
573
+ commands: [
574
+ {
575
+ id: "policy:detect",
576
+ group: "policy",
577
+ describe: "Detect policy category for changed or specified repos",
578
+ longDescription: "Determines the category for repos based on git changes or a specified path, then resolves applicable policy rules.",
579
+ handler: "./cli/commands/detect.js#default",
580
+ handlerPath: "./cli/commands/detect.js",
581
+ flags: defineCommandFlags({
582
+ path: {
583
+ type: "string",
584
+ description: "Repo path to check (relative to workspace root). Defaults to git diff."
585
+ },
586
+ json: {
587
+ type: "boolean",
588
+ description: "Output as JSON",
589
+ default: false
590
+ }
591
+ }),
592
+ permissions: detectPermissions
593
+ },
594
+ {
595
+ id: "policy:check",
596
+ group: "policy",
597
+ describe: "Run policy checks for changed repos or a specific path",
598
+ longDescription: "Runs all applicable policy rules for detected repos. Exits with code 1 on violations. Use in CI or pnpm done pipeline.",
599
+ handler: "./cli/commands/check.js#default",
600
+ handlerPath: "./cli/commands/check.js",
601
+ flags: defineCommandFlags({
602
+ path: {
603
+ type: "string",
604
+ description: "Repo path to check (relative to workspace root). Defaults to git diff."
605
+ },
606
+ json: {
607
+ type: "boolean",
608
+ description: "Output as JSON",
609
+ default: false
610
+ }
611
+ }),
612
+ permissions: checkPermissions
613
+ },
614
+ {
615
+ id: "policy:rules",
616
+ group: "policy",
617
+ describe: "Show all configured policy rules and their categories",
618
+ handler: "./cli/commands/rules.js#default",
619
+ handlerPath: "./cli/commands/rules.js",
620
+ flags: defineCommandFlags({
621
+ json: {
622
+ type: "boolean",
623
+ description: "Output as JSON",
624
+ default: false
625
+ }
626
+ }),
627
+ permissions: detectPermissions
628
+ },
629
+ {
630
+ id: "policy:snapshot",
631
+ group: "policy",
632
+ describe: "Create or update API snapshot for a repo (run after npm publish)",
633
+ longDescription: "Extracts exported symbols from dist/*.d.ts files and saves them to .kb/api-snapshots/. Used by api-compat-check to detect breaking changes.",
634
+ handler: "./cli/commands/snapshot.js#default",
635
+ handlerPath: "./cli/commands/snapshot.js",
636
+ flags: defineCommandFlags({
637
+ path: {
638
+ type: "string",
639
+ description: "Repo path (relative to workspace root)",
640
+ required: true
641
+ }
642
+ }),
643
+ permissions: snapshotPermissions
644
+ }
645
+ ]
646
+ },
647
+ permissions: pluginPermissions
648
+ };
649
+
650
+ export { buildPackageMap, detectCategories, detectCategory, getRepoPackageNames, manifest, runChecks };
651
+ //# sourceMappingURL=index.js.map
652
+ //# sourceMappingURL=index.js.map