@longzai-intelligence-issues/ledger 0.0.3 → 0.0.5

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.
@@ -19,6 +19,7 @@ import {
19
19
  type NumberingAuditFinding,
20
20
  type NumberingFinding,
21
21
  type NumberingKeyRule,
22
+ reconcileSlugDigitAllowlist,
22
23
  scanScopeForFindings,
23
24
  } from './numbering.core.js';
24
25
 
@@ -263,6 +264,10 @@ export type NumberingCheckCommandInput = {
263
264
  * 目录增量档:只判覆盖 scope,豁免按目录过滤(防其他目录条目误判 stale),
264
265
  * 不跑 slug 白名单失效对账(扫描面不完整防假阳性)。
265
266
  *
267
+ * 全量档:slug 白名单失效对账在全部 scope 扫描完毕后跨 scope 聚合命中集统一
268
+ * 判定——白名单是根级全局登记,条目在任何 scope 命中即非 stale,逐 scope 各自
269
+ * 对账会对其余 scope 产生误报。
270
+ *
266
271
  * @param input - 命令入参
267
272
  * @returns 检查结果
268
273
  */
@@ -285,6 +290,16 @@ export function runNumberingCheck(input: NumberingCheckCommandInput): NumberingC
285
290
  */
286
291
  let scannedFiles = 0;
287
292
 
293
+ /**
294
+ * 根级全局 slug 白名单 glob 全集(逐 scope 传入匹配 + 末尾统一对账)
295
+ */
296
+ const slugDigitAllowlistGlobs = input.config.slugDigitAllowlist.map((entry) => entry.glob);
297
+
298
+ /**
299
+ * 跨 scope 聚合的 slug 白名单命中集(任一 scope 命中即存活)
300
+ */
301
+ const slugDigitAllowlistHits = new Set<string>();
302
+
288
303
  for (const scope of scopes) {
289
304
  /**
290
305
  * 注册表文件名列表
@@ -309,17 +324,32 @@ export function runNumberingCheck(input: NumberingCheckCommandInput): NumberingC
309
324
  )
310
325
  .map((entry) => ({ ...entry, files: [...entry.files] }));
311
326
 
327
+ /**
328
+ * 单 scope 扫描结果(违例 + slug 白名单命中集)
329
+ */
330
+ const scan = scanScopeForFindings({
331
+ scopeKey: scope.key,
332
+ basenames,
333
+ rule: ISSUES_KEY_RULE,
334
+ reserved: scope.numbering.reserved,
335
+ allowlist,
336
+ slugDigitPolicy: scope.slugDigitPolicy,
337
+ slugDigitAllowlist: slugDigitAllowlistGlobs,
338
+ toRepoRelative: (basename) => `${scope.registryDir}/${basename}`,
339
+ });
340
+
341
+ findings.push(...scan.findings);
342
+
343
+ for (const glob of scan.slugDigitAllowlistHits) {
344
+ slugDigitAllowlistHits.add(glob);
345
+ }
346
+ }
347
+
348
+ if (input.dirRel === undefined) {
312
349
  findings.push(
313
- ...scanScopeForFindings({
314
- scopeKey: scope.key,
315
- basenames,
316
- rule: ISSUES_KEY_RULE,
317
- reserved: scope.numbering.reserved,
318
- allowlist,
319
- slugDigitPolicy: scope.slugDigitPolicy,
320
- slugDigitAllowlist: input.config.slugDigitAllowlist.map((entry) => entry.glob),
321
- toRepoRelative: (basename) => `${scope.registryDir}/${basename}`,
322
- fullMode: input.dirRel === undefined,
350
+ ...reconcileSlugDigitAllowlist({
351
+ allowlist: slugDigitAllowlistGlobs,
352
+ hits: slugDigitAllowlistHits,
323
353
  }),
324
354
  );
325
355
  }
@@ -2,8 +2,9 @@
2
2
  * 编号门禁核心(纯函数,零 fs)
3
3
  *
4
4
  * apex numbering-guard 语义迁移:四类键型提取、取号(max+1 顺延跳已占与保留、
5
- * 宽度随最大键)、五类违例扫描(目录=编号命名空间)、slug 数字策略、覆盖面
6
- * 审计。文件系统编排由 numbering.commands 承担,本模块可独立测试。
5
+ * 宽度随最大键)、五类违例扫描(目录=编号命名空间)、slug 数字策略、slug 白名单
6
+ * 失效对账(跨 scope 聚合口径)、覆盖面审计。文件系统编排由 numbering.commands
7
+ * 承担,本模块可独立测试。
7
8
  */
8
9
 
9
10
  import { matchMiniGlob } from '@/fs/mini-glob.utils';
@@ -338,11 +339,23 @@ export type ScopeScanInput = {
338
339
  * 文件仓库相对路径构造器(slug 白名单匹配口径)
339
340
  */
340
341
  toRepoRelative: (basename: string) => string;
342
+ };
343
+
344
+ /**
345
+ * 单 scope 扫描结果
346
+ */
347
+ export type ScopeScanResult = {
348
+ /**
349
+ * 违例列表
350
+ */
351
+ findings: NumberingFinding[];
341
352
 
342
353
  /**
343
- * 全量档标记(true 时追加 slug 白名单失效对账)
354
+ * slug 白名单命中 glob 列表(本 scope 启用域内实匹配到含数字 slug 文件的条目;
355
+ * 全局条目只在其文件所属 scope 命中——失效对账须由调用方跨全部 scope 聚合
356
+ * 命中集后经 reconcileSlugDigitAllowlist 统一判定,单 scope 命中集不构成全局口径)
344
357
  */
345
- fullMode: boolean;
358
+ slugDigitAllowlistHits: string[];
346
359
  };
347
360
 
348
361
  /**
@@ -374,12 +387,13 @@ function sortedEqual(left: readonly string[], right: readonly string[]): boolean
374
387
  * 单 scope 违例扫描(目录=编号命名空间)
375
388
  *
376
389
  * 判定顺序:先撞号组(豁免须排序文件集双向相等精确覆盖)后保留号;全部组判
377
- * 完后逐豁免条目对账(棘轮只许缩短)。
390
+ * 完后逐豁免条目对账(棘轮只许缩短)。slug 白名单命中集随结果返回,失效对账
391
+ * 不在本函数内做——全局条目跨 scope 存活,须由调用方聚合后统一判定。
378
392
  *
379
393
  * @param input - 扫描入参
380
- * @returns 违例列表
394
+ * @returns 扫描结果(违例列表 + slug 白名单命中集)
381
395
  */
382
- export function scanScopeForFindings(input: ScopeScanInput): NumberingFinding[] {
396
+ export function scanScopeForFindings(input: ScopeScanInput): ScopeScanResult {
383
397
  /**
384
398
  * 违例收集器
385
399
  */
@@ -503,12 +517,12 @@ export function scanScopeForFindings(input: ScopeScanInput): NumberingFinding[]
503
517
  });
504
518
  });
505
519
 
506
- if (input.slugDigitPolicy !== 'allow') {
507
- /**
508
- * slug 白名单命中集合(全量档失效对账用)
509
- */
510
- const allowlistHits = new Set<string>();
520
+ /**
521
+ * slug 白名单命中 glob 收集器(跨 scope 失效对账聚合口径由调用方承担)
522
+ */
523
+ const slugDigitAllowlistHits: string[] = [];
511
524
 
525
+ if (input.slugDigitPolicy !== 'allow') {
512
526
  for (const basename of input.basenames) {
513
527
  /**
514
528
  * slug 段
@@ -529,7 +543,7 @@ export function scanScopeForFindings(input: ScopeScanInput): NumberingFinding[]
529
543
  */
530
544
  const whitelisted = input.slugDigitAllowlist.some((glob) => {
531
545
  if (matchMiniGlob(glob, repoRelative)) {
532
- allowlistHits.add(glob);
546
+ slugDigitAllowlistHits.push(glob);
533
547
 
534
548
  return true;
535
549
  }
@@ -545,20 +559,51 @@ export function scanScopeForFindings(input: ScopeScanInput): NumberingFinding[]
545
559
  });
546
560
  }
547
561
  }
562
+ }
548
563
 
549
- if (input.fullMode) {
550
- /**
551
- * 白名单失效对账(policy 启用域内命中不到任何文件即失效)
552
- */
553
- for (const glob of input.slugDigitAllowlist) {
554
- if (!allowlistHits.has(glob)) {
555
- findings.push({
556
- kind: 'slugDigitAllowlistStale',
557
- scopeKey: 'global',
558
- message: `slug 白名单条目「${glob}」在启用域内已匹配不到任何文件——棘轮只许缩短,须同步删除该条目`,
559
- });
560
- }
561
- }
564
+ return { findings, slugDigitAllowlistHits };
565
+ }
566
+
567
+ /**
568
+ * slug 白名单失效对账入参
569
+ */
570
+ export type SlugDigitAllowlistReconcileInput = {
571
+ /**
572
+ * slug 数字白名单 glob 全集(根级全局登记,逐条判定)
573
+ */
574
+ allowlist: readonly string[];
575
+
576
+ /**
577
+ * 跨全部 slugDigitPolicy 启用域聚合的命中 glob 集(任一 scope 命中即非失效)
578
+ */
579
+ hits: ReadonlySet<string>;
580
+ };
581
+
582
+ /**
583
+ * slug 白名单失效对账(跨 scope 聚合口径)
584
+ *
585
+ * 白名单是根级全局登记而命中发生在各 scope 扫描面内:条目在其文件所属 scope
586
+ * 命中即存活,其余 scope 不得重复判定——命中集必须由调用方聚合全部 scope 后
587
+ * 传入。逐条对账,任何启用域内命中不到含数字 slug 文件即失效(棘轮只许缩短)。
588
+ *
589
+ * @param input - 对账入参
590
+ * @returns 违例列表
591
+ */
592
+ export function reconcileSlugDigitAllowlist(
593
+ input: SlugDigitAllowlistReconcileInput,
594
+ ): NumberingFinding[] {
595
+ /**
596
+ * 违例收集器
597
+ */
598
+ const findings: NumberingFinding[] = [];
599
+
600
+ for (const glob of input.allowlist) {
601
+ if (!input.hits.has(glob)) {
602
+ findings.push({
603
+ kind: 'slugDigitAllowlistStale',
604
+ scopeKey: 'global',
605
+ message: `slug 白名单条目「${glob}」在启用域内已匹配不到任何文件——棘轮只许缩短,须同步删除该条目`,
606
+ });
562
607
  }
563
608
  }
564
609