@longzai-intelligence-issues/ledger 0.0.4 → 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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@
5
5
  格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
6
6
  本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
7
7
 
8
+ ## [0.0.5] - 2026-09-14
9
+
10
+ ### 修复
11
+
12
+ - 修复 numbering check 在多 forbid scope 仓中对根级全局 slugDigitAllowlist 条目的 slugDigitAllowlistStale 误报:失效对账命中集改为命令层跨全部 scope 聚合后统一判定,任一 scope 命中即非 stale
13
+
8
14
  ## [0.0.4] - 2026-09-14
9
15
 
10
16
  ### 变更
package/dist/index.d.ts CHANGED
@@ -1917,8 +1917,9 @@ declare function backfillRequiredFields(input: BackfillInput): BackfillResult;
1917
1917
  * 编号门禁核心(纯函数,零 fs)
1918
1918
  *
1919
1919
  * apex numbering-guard 语义迁移:四类键型提取、取号(max+1 顺延跳已占与保留、
1920
- * 宽度随最大键)、五类违例扫描(目录=编号命名空间)、slug 数字策略、覆盖面
1921
- * 审计。文件系统编排由 numbering.commands 承担,本模块可独立测试。
1920
+ * 宽度随最大键)、五类违例扫描(目录=编号命名空间)、slug 数字策略、slug 白名单
1921
+ * 失效对账(跨 scope 聚合口径)、覆盖面审计。文件系统编排由 numbering.commands
1922
+ * 承担,本模块可独立测试。
1922
1923
  */
1923
1924
  /**
1924
1925
  * 编号键规则(判别联合;v1 issues 注册表固定 numericPrefix,其余键型供通用引擎使用)
@@ -2101,21 +2102,57 @@ type ScopeScanInput = {
2101
2102
  * 文件仓库相对路径构造器(slug 白名单匹配口径)
2102
2103
  */
2103
2104
  toRepoRelative: (basename: string) => string;
2105
+ };
2106
+ /**
2107
+ * 单 scope 扫描结果
2108
+ */
2109
+ type ScopeScanResult = {
2104
2110
  /**
2105
- * 全量档标记(true 时追加 slug 白名单失效对账)
2111
+ * 违例列表
2106
2112
  */
2107
- fullMode: boolean;
2113
+ findings: NumberingFinding[];
2114
+ /**
2115
+ * slug 白名单命中 glob 列表(本 scope 启用域内实匹配到含数字 slug 文件的条目;
2116
+ * 全局条目只在其文件所属 scope 命中——失效对账须由调用方跨全部 scope 聚合
2117
+ * 命中集后经 reconcileSlugDigitAllowlist 统一判定,单 scope 命中集不构成全局口径)
2118
+ */
2119
+ slugDigitAllowlistHits: string[];
2108
2120
  };
2109
2121
  /**
2110
2122
  * 单 scope 违例扫描(目录=编号命名空间)
2111
2123
  *
2112
2124
  * 判定顺序:先撞号组(豁免须排序文件集双向相等精确覆盖)后保留号;全部组判
2113
- * 完后逐豁免条目对账(棘轮只许缩短)。
2125
+ * 完后逐豁免条目对账(棘轮只许缩短)。slug 白名单命中集随结果返回,失效对账
2126
+ * 不在本函数内做——全局条目跨 scope 存活,须由调用方聚合后统一判定。
2114
2127
  *
2115
2128
  * @param input - 扫描入参
2129
+ * @returns 扫描结果(违例列表 + slug 白名单命中集)
2130
+ */
2131
+ declare function scanScopeForFindings(input: ScopeScanInput): ScopeScanResult;
2132
+ /**
2133
+ * slug 白名单失效对账入参
2134
+ */
2135
+ type SlugDigitAllowlistReconcileInput = {
2136
+ /**
2137
+ * slug 数字白名单 glob 全集(根级全局登记,逐条判定)
2138
+ */
2139
+ allowlist: readonly string[];
2140
+ /**
2141
+ * 跨全部 slugDigitPolicy 启用域聚合的命中 glob 集(任一 scope 命中即非失效)
2142
+ */
2143
+ hits: ReadonlySet<string>;
2144
+ };
2145
+ /**
2146
+ * slug 白名单失效对账(跨 scope 聚合口径)
2147
+ *
2148
+ * 白名单是根级全局登记而命中发生在各 scope 扫描面内:条目在其文件所属 scope
2149
+ * 命中即存活,其余 scope 不得重复判定——命中集必须由调用方聚合全部 scope 后
2150
+ * 传入。逐条对账,任何启用域内命中不到含数字 slug 文件即失效(棘轮只许缩短)。
2151
+ *
2152
+ * @param input - 对账入参
2116
2153
  * @returns 违例列表
2117
2154
  */
2118
- declare function scanScopeForFindings(input: ScopeScanInput): NumberingFinding[];
2155
+ declare function reconcileSlugDigitAllowlist(input: SlugDigitAllowlistReconcileInput): NumberingFinding[];
2119
2156
  /**
2120
2157
  * 覆盖面审计入参
2121
2158
  */
@@ -2270,6 +2307,10 @@ type NumberingCheckCommandInput = {
2270
2307
  * 目录增量档:只判覆盖 scope,豁免按目录过滤(防其他目录条目误判 stale),
2271
2308
  * 不跑 slug 白名单失效对账(扫描面不完整防假阳性)。
2272
2309
  *
2310
+ * 全量档:slug 白名单失效对账在全部 scope 扫描完毕后跨 scope 聚合命中集统一
2311
+ * 判定——白名单是根级全局登记,条目在任何 scope 命中即非 stale,逐 scope 各自
2312
+ * 对账会对其余 scope 产生误报。
2313
+ *
2273
2314
  * @param input - 命令入参
2274
2315
  * @returns 检查结果
2275
2316
  */
@@ -2493,4 +2534,4 @@ type TriageClassification = {
2493
2534
  */
2494
2535
  declare function classifyIssue(record: IssueDocRecord): TriageClassification;
2495
2536
  //#endregion
2496
- export { ADJUDICATION_KEYWORD, ASCII_KEBAB_SLUG_PATTERN, AuditCoverageInput, BLOCKING_LINE_KEY, BackfillInput, BackfillResult, CANONICAL_STATUS_LABELS, CORE_LINT_RULES, CRITERIA_CHANNEL_PATTERN, CRITERIA_FENCE_CLOSE_PATTERN, CRITERIA_FENCE_OPEN_PATTERN, CloseBatchResult, CloseCaseResult, CloseGateResult, Criterion, CriterionCertificate, CriterionRunResult, DISPOSITION_CANDIDATES, DISPOSITION_PLACEHOLDER_PATTERN, DISPOSITION_PLACEHOLDER_WINDOW_LINES, DISPOSITION_TITLE_KEYWORD, DocRefCheckResult, DocRefViolation, DocsBaselineInput, DocsBaselineResult, DocsCheckInput, DocsFixInput, DocsFixResult, EVIDENCE_ANCHOR_MARKERS, EvidenceAnchor, EvidenceAnchorKind, EvidenceCloseInput, EvidenceReadResult, FreezeIssueInput, FreezeResult, GUIDE_FIXTURE_END_MARK, GUIDE_FIXTURE_START_MARK, GreenFlipInput, GreenFlipResult, HEADING_ALONE_METADATA_KEYS, HISTORY_LINE_KEY, HealthReport, ISSUES_KEY_RULE, ISSUE_FILE_FORMAT_GUIDE_RELATIVE, ISSUE_REF_TOKEN_PATTERN, IssueDocRecord, IssueRefToken, IssueStatusKind, IssueTemplatePriority, IssueTemplateRef, IssueTemplateValues, LEGACY_STATUS_VOCABULARY, LintResult, LintRule, LintRuleContext, LintViolation, META_LINE_PREFIX_SOURCE_TEMPLATE, MigrateCoreOptions, MigrateCoreResult, MigrateLegacyFileInput, MigrateLegacyFileResult, MigrationTransformation, MiniGlobError, NextNumberInput, NextNumberResult, NormalizeHeaderFileInput, NormalizeHeaderOptions, NormalizeHeaderResult, NormalizeOutcome, NormalizeResult, NumberingAllowlistEntry, NumberingAuditCommandInput, NumberingAuditFinding, NumberingAuditFindingKind, NumberingAuditResult, NumberingCheckCommandInput, NumberingCheckResult, NumberingFinding, NumberingFindingKind, NumberingKeyRule, NumberingNextCommandInput, NumberingNextResult, PENDING_REASON_PATTERN, ParsedCriteriaBlock, ParsedCriterion, ParsedHeader, PromptEntry, PromptsOptions, REGISTRY_FILE_PATTERN, RegistryFileNameParts, RerunCloseInput, RunCriterionInput, RunHealthInput, RunLintInput, RunLockHandle, RunTriageInput, RunTriageResult, STATUS_LINE_LOCATE_PATTERN, STATUS_LINE_PREFIX_SOURCE, STATUS_LINE_SCAN_LINES, ScopeScanInput, TEMPLATE_FIELDS, TemplateFieldDoc, TriageCategory, TriageClassification, TriageItem, TriageLedgerFile, UnfreezeIssueInput, WalkOptions, WriteLintBaselineInput, WriteLintBaselineResult, acquireRunLock, applyGreenFlip, auditCoverage, backfillRequiredFields, buildMetaLineLocatePattern, canonicalizeH1, canonicalizeStatusValue, certificatePath, classifyIssue, classifyStatusLine, cleanValue, compileMiniGlob, computeCriterionFingerprint, computeNextNumber, deriveEntriesFromViolations, deriveWalkPrefixes, evidenceCloseFlow, extractFileKey, extractIssueRefTokens, extractSlugSegment, findCertificate, findPendingWord, freezeIssue, keyLeadingNumber, listPrompts, loadIssueFileFormatGuide, locateDispositionText, locateIssueFileFormatGuide, mapPriorityValue, matchMiniGlob, migrateLegacyCore, migrateLegacyFile, normalizeHeader, normalizeHeaderFile, parseCriteriaBlock, parseHeaderDialect, parseIssueDoc, parseRegistryFileName, readEvidence, readEvidenceAnchors, readPendingReason, registerLintRulePack, renderCriteriaBlockExample, renderEvidenceCloseBlock, renderIssueTemplate, renderIssueTemplateSkeleton, renderRegistryReadmeStub, renderRerunEvidenceBlock, rerunCloseFlow, resolveCloseGates, runCriterionViaVault, runDocsCheck, runDocsFix, runHealth, runLint, runNumberingAudit, runNumberingCheck, runNumberingNext, runTriage, runWriteDocsBaseline, runWriteLintBaseline, scanOrphanSidecars, scanRegistryDir, scanScopeForFindings, showPrompt, sliceGuideFixtureSample, unfreezeIssue, walkFilesUnderPrefixes, walkFilesUnderRoots };
2537
+ export { ADJUDICATION_KEYWORD, ASCII_KEBAB_SLUG_PATTERN, AuditCoverageInput, BLOCKING_LINE_KEY, BackfillInput, BackfillResult, CANONICAL_STATUS_LABELS, CORE_LINT_RULES, CRITERIA_CHANNEL_PATTERN, CRITERIA_FENCE_CLOSE_PATTERN, CRITERIA_FENCE_OPEN_PATTERN, CloseBatchResult, CloseCaseResult, CloseGateResult, Criterion, CriterionCertificate, CriterionRunResult, DISPOSITION_CANDIDATES, DISPOSITION_PLACEHOLDER_PATTERN, DISPOSITION_PLACEHOLDER_WINDOW_LINES, DISPOSITION_TITLE_KEYWORD, DocRefCheckResult, DocRefViolation, DocsBaselineInput, DocsBaselineResult, DocsCheckInput, DocsFixInput, DocsFixResult, EVIDENCE_ANCHOR_MARKERS, EvidenceAnchor, EvidenceAnchorKind, EvidenceCloseInput, EvidenceReadResult, FreezeIssueInput, FreezeResult, GUIDE_FIXTURE_END_MARK, GUIDE_FIXTURE_START_MARK, GreenFlipInput, GreenFlipResult, HEADING_ALONE_METADATA_KEYS, HISTORY_LINE_KEY, HealthReport, ISSUES_KEY_RULE, ISSUE_FILE_FORMAT_GUIDE_RELATIVE, ISSUE_REF_TOKEN_PATTERN, IssueDocRecord, IssueRefToken, IssueStatusKind, IssueTemplatePriority, IssueTemplateRef, IssueTemplateValues, LEGACY_STATUS_VOCABULARY, LintResult, LintRule, LintRuleContext, LintViolation, META_LINE_PREFIX_SOURCE_TEMPLATE, MigrateCoreOptions, MigrateCoreResult, MigrateLegacyFileInput, MigrateLegacyFileResult, MigrationTransformation, MiniGlobError, NextNumberInput, NextNumberResult, NormalizeHeaderFileInput, NormalizeHeaderOptions, NormalizeHeaderResult, NormalizeOutcome, NormalizeResult, NumberingAllowlistEntry, NumberingAuditCommandInput, NumberingAuditFinding, NumberingAuditFindingKind, NumberingAuditResult, NumberingCheckCommandInput, NumberingCheckResult, NumberingFinding, NumberingFindingKind, NumberingKeyRule, NumberingNextCommandInput, NumberingNextResult, PENDING_REASON_PATTERN, ParsedCriteriaBlock, ParsedCriterion, ParsedHeader, PromptEntry, PromptsOptions, REGISTRY_FILE_PATTERN, RegistryFileNameParts, RerunCloseInput, RunCriterionInput, RunHealthInput, RunLintInput, RunLockHandle, RunTriageInput, RunTriageResult, STATUS_LINE_LOCATE_PATTERN, STATUS_LINE_PREFIX_SOURCE, STATUS_LINE_SCAN_LINES, ScopeScanInput, ScopeScanResult, SlugDigitAllowlistReconcileInput, TEMPLATE_FIELDS, TemplateFieldDoc, TriageCategory, TriageClassification, TriageItem, TriageLedgerFile, UnfreezeIssueInput, WalkOptions, WriteLintBaselineInput, WriteLintBaselineResult, acquireRunLock, applyGreenFlip, auditCoverage, backfillRequiredFields, buildMetaLineLocatePattern, canonicalizeH1, canonicalizeStatusValue, certificatePath, classifyIssue, classifyStatusLine, cleanValue, compileMiniGlob, computeCriterionFingerprint, computeNextNumber, deriveEntriesFromViolations, deriveWalkPrefixes, evidenceCloseFlow, extractFileKey, extractIssueRefTokens, extractSlugSegment, findCertificate, findPendingWord, freezeIssue, keyLeadingNumber, listPrompts, loadIssueFileFormatGuide, locateDispositionText, locateIssueFileFormatGuide, mapPriorityValue, matchMiniGlob, migrateLegacyCore, migrateLegacyFile, normalizeHeader, normalizeHeaderFile, parseCriteriaBlock, parseHeaderDialect, parseIssueDoc, parseRegistryFileName, readEvidence, readEvidenceAnchors, readPendingReason, reconcileSlugDigitAllowlist, registerLintRulePack, renderCriteriaBlockExample, renderEvidenceCloseBlock, renderIssueTemplate, renderIssueTemplateSkeleton, renderRegistryReadmeStub, renderRerunEvidenceBlock, rerunCloseFlow, resolveCloseGates, runCriterionViaVault, runDocsCheck, runDocsFix, runHealth, runLint, runNumberingAudit, runNumberingCheck, runNumberingNext, runTriage, runWriteDocsBaseline, runWriteLintBaseline, scanOrphanSidecars, scanRegistryDir, scanScopeForFindings, showPrompt, sliceGuideFixtureSample, unfreezeIssue, walkFilesUnderPrefixes, walkFilesUnderRoots };
package/dist/index.js CHANGED
@@ -67,13 +67,13 @@ import"./rolldown-runtime-BqT_7tdF.js";import{renderBaselineRegeneratedConfig as
67
67
  `)),{ok:!0,message:i===`freeze`?`已冻结(⚪)`:`已解冻恢复 🔴`,idempotent:!1}}const mt=[`真机`,`雪场`,`合作方`,`回执`,`技术支持`,`REAL_DEVICE`,`人工确认`,`环境不具备`],ht=[`在途`,`并行线`,`待并行`,`让锁`,`待落地`,`待补跑`,`待复跑`];function B(e){let t=mt.find(t=>e.rawContent.includes(t));if(t!==void 0)return{category:`B`,reason:`判据依赖环境(命中「${t}」)——诚实保持黄灯,环境具备后再收口`};let n=ht.find(t=>e.rawContent.includes(t));if(n!==void 0)return{category:`C`,reason:`被并行在途线阻断(命中「${n}」)——待在途线落地后复跑收口`};let r=e.criteria?.criteria.length??0;return{category:`A`,reason:r>0?`判据全部机器可跑(${r} 条)——verify-close 补跑收口`:`判据清单为空,需人工核对后补录 criteria 块再收口`}}function gt(e,t,n){return(n===void 0?t.scopes:t.scopes.filter(e=>n.includes(e.registryDir))).flatMap(t=>F(u(e,t.registryDir)).map(e=>({record:e,file:`${t.registryDir}/${e.file}`,registryDir:t.registryDir})))}function _t(e){let t=gt(e.root,e.config,e.dirRels),n={};for(let{record:e}of t)n[e.status]=(n[e.status]??0)+1;let r=t.filter(({record:e})=>e.status===`yellow`||e.status===`legacy-yellow`),i=r.reduce((e,{record:t})=>Math.max(e,t.modifiedAtMs),0),a={};for(let{record:e}of r){let{category:t}=B(e);a[t]=(a[t]??0)+1}return{statusCounts:n,yellowTotal:r.length,oldestYellowAgeDays:i===0?0:Math.max(0,Math.floor((Date.now()-i)/864e5)),categoryCounts:a}}function vt(e){let r=gt(e.root,e.config).filter(({record:e})=>e.status===`yellow`||e.status===`legacy-yellow`).map(({record:e,file:t})=>{let n=B(e);return{id:e.id,file:t,status:e.status,category:n.category,reason:n.reason,criteriaCommands:e.criteria?.criteria.map(e=>e.command)??[]}}),i={generatedAtMs:Date.now(),registryDirs:e.config.scopes.map(e=>e.registryDir),items:r},o=e.outDir??u(e.root,`.lzi`,`issues`,`runs`,`ledger`);n(o,{recursive:!0});let s=u(o,`triage-${i.generatedAtMs}.json`);for(;t(s)||t(`${s}.tmp`);)i.generatedAtMs+=1,s=u(o,`triage-${i.generatedAtMs}.json`);let l=`${s}.tmp`;return c(l,`${JSON.stringify(i,null,2)}\n`),a(l,s),{ledgerPath:s,ledger:i}}const yt=new Map;function bt(e,t){yt.set(e,t)}function xt(e){if(typeof e!=`object`||!e)return null;let t=Reflect.get(e,`items`);if(t===void 0)return[];if(!Array.isArray(t))return null;let n=[];for(let e of t){if(typeof e!=`object`||!e)continue;let t=Reflect.get(e,`issueId`);typeof t==`string`&&t!==``&&n.push(t)}return n}const St=[{name:`status-line-missing`,evaluate:e=>e.status===`unknown`?`头部 30 行内未定位到状态行(状态行是处理情况的第一入口)`:null},{name:`status-line-legacy-format`,evaluate:(e,t)=>t.config.lint.strictLegacy&&(e.status===`legacy-yellow`||e.status===`legacy-green`)?`状态行为旧格式变体(${e.status})——须迁移为规范五态写法`:null},{name:`yellow-no-pending-reason`,evaluate:e=>(e.status===`yellow`||e.status===`legacy-yellow`)&&e.pendingReason===null?`黄灯(处理中)状态行须括注 ≥4 字挂起原因`:null},{name:`green-no-disposition-section`,evaluate:e=>(e.status===`green`||e.status===`legacy-green`)&&!e.hasDispositionSection?`绿灯缺少「处置结果/验证口径」段`:null},{name:`green-disposition-placeholder`,evaluate:e=>(e.status===`green`||e.status===`legacy-green`)&&e.hasDispositionSection&&e.dispositionIsPlaceholder?`处置段仍为占位形态(处置中/待回填)——禁止虚标终态`:null},{name:`green-canonical-no-evidence-block`,evaluate:e=>e.status===`green`&&e.statusLine.includes(`判据证据锚点见处置段`)&&!e.hasEvidenceAnchorBlock?`状态行声称「判据证据锚点见处置段」而证据锚点块缺席(手工翻绿识别——收口须经 verify-close)`:null},{name:`frozen-format`,evaluate:e=>e.status===`frozen`&&!/⚪\s*已冻结/.test(e.statusLine)?`冻结状态行须为「⚪ 已冻结(…)」规范形态`:null},{name:`frozen-no-adjudication-note`,evaluate:e=>e.status===`frozen`&&!e.statusLine.includes(`用户裁定`)?`冻结状态行须含「用户裁定」语(工具拒绝形式冻结)`:null},{name:`issue-slug-non-ascii`,evaluate:e=>g.test(e.slug)?null:`slug 段须为纯 ASCII kebab(小写字母/数字/连字符,禁中文与非 ASCII 字符):${e.file}`},{name:`issue-slug-contains-digit`,evaluate:(e,t)=>{if(!/\d/.test(e.slug)||t.config.scopes.find(e=>e.key===t.scopeKey)?.slugDigitPolicy===`allow`)return null;let n=`${t.registryDir}/${e.file}`;return t.config.slugDigitAllowlist.some(e=>k(e.glob,n))?null:`slug 段含数字(scope 政策 slugDigitPolicy 或白名单 slugDigitAllowlist 登记):${e.file}`}},{name:`duplicate-metadata-key`,evaluate:e=>{let t=[];for(let n of e.rawContent.split(/\r?\n/)){if(n.startsWith(`## `)||t.length>=40)break;t.push(n)}let n=new Map;for(let e of t){let t=/^(?:\*\*|- \*\*|- )(优先级|状态|立案日期|所属域|来源|严重程度)(?:\*\*)?\s*[::]/.exec(e.trim());if(t!==null){let e=t[1]??``;n.set(e,(n.get(e)??0)+1)}}let r=[...n.entries()].filter(([,e])=>e>1).map(([e])=>e);return r.length>0?`头部区元信息键重复:${r.join(`、`)}(方言残留块与规范头部并存——须经 migrate-legacy 收敛)`:null}},{name:`canceled-no-reason`,evaluate:e=>e.status===`canceled`&&e.pendingReason===null?`已取消(⚫)状态行须括注 ≥4 字取消原因`:null},{name:`blocking-ref-unknown`,evaluate:(e,t)=>{let n=e.blockedByRefs.filter(e=>!t.registryBasenames.has(`${e.number}.md`));return n.length>0?`「阻塞」引用的 issue 不存在:${n.map(e=>e.number).join(`、`)}`:null}},{name:`orphan-sidecar`,evaluate:(e,n)=>{let i=u(n.root,`.lzi`,`issues`,`comments`,n.scopeKey,`${e.id}.json`);if(!t(i))return null;try{let e=xt(JSON.parse(r(i,`utf8`)));if(e===null)return`评论边车结构非法(items 非数组)——孤儿判定降级为违规提示`;let t=e.filter(e=>!n.registryBasenames.has(`${e}.md`));return t.length>0?`评论边车存在孤儿引用(issue 不存在):${t.join(`、`)}`:null}catch{return`评论边车 JSON 解析失败(孤儿判定降级为违规提示)`}}}];function Ct(e){let t=[...St];for(let n of e.config.lint.rulePacks){let e=yt.get(n);e!==void 0&&t.push(...e)}let n=e.dirRels===void 0?e.config.scopes:e.config.scopes.filter(t=>e.dirRels?.includes(t.registryDir)),r=[];for(let i of n){let n=F(u(e.root,i.registryDir)),a=new Set(n.map(e=>e.file)),o={root:e.root,scopeKey:i.key,registryDir:i.registryDir,registryBasenames:a,config:e.config};for(let e of n)for(let n of t){let t=n.evaluate(e,o);t!==null&&r.push({file:`${i.registryDir}/${e.file}`,line:e.statusLineNumber,rule:n.name,message:t})}}let i=new Map(e.config.lint.baseline.map(e=>[`${e.file}::${e.rule}`,e]));if(e.ignoreBaseline===!0)return{violations:r.sort(wt),suppressed:0,baselineCount:i.size};let a=[],o=0,s=new Set;for(let e of r){let t=`${e.file}::${e.rule}`;if(i.has(t)){o+=1,s.add(t);continue}a.push(e)}let c=[...i.entries()].filter(([e])=>!s.has(e)).map(([e,t])=>({file:e.split(`::`)[0]??``,line:0,rule:`baseline-stale`,message:`基线条目已无对应现行违规——棘轮只许缩短,须同步删除该条目(收编指针:${t.issue})`}));return{violations:[...a,...c].sort(wt),suppressed:o,baselineCount:i.size}}function wt(e,t){return e.file===t.file?e.rule.localeCompare(t.rule):e.file.localeCompare(t.file)}function Tt(e,n){let r=[];for(let i of n.scopes){let n=u(e,`.lzi`,`issues`,`comments`,i.key);if(!t(n))continue;let a=new Set(F(u(e,i.registryDir)).map(e=>e.id));for(let e of j(n,[``])){let t=e.split(`/`).pop()?.replace(/\.json$/,``)??``;t!==``&&!a.has(t)&&r.push({file:`.lzi/issues/comments/${i.key}/${t}.json`,line:0,rule:`orphan-sidecar`,message:`评论边车为孤儿(注册表无 ${i.registryDir}/${t}-*.md)——请联动清理`})}}return r}function Et(t){if(t.issuePointer.trim()===``)return{ok:!1,error:`再生须携带非空 --issue <指针>(收编盖章)`};let n=Ct({root:t.root,config:t.config,ignoreBaseline:!0}).violations.filter(e=>e.rule!==`baseline-stale`),r=new Set(t.config.lint.baseline.map(e=>`${e.file}::${e.rule}`)),i=n.filter(e=>!r.has(`${e.file}::${e.rule}`));if(i.length>0){let e=i.slice(0,10).map(e=>`${e.file} [${e.rule}]`).join(`
68
68
  `);return{ok:!1,error:`存在 ${i.length} 项基线外新增违规,拒绝再生(先处置新增违规后再收编存量):\n${e}`}}let a=n.map(e=>({file:e.file,rule:e.rule,issue:t.issuePointer}));return{ok:!0,content:e(t.config,`lint`,a),entryCount:a.length}}function Dt(e,t){return e.map(e=>({file:e.file,rule:e.rule,issue:t}))}const V=new Set([`优先级`,`状态`,`立案日期`,`日期`,`所属域`,`来源`,`严重程度`,`严重度`]),Ot={立案日期:10,优先级:20,严重程度:30,状态:40,处置日期:50,所属域:60};function H(e){let t=[],n=[],r=e.length;for(let i=0;i<e.length;i+=1){let a=e[i]??``,o=/^##\s+([^::]{1,10})\s*$/.exec(a);if(o!==null&&V.has(o[1]?.trim()??``)){let n=i+1;for(;n<e.length&&(e[n]??``).trim()===``;)n+=1;let r=(e[n]??``).trim();if(r!==``&&!/^[#>*|`-]/.test(r)&&!/^[^::]{1,8}[::]/.test(r)&&r.length<=40){t.push({key:o[1]?.trim()??``,value:r});let a=n+1;for(;a<e.length&&(e[a]??``).trim()===``;)a+=1;let s=e[a]??``,c=/^##\s+([^::]{1,10})\s*$/.exec(s);i=c!==null&&V.has(c[1]?.trim()??``)?a-1:n;continue}}if(a.startsWith(`## `)||a===``&&t.length>0&&i>4){r=i;break}if(a===``||a.startsWith(`# `))continue;let s=/^##\s+([^::]{1,8})\s*[::]\s*(.+)$/.exec(a);if(s!==null){t.push({key:s[1]?.trim()??``,value:s[2]?.trim()??``});continue}let c=/^(?:\*\*|- \*\*|- )([^*::]{1,8})(?:\*\*)?\s*[::]\s*(.+)$/.exec(a);if(c!==null){t.push({key:c[1]?.trim()??``,value:c[2]?.trim()??``});continue}let l=/^(?:\*\*|- \*\*|- )([^*::]{1,8})(?:\*\*)?\s*[::]\s*$/.exec(a);if(l!==null){let n=[],r=a.length-a.trimStart().length,o=i+1;for(;o<e.length;){let t=e[o]??``;if(t.trim()===``||t.length-t.trimStart().length<=r)break;n.push(t.trim().replace(/^-\s*/,``)),o+=1}if(n.length>0){t.push({key:l[1]?.trim()??``,value:n.join(`;`)}),i=o-1;continue}}if(a.startsWith(`>`)){let e=a.slice(1).split(/[||]/).map(e=>e.trim()),r=[];for(let n of e){let e=/^([^::]{1,8})\s*[::]\s*(.+)$/.exec(n);e===null?r.push(n):t.push({key:(e[1]??``).replace(/^\*+|\*+$/g,``).trim(),value:e[2]?.trim()??``})}r.length===e.length?n.push(a):n.push(...r);continue}let u=/^\|([^|]*)\|([^|]*)\|$/.exec(a.trim());if(u!==null){let r=(u[1]??``).trim(),o=(u[2]??``).trim(),s=/^:?-{2,}:?$/,c=s.test(r)&&s.test(o),l=/^\|([^|]*)\|([^|]*)\|$/.exec((e[i+1]??``).trim()),d=l!==null&&s.test((l[1]??``).trim())&&s.test((l[2]??``).trim());if(c||d&&r!==``)continue;r===``?n.push(a):t.push({key:r,value:o});continue}n.push(a)}return{fields:t,residuals:n,bodyStartIndex:r}}function kt(e,t){return Ot[e]??1e3+t}function At(e,t){let n=e.split(/\r?\n/),r=H(n);if(!r.fields.some(e=>e.key===`状态`)){if(t?.status===void 0)return{content:e,outcome:`skipped`};r.fields.push({key:`状态`,value:E[t.status]})}let i=new Map;r.fields.forEach((e,t)=>{i.has(e.key)||i.set(e.key,t)});let a=[...[...r.fields].sort((e,t)=>{let n=kt(e.key,i.get(e.key)??0),r=kt(t.key,i.get(t.key)??0);return n===r?0:n-r}).map(e=>`**${e.key}**: ${e.value}`),...r.residuals],o=n.findIndex((e,t)=>t>0&&e.startsWith(`# `)),s=[...n.slice(0,o===-1?1:o),``,...a,``,...n.slice(r.bodyStartIndex)].join(`
69
69
  `);return s===e?{content:e,outcome:`unchanged`}:{content:s,outcome:`rewritten`}}function jt(e,t){return e.fields.filter(e=>!t.includes(`**${e.key}**: ${e.value}`)).map(e=>e.key)}function Mt(e){let t=``;try{t=r(u(e.root,e.file),`utf8`)}catch{return{file:e.file,outcome:`failed`,message:`读档失败(文件不存在或不可读)`}}let n=At(t,{status:e.status});if(n.outcome===`skipped`)return{file:e.file,outcome:`skipped`,message:`无可识别状态行且未给 --status——灯位属语义判断,不猜灯`};if(n.outcome===`unchanged`)return{file:e.file,outcome:`unchanged`,message:`头部已是规范形态`};let i=jt(H(t.split(/\r?\n/)),n.content);return i.length>0?{file:e.file,outcome:`failed`,message:`零丢失守卫失败:字段 ${i.join(`、`)} 未在产物在场——不落盘`}:(c(u(e.root,e.file),n.content),{file:e.file,outcome:`rewritten`,message:`头部已迁移为规范形态`})}function U(e){return e.trim().replace(/^\*\*/,``).replace(/\*\*$/,``).replace(/\*\*(?=[((])/,``).trim()}function Nt(e){for(let t of[`frozen`,`green`,`deferred`,`canceled`,`inProgress`,`red`])for(let n of ae[t])if(e.startsWith(n))return{tier:t,word:n};return null}function Pt(e,t){let n=e.slice(t.length).trim(),r=/^[((]([^))]*)[))]/.exec(n);return r===null?{paren:``,rest:n}:{paren:(r[1]??``).trim(),rest:n.slice((r[0]??``).length).trim()}}function W(e){let t=Nt(e);if(t===null)return null;let{paren:n,rest:r}=Pt(e,t.word),i=[n,r,`原「${t.word}」`].filter(e=>e!==``);switch(t.tier){case`frozen`:return null;case`green`:return{value:`🟢 已处置(${i.join(`;`)})`,tier:t.tier,word:t.word};case`red`:return{value:`🔴 待处理(${i.join(`;`)})`,tier:t.tier,word:t.word};case`deferred`:return{value:`🔴 待处理(${[n===``?`延期`:n,`原「${t.word}」`].join(`;`)})`,tier:t.tier,word:t.word};case`canceled`:return n===``||n.length<4?null:{value:`⚫ 已取消(${i.join(`;`)})`,tier:t.tier,word:t.word};case`inProgress`:return n.length<4?null:{value:`🟡 处理中(${n})`,tier:t.tier,word:t.word}}}function G(e){return{高:`P1`,中:`P2`,低:`P3`,P1:`P1`,P2:`P2`,P3:`P3`}[U(e)]??null}function Ft(e,t){let n=e.replace(/^#\s+/,``).replace(RegExp(`^${t}\\s*(?:[-—―·..::-]+\\s*)?`),``).trim();return{line:`# ${t} - ${n}`,titleCore:n}}function It(e){let{fields:t,transformations:n}=e,r=t.findIndex(e=>e.key===`立案日期`),i=t.findIndex(e=>e.key===`日期`);if(r===-1&&i!==-1){let e=t[i];e!==void 0&&(t[i]={key:`立案日期`,value:e.value},n.push({kind:`field-alias`,detail:`日期 → 立案日期(${e.value})`}))}if(!t.some(e=>e.key===`立案日期`)){if(e.defaultFilingDate===void 0)return{skipMessage:`缺立案日期且未给回填缺省——交人工`};t.push({key:`立案日期`,value:e.defaultFilingDate}),n.push({kind:`required-backfilled`,detail:`立案日期 ← ${e.defaultFilingDate}`})}let a=t.findIndex(e=>e.key===`优先级`);if(a!==-1){let e=t[a]?.value??``,r=G(e);r!==null&&r!==e&&(t[a]={key:`优先级`,value:r},n.push({kind:`priority-mapped`,detail:`优先级 ${e} → ${r}`}))}else{let r=t.find(e=>e.key===`严重程度`),i=(r===void 0?null:G(r.value))??e.defaultPriority??null;if(i===null)return{skipMessage:`缺优先级且未给回填缺省——交人工`};t.push({key:`优先级`,value:i}),n.push({kind:r===void 0?`priority-backfilled`:`priority-mapped`,detail:`优先级 ← ${i}${r===void 0?`(缺省回填)`:`(严重程度 ${r.value})`}`})}return{skipMessage:null}}const Lt=new Set([`状态`,`优先级`,`立案日期`,`日期`,`来源`,`严重程度`,`严重度`,`所属域`,`处置日期`,`沿革`,`阻塞`]);function Rt(e,t){let n=[],r=0,i=!1,a=!1,o=0,s=[];for(;o<e.length;){let t=e[o]??``;if(t.trim()===``){n.push(t),o+=1;continue}let c=/^(?:\*\*|- \*\*|- )([^*::]{1,20})(?:\*\*)?\s*[::]\s*(.+)$/.exec(t),l=/^(?:\*\*|- \*\*|- )([^*::]{1,20})(?:\*\*)?\s*[::]\s*$/.exec(t);if(c===null&&l===null)break;let u=((c??l)?.[1]??``).trim();if(u===``)break;if(n.push(t),r+=1,Lt.has(u)&&(i=!0),c!==null){s.push({key:u,value:U(c[2]??``)}),o+=1;continue}let d=[],f=t.length-t.trimStart().length;for(o+=1;o<e.length;){let t=e[o]??``;if(t.trim()===``||t.length-t.trimStart().length<=f)break;n.push(t),d.push(t.trim().replace(/^-\s*/,``)),o+=1}d.length===0?s.push({key:u,value:``}):(s.push({key:u,value:d.join(`;`)}),a=!0)}if(r===0||!i&&r<2&&!a)return{consumedLines:0,restLines:e};for(t.push(...s);n.length>0&&(n[n.length-1]??``).trim()===``;)n.pop(),--o;return{consumedLines:r,restLines:e.slice(o)}}function zt(e,t){let n,r=!1,i=e.filter(e=>e.key===`状态`);if(i.length>1){let r=i[0]?.value.includes(`存量清偿定档:原案无状态行`)===!0,a=i.slice(1).find(e=>!e.value.includes(`存量清偿定档:原案无状态行`));if(r&&a!==void 0){let r=W(a.value);if(r!==null){let i=e.find(e=>e.key===`状态`);i!==void 0&&(i.value=r.value),n=r.value,t.push({kind:`duplicate-field-converged`,detail:`状态 键去重:注入定档让位原始语义 → ${r.value}`})}}}let a=e.filter(e=>e.key===`来源`);if(a.length>1){let n=a[0]?.value.includes(`格式清偿迁移回填`)===!0,i=a.slice(1).find(e=>!e.value.includes(`格式清偿迁移回填`));if(n&&i!==void 0){let n=e.find(e=>e.key===`来源`);n!==void 0&&(n.value=i.value),r=!0,t.push({kind:`duplicate-field-converged`,detail:`来源 键去重:回填缺省让位原始语义 → ${i.value.slice(0,60)}`})}}let o=new Map;for(let n=0;n<e.length;n+=1){let r=e[n];if(r===void 0)continue;let i=o.get(r.key);if(i===void 0){o.set(r.key,n);continue}let a=e[i];r.value!==``&&a!==void 0&&!a.value.includes(r.value)&&(a.value=a.value===``?r.value:`${a.value};${r.value}`),t.push({kind:`duplicate-field-converged`,detail:`${r.key} 键去重:重复值并入首现(${r.value.slice(0,40)})`}),e.splice(n,1),--n}return{statusValue:n,sourceReplaced:r}}const K=[{pattern:/^验证(结果)?(?!口径)/,title:`处置结果/验证口径`},{pattern:/^(治理记录|治理落地记录|治理落地|落地治理|治理(落地)|治理动作|治理$)/,title:`处置结果/治理记录`},{pattern:/^(修复(?!方向)|解决方案|变更内容|处理报告|处置(?!方向))/,title:`处置结果/变更记录`}];function Bt(e){let t=[],n=-1,r=``;for(let t of K){let i=e.findIndex(e=>e.startsWith(`## `)&&t.pattern.test(e.slice(3).trim()));if(i!==-1){n=i,r=t.title;break}}let i=e.some(e=>e.startsWith(`## `)&&e.includes(`处置结果`)),a=[...e],o=!1;if(!i&&n!==-1){let e=a[n]??``;a[n]=`## ${r}`,t.push({kind:`disposition-renamed`,detail:`${e} → ## ${r}`}),o=a.slice(n+1,n+1+8).some(e=>x.test(e))}return!i&&(n===-1||r===`处置结果/变更记录`)&&!o&&(a.push(``,`## 处置结果/验证口径`,``,`(原案无独立验证段;处置证据见上文各节——格式清偿迁移回填)`),t.push({kind:`disposition-appended`,detail:`追加 ## 处置结果/验证口径(无独立验证段的事实回填说明)`})),{lines:a,transformations:t,placeholder:o}}const Vt=[`优先级`,`状态`,`立案日期`,`所属域`,`来源`,`沿革`,`阻塞`];function Ht(e){if(e.injected||e.alreadyCanonical||e.originalStatus.length<=80)return null;let t=W(U(e.originalStatus).slice(0,12));if(t===null||t.tier!==`green`&&t.tier!==`red`&&t.tier!==`deferred`)return`manual`;let n=t.tier===`green`?`green`:`red`;return{summary:U(e.originalStatus.replace(/\*\*/g,``)),canonical:`${E[n]}(原「${t.word}」)`,tier:n}}function Ut(e){if(!e.fields.some(e=>e.key===`来源`)){if(e.defaultSource===void 0)return{skipMessage:`缺来源且未给回填缺省——交人工`};e.fields.push({key:`来源`,value:e.defaultSource}),e.transformations.push({kind:`required-backfilled`,detail:`来源 ← 缺省回填`})}return e.fields.some(e=>e.key===`所属域`)||(e.fields.push({key:`所属域`,value:e.scopeTitle}),e.transformations.push({kind:`required-backfilled`,detail:`所属域 ← ${e.scopeTitle}`})),{skipMessage:null}}function Wt(e){let t=e.lines.findIndex(e=>e.startsWith(`# `));if(t===-1)return null;let n=Ft(e.lines[t]??``,e.number);return n.line!==(e.lines[t]??``)&&e.transformations.push({kind:`h1-numbered`,detail:`${e.lines[t]} → ${n.line}`}),{line:n.line}}function Gt(e,t){let n=e.split(/\r?\n/),r=H(n),i=r.fields.findIndex(e=>e.key===`状态`),a=i===-1&&(t.status===`red`||t.status===`green`);if(i===-1&&!a)return{outcome:`skipped`,content:e,message:`无可识别状态行——灯位属语义判断,交人工(--status red|green 显式定档)`,transformations:[],sourceReplaced:!1};let o=a?`${E[t.status??`red`]}(存量清偿定档:原案无状态行,依结案内容人工定档)`:U(r.fields[i??0]?.value??``),s=!a&&D(`状态: ${o}`)!==`unknown`,c=Ht({injected:a,alreadyCanonical:s,originalStatus:o});if(c===`manual`)return{outcome:`skipped`,content:e,message:`状态值超长(${o.length} 字符——段落级文本混入状态行且词表未命中)——交人工拆分`,transformations:[],sourceReplaced:!1};let l=[],u=r.fields.map(e=>({key:e.key,value:U(e.value)}));a&&(u.push({key:`状态`,value:o}),l.push({kind:`status-canonicalized`,detail:`<无状态行> → ${o}(--status 注入,人工定档)`}));let d=It({fields:u,transformations:l,defaultPriority:t.defaultPriority,defaultFilingDate:t.defaultFilingDate});if(d.skipMessage!==null)return{outcome:`skipped`,content:e,message:d.skipMessage,transformations:[],sourceReplaced:!1};let f=t.status===`green`?`green`:`red`,p=a?{value:o,tier:f,word:`<无状态行>`}:s?{value:o,tier:D(`状态: ${o}`)===`green`||D(`状态: ${o}`)===`legacy-green`?`green`:`red`,word:`<已规范>`}:c===null?W(o):{value:c.canonical,tier:c.tier,word:`<超长拆分>`};if(p===null)return{outcome:`skipped`,content:e,message:`状态值「${o}」不可机械迁移(冻结/取消无据/黄灯无因/词表未识别)——交人工`,transformations:[],sourceReplaced:!1};c!==null&&!u.some(e=>e.key===`处置摘要`)&&(u.push({key:`处置摘要`,value:c.summary}),l.push({kind:`status-canonicalized`,detail:`状态值超长拆分:${c.summary.slice(0,24)}… → 短规范状态 + 处置摘要字段(${c.summary.length} 字符全文承载)`}));let m=p.tier===`green`,h=n.slice(r.bodyStartIndex),g=Rt(h,u);g.consumedLines>0&&(h=g.restLines,l.push({kind:`header-converged`,detail:`体首残留元信息块吸收 ${g.consumedLines} 行(键去重收敛见产物)`}));let _=zt(u,l),v=m?Bt(h):{lines:h,transformations:[],placeholder:!1};h=v.lines,l.push(...v.transformations);let ee=_.statusValue??(!a&&v.placeholder?`🟡 处理中(历史标记已处置但验证口径为占位,清偿时待核实;原「${p.word}」)`:p.value);!a&&!s&&(v.placeholder?l.push({kind:`status-downgraded-placeholder`,detail:`${o} → 🟡 处理中(处置段占位降级)`}):l.push({kind:`status-canonicalized`,detail:`${o} → ${p.value}`}));let y=u.findIndex(e=>e.key===`状态`);y!==-1&&(u[y]={key:`状态`,value:ee});let b=Ut({fields:u,transformations:l,defaultSource:t.defaultSource,scopeTitle:t.scopeTitle});if(b.skipMessage!==null)return{outcome:`skipped`,content:e,message:b.skipMessage,transformations:[],sourceReplaced:!1};let x=Wt({lines:n,number:t.number,transformations:l});if(x===null)return{outcome:`skipped`,content:e,message:`无一级标题——交人工`,transformations:[],sourceReplaced:!1};let te=[...Vt.flatMap(e=>u.filter(t=>t.key===e)),...u.filter(e=>!Vt.includes(e.key))],S=0;for(;S<h.length&&(h[S]??``).trim()===``;)S+=1;let C=[x.line,``,...te.map(e=>`**${e.key}**: ${e.value}`),...r.residuals.length>0?[``,...r.residuals]:[],``,...h.slice(S)].join(`
70
- `);return C===e?{outcome:`unchanged`,content:e,message:`已是规范形态`,transformations:[],sourceReplaced:!1}:{outcome:`rewritten`,content:C,message:`存量头部已清偿为规范形态`,transformations:l,sourceReplaced:_.sourceReplaced}}const Kt=/^([0-9]{3,4})-.+\.md$/;function qt(e,t){return e.scopes.filter(e=>t===e.registryDir||t.startsWith(`${e.registryDir}/`)).sort((e,t)=>t.registryDir.length-e.registryDir.length)[0]?.title??null}function Jt(e,t,n){let r=[],i=e.split(/\r?\n/);for(let e=0;e<i.length;e+=1){let a=i[e]??``,o=a.trim();if(o===``||t.includes(o))continue;if(o.startsWith(`# `)){let e=o.replace(/^#\s+/,``).replace(/^[0-9]{3,4}\s*(?:[-—―·..::-]+\s*|\s+)/,``).trim();if(e!==``&&t.includes(e))continue}if(o.startsWith(`## `)&&K.some(e=>e.pattern.test(o.slice(3).trim()))&&t.includes(`处置结果/`))continue;let s=/^\|([^|]*)\|([^|]*)\|$/.exec(o);if(s!==null){let a=(s[1]??``).trim(),c=(s[2]??``).trim(),l=/^:?-{2,}:?$/,u=/^\|([^|]*)\|([^|]*)\|$/.exec((i[e+1]??``).trim()),d=u!==null&&l.test((u[1]??``).trim())&&l.test((u[2]??``).trim());if(l.test(a)&&l.test(c)||d&&a!==``||a===``||n.includes(a)||t.includes(`**${a}**: ${c}`))continue;r.push(o.slice(0,60));continue}if(o.startsWith(`>`)){let e=o.slice(1).split(/[||]/),r=!0;for(let i of e){let e=/^([^::]{1,8})\s*[::]\s*(.+)$/.exec(i.trim());if(e===null)continue;let a=(e[1]??``).replace(/^\*+|\*+$/g,``).trim(),o=U(e[2]??``);!n.includes(a)&&!t.includes(o)&&(r=!1)}if(r)continue}let c=/^(?:\*\*|- \*\*|- )([^*::]{1,20})(?:\*\*)?\s*[::]\s*$/.exec(o);if(c!==null&&t.includes(`**${(c[1]??``).trim()}**:`)||o.startsWith(`- `)&&t.includes(o.slice(2).trim()))continue;let l=/^(?:\*\*|- \*\*|- |## )([^*::]{1,20})(?:\*\*)?\s*[::]\s*(.+)$/.exec(a);if(l!==null){let e=(l[1]??``).trim(),r=U(l[2]??``);if(n.includes(e)||t.includes(r))continue}let u=/^##\s*([^::]{1,10})\s*$/.exec(o);if(u!==null&&V.has(u[1]?.trim()??``))continue;let d=e-1;for(;d>=0&&(i[d]??``).trim()===``;)--d;let f=/^##\s*([^::]{1,10})\s*$/.exec((i[d]??``).trim());if(f!==null&&V.has(f[1]?.trim()??``)){let e=(f[1]??``).trim();if(n.includes(e)||t.includes(U(o)))continue}r.push(o.slice(0,60))}return r}function Yt(e){let t=e.file.split(`/`).pop()??``,n=Kt.exec(t);if(n===null)return{file:e.file,outcome:`skipped`,message:`文件名不属注册表编号形态(NNNN-slug.md)`,content:``,transformations:[]};let i=qt(e.config,e.file);if(i===null)return{file:e.file,outcome:`skipped`,message:`文件不在任何已登记 scope 的 registryDir 内——交人工`,content:``,transformations:[]};let a=``;try{a=r(u(e.root,e.file),`utf8`)}catch{return{file:e.file,outcome:`failed`,message:`读档失败(文件不存在或不可读)`,content:``,transformations:[]}}let o=Gt(a,{number:n[1]??``,scopeTitle:i,defaultPriority:e.defaultPriority,defaultSource:e.defaultSource,defaultFilingDate:e.defaultFilingDate,status:e.status});if(o.outcome!==`rewritten`)return{file:e.file,outcome:o.outcome,message:o.message,content:o.content,transformations:o.transformations};let s=Jt(a,o.content,o.sourceReplaced?[`状态`,`日期`,`优先级`,`来源`]:[`状态`,`日期`,`优先级`]);return s.length>0?{file:e.file,outcome:`failed`,message:`零丢失守卫失败:${s.length} 项原文内容未在产物在场(如 ${s[0]})——不落盘`,content:o.content,transformations:o.transformations}:(e.dryRun!==!0&&c(u(e.root,e.file),o.content),{file:e.file,outcome:`rewritten`,message:e.dryRun===!0?`dry-run 通过(未落盘)`:`存量头部已清偿为规范形态`,content:o.content,transformations:o.transformations})}function q(e,t){if(t.kind===`numericPrefix`)return/^(\d+)(?=[-._ ]|$)/.exec(e)?.[1]??null;if(t.kind===`compositePrefix`)return RegExp(`^(\\d+(?:[-._ ]\\d+){${t.groups-1}})(?=[-._ ]|$)`).exec(e)?.[1]??null;if(t.kind===`numericSuffix`){let t=e.replace(/\.[^.]+$/,``);return/(\d+)$/.exec(t)?.[1]??null}return new RegExp(t.pattern).exec(e)?.groups?.key??null}function J(e){let t=/^\d+/.exec(e);return t===null?null:Number.parseInt(t[0]??``,10)}function Y(e){let t=new Set;for(let n of e.basenames){let r=q(n,e.rule);r!==null&&t.add(r)}if(t.size===0)return{ok:!1,error:`该范围当前无编号文件,无法推断位数——请人工定号后落盘`};let n=``;for(let e of t)(J(e)??0)>(J(n)||0)&&(n=e);if(n===``)return{ok:!1,error:`该范围无数字前缀键,无法取号`};let r=n.length,i=new Set(e.reserved),a=0,o=0,s=(J(n)??0)+1;for(;;){let e=String(s).padStart(r,`0`);if(t.has(e)){a+=1,s+=1;continue}if(i.has(e)){o+=1,s+=1;continue}return{ok:!0,number:e,skippedOccupied:a,skippedReserved:o}}}function Xt(e){let t=/^\d+[-._ ](.+)\.[^.]+$/.exec(e);return t===null?null:t[1]??``}function Zt(e,t){if(e.length!==t.length)return!1;let n=[...e].sort(),r=[...t].sort();return n.every((e,t)=>e===r[t])}function Qt(e){let t=[],n=new Map;for(let t of e.basenames){let r=q(t,e.rule);r!==null&&n.set(r,[...n.get(r)??[],t])}let r=new Map(e.reserved.map(e=>[e.number,e.reason])),i=new Set;for(let[a,o]of n){if(o.length>=2){let n=e.allowlist.findIndex((t,n)=>!i.has(n)&&t.number===a&&Zt(t.files,o.map(t=>e.toRepoRelative(t))));n===-1?t.push({kind:`duplicate`,scopeKey:e.scopeKey,message:`scope「${e.scopeKey}」编号 ${a} 撞号:${o.join(`、`)}——先入库者保号,后到线顺延清偿`}):i.add(n);continue}let n=r.get(a);n!==void 0&&t.push({kind:`reservedOccupied`,scopeKey:e.scopeKey,message:`scope「${e.scopeKey}」保留号 ${a} 被占用(${o.join(`、`)};保留理由:${n}——保留号不回收复用,请让号重编`})}if([...n.keys()].some(e=>e.length>1&&e.startsWith(`0`))){let r=new Set([...n.keys()].map(e=>e.length));r.size>1&&t.push({kind:`widthInconsistent`,scopeKey:e.scopeKey,message:`scope「${e.scopeKey}」零填充位数混用(族内键长 ${[...r].sort((e,t)=>e-t).join(`/`)}):${[...n.keys()].slice(0,6).join(`、`)}`})}if(e.allowlist.forEach((r,a)=>{if(i.has(a))return;let o=r.files.filter(e=>{let t=e.split(`/`).pop()??``;return(n.get(r.number)??[]).includes(t)});t.push({kind:`allowlistStale`,scopeKey:e.scopeKey,message:`scope「${e.scopeKey}」豁免条目(${r.number},${r.status})已无对应现行撞号组——棘轮只许缩短,须同步删除该条目(登记集:${r.files.join(`、`)};磁盘现状:${o.length>0?o.join(`、`):`无`})`})}),e.slugDigitPolicy!==`allow`){let n=new Set;for(let r of e.basenames){let i=Xt(r);if(i===null||i===``||!/\d/.test(i))continue;let a=e.toRepoRelative(r);e.slugDigitAllowlist.some(e=>k(e,a)?(n.add(e),!0):!1)||t.push({kind:`slugContainsDigit`,scopeKey:e.scopeKey,message:`scope「${e.scopeKey}」slug 段含数字:${r}(白名单登记请到配置 slugDigitAllowlist,逐条带理由)`})}if(e.fullMode)for(let r of e.slugDigitAllowlist)n.has(r)||t.push({kind:`slugDigitAllowlistStale`,scopeKey:`global`,message:`slug 白名单条目「${r}」在启用域内已匹配不到任何文件——棘轮只许缩短,须同步删除该条目`})}return t}function $t(e){let t=[],n=new RegExp(e.numberishPattern),r=new Map,i=new Set;for(let t of e.files){let a=t.split(`/`).pop()??``;if(!n.test(a)||e.registryDirs.some(e=>t===e||t.startsWith(`${e}/`))||e.pending.find(e=>k(e.glob,t)?(i.add(e.glob),!0):!1)!==void 0)continue;let o=t.split(`/`).slice(0,-1).join(`/`);r.set(o,[...r.get(o)??[],a])}for(let e of[...r.keys()].sort((e,t)=>e.localeCompare(t)))t.push({kind:`coverageGap`,message:`未登记编号区:目录 ${e===``?`(仓库根)`:e} 有 ${r.get(e)?.length??0} 个编号文件未被任何 scope/pending 覆盖——${(r.get(e)??[]).join(`、`)}(请在 lzi-issues.config.ts 登记 scope 或 pending 并注明理由)`});for(let n of e.pending)i.has(n.glob)||t.push({kind:`pendingStale`,message:`pending 条目「${n.glob}」已匹配不到任何编号文件——棘轮只许缩短,须同步删除该条目(登记理由:${n.reason})`});return t}const X={kind:`numericPrefix`};function Z(e,t){return j(e,[t]).map(e=>e.split(`/`).pop()??``)}function en(e){let t=e.scopeKey!==void 0&&e.dirRel!==void 0,n=e.scopeKey===void 0&&e.dirRel===void 0;if(t||n)return{ok:!1,error:`取号须且仅须指定 --scope <key> 或 --dir <目录> 之一`};if(e.scopeKey!==void 0){let t=e.config.scopes.find(t=>t.key===e.scopeKey);if(t===void 0)return{ok:!1,error:`scope「${e.scopeKey}」未登记(请在 lzi-issues.config.ts 注册)`};let n=Y({basenames:Z(e.root,t.registryDir),rule:X,reserved:t.numbering.reserved.map(e=>e.number)});return n.ok?{ok:!0,number:n.number,target:t.key,skippedOccupied:n.skippedOccupied,skippedReserved:n.skippedReserved}:n}let r=e.config.scopes.filter(t=>t.registryDir===e.dirRel);if(r.length===0)return{ok:!1,error:`目录「${e.dirRel}」未登记为任何 scope 的注册表(请在 lzi-issues.config.ts 注册后再取号)`};let i=[...new Set(r.flatMap(e=>e.numbering.reserved.map(e=>e.number)))],a=Y({basenames:Z(e.root,e.dirRel??``),rule:X,reserved:i});return a.ok?{ok:!0,number:a.number,target:e.dirRel??``,skippedOccupied:a.skippedOccupied,skippedReserved:a.skippedReserved}:a}function tn(e){let t=e.dirRel===void 0?e.config.scopes:e.config.scopes.filter(t=>t.registryDir===e.dirRel),n=[],r=0;for(let i of t){let t=Z(e.root,i.registryDir);r+=t.length;let a=e.dirRel===void 0?e.config.numbering.allowlist.filter(e=>e.scopeKey===i.key).map(e=>({...e,files:[...e.files]})):e.config.numbering.allowlist.filter(t=>t.scopeKey===i.key&&t.files.some(t=>t.startsWith(`${e.dirRel}/`))).map(e=>({...e,files:[...e.files]}));n.push(...Qt({scopeKey:i.key,basenames:t,rule:X,reserved:i.numbering.reserved,allowlist:a,slugDigitPolicy:i.slugDigitPolicy,slugDigitAllowlist:e.config.slugDigitAllowlist.map(e=>e.glob),toRepoRelative:e=>`${i.registryDir}/${e}`,fullMode:e.dirRel===void 0}))}return{findings:n,scopeCount:t.length,scannedFiles:r}}function nn(e){let t=[];for(let n of e.config.numbering.audit.roots)t.push(...M(u(e.root,n),{excludeGlobs:e.config.numbering.globalExclusions.map(e=>e.glob)}).map(e=>n===``?e:`${n}/${e}`));return{findings:$t({files:t,numberishPattern:e.config.numbering.audit.numberishPattern,registryDirs:e.config.scopes.map(e=>e.registryDir),pending:e.config.numbering.audit.pending}),scannedFiles:t.length}}const Q=`<!-- issue-file-format-fixture:start -->`,$=`<!-- issue-file-format-fixture:end -->`,rn=`docs/guides/issue-file-format.md`;function an(){let e=l(f(import.meta.url.replace(`file://`,``)));for(let n=0;n<10;n+=1){let n=u(e,rn);if(t(n))return n;let r=l(e);if(r===e)break;e=r}throw Error(`未定位到格式指南:${rn}(自模块位置向上 10 层内)`)}function on(){return r(an(),`utf8`)}function sn(e){let t=e.indexOf(Q),n=e.indexOf($);if(t===-1)throw Error(`指南活样板起点标记缺席:${Q}`);if(n===-1)throw Error(`指南活样板终点标记缺席:${$}`);if(n<t)throw Error(`指南活样板标记次序颠倒(终点在起点之前)`);return e.slice(t+40,n).replace(/^\n+/,``).replace(/\n+$/,``)}const cn=[`## 一、问题动机`,``,`> <问题原文逐字记录,禁止转述改写>`,``,`客观证据(报错/复现步骤/涉及文件/日志路径):`,`- <证据>`,``].join(`
71
- `),ln=[`## 二、现状与证据`,``,`<排查链路与实证;无法确认根因时如实标注「根因待治理时核实」>`,``].join(`
72
- `),un=[`## 三、处置方向`,``,`【1】备选方案和分析`,`- <备选>:<分析>`,``,`【2】推荐方案和分析`,`- <推荐>:<分析;能自决的直接执行,裁决类停下等用户裁定>`,``].join(`
73
- `),dn=[`## 四、处置结果/验证口径`,``,`<随治理进展回填;判据以 criteria 围栏块声明;收口由 verify-close 回填证据锚点>`,``].join(`
74
- `);function fn(e){return e.map(e=>e.slug?`[${e.number}](./${e.number}-${e.slug}.md)`:`[${e.number}]`).join(`;`)}function pn(){return[`# <NNNN> - <一句话标题:问题是什么,禁笼统词>`,``,`**优先级**: <P1|P2|P3>`,`**状态**: ${E.red}`,`**立案日期**: <YYYY-MM-DD>`,`**所属域**: <scope title>`,`**来源**: <问题原文出处(用户裁定/会话记录/自动巡检 + 日期)>`,`**沿革**: <可选:前序 issue 引用,如 [0035](./0035-<slug>.md)>`,`**阻塞**: <可选:被阻塞于哪些 issue;全部 🟢 后方可收口>`,``,cn,ln,un,dn].join(`
75
- `)}function mn(e){let t=[`# ${e.number} - ${e.title}`,``,`**优先级**: ${e.priority}`,`**状态**: ${E.red}`,`**立案日期**: ${e.date}`,`**所属域**: ${e.scopeTitle}`,`**来源**: ${e.source}`];return e.historyRefs!==void 0&&e.historyRefs.length>0&&t.push(`**${w}**: ${fn(e.historyRefs)}`),e.blockedByRefs!==void 0&&e.blockedByRefs.length>0&&t.push(`**${T}**: ${fn(e.blockedByRefs)}`),t.push(``),[t.join(`
76
- `),cn,ln,un,dn].join(`
77
- `)}function hn(){return["```criteria",`# 一行一判据:label|channel|cwd|command(四段竖线分隔,段内禁竖线)`,`# channel 限字母数字起首的 [A-Za-z0-9._-];cwd 相对仓库根;command 为退出码即判据的自动化命令`,`# summary 行唯一且非空——真跑收口 --criteria-from-issue 的机器可读载体`,`summary: 示例:单包测试全绿`,`单包测试|test|packages/demo|bun run test`,"```",``].join(`
78
- `)}function gn(e,t){let n=t===null?"- 标准模板:`lzi-issues template`(字段契约 `--format json`);完整格式规范见 lzi-issues 仓 docs/guides/issue-file-format.md(本仓未携带指南文件,不落链接)":"- 标准模板:`lzi-issues template`;完整格式规范:[issue-file-format]("+t+`)`;return[`# ${e} issue 注册表`,``,"本目录是 lzi-issues 注册表(`NNNN-<slug>.md`,目录即编号命名空间,编号独立序列)。",``,`## 建档`,``,"- `lzi-issues create --title <标题> --scope <key>`(编号走门禁取号,禁止手工定号)",n,``,`## 状态行(处理情况第一入口)`,``,`- ${E.red}:待处理`,`- ${E.yellow}(须括注 ≥4 字挂起原因)`,`- ${E.green}(须处置段 + 证据锚点;收口走 \`lzi-issues verify-close\`)`,`- ${E.frozen}(仅经 \`lzi-issues freeze\`,理由须含「用户裁定」)`,`- ${E.canceled}(须括注取消原因)`,``,`## 正文四段`,``,`一、问题动机 → 二、现状与证据 → 三、处置方向(【1】备选方案和分析 / 【2】推荐方案和分析)→ 四、处置结果/验证口径`,``].join(`
79
- `)}const _n=[{name:`number`,required:!0,description:`scope 内编号,4 位零填充,由编号门禁取号分配(写入时刻确定)`},{name:`slug`,required:!0,description:`ASCII kebab slug(小写字母/数字/连字符,禁中文),slug 段默认禁数字(slugDigitPolicy)`},{name:`title`,required:!0,description:`一句话标题:问题是什么,禁笼统词`},{name:`priority`,required:!0,description:`优先级,P1 | P2 | P3`},{name:`status`,required:!0,description:`状态行五态;建档固定 🔴 待处理,流转走工具(verify-close / freeze 等)`},{name:`立案日期`,required:!0,description:`YYYY-MM-DD`},{name:`所属域`,required:!0,description:`config 登记的 scope title`},{name:`来源`,required:!0,description:`问题原文出处(用户裁定 / 会话记录 / 自动巡检 + 日期)`},{name:`沿革`,required:!1,description:`前序 issue 引用,markdown 相对链接或纯编号形态`},{name:`阻塞`,required:!1,description:`被阻塞于哪些 issue;全部 🟢 后方可收口(blockingMustBeGreen 门禁)`}];export{z as ADJUDICATION_KEYWORD,g as ASCII_KEBAB_SLUG_PATTERN,T as BLOCKING_LINE_KEY,E as CANONICAL_STATUS_LABELS,St as CORE_LINT_RULES,re as CRITERIA_CHANNEL_PATTERN,ne as CRITERIA_FENCE_CLOSE_PATTERN,C as CRITERIA_FENCE_OPEN_PATTERN,K as DISPOSITION_CANDIDATES,x as DISPOSITION_PLACEHOLDER_PATTERN,te as DISPOSITION_PLACEHOLDER_WINDOW_LINES,b as DISPOSITION_TITLE_KEYWORD,S as EVIDENCE_ANCHOR_MARKERS,$ as GUIDE_FIXTURE_END_MARK,Q as GUIDE_FIXTURE_START_MARK,V as HEADING_ALONE_METADATA_KEYS,w as HISTORY_LINE_KEY,X as ISSUES_KEY_RULE,rn as ISSUE_FILE_FORMAT_GUIDE_RELATIVE,oe as ISSUE_REF_TOKEN_PATTERN,ae as LEGACY_STATUS_VOCABULARY,ie as META_LINE_PREFIX_SOURCE_TEMPLATE,O as MiniGlobError,y as PENDING_REASON_PATTERN,h as REGISTRY_FILE_PATTERN,v as STATUS_LINE_LOCATE_PATTERN,_ as STATUS_LINE_PREFIX_SOURCE,ee as STATUS_LINE_SCAN_LINES,_n as TEMPLATE_FIELDS,it as acquireRunLock,I as applyGreenFlip,$t as auditCoverage,It as backfillRequiredFields,le as buildMetaLineLocatePattern,Ft as canonicalizeH1,W as canonicalizeStatusValue,R as certificatePath,B as classifyIssue,D as classifyStatusLine,U as cleanValue,we as compileMiniGlob,L as computeCriterionFingerprint,Y as computeNextNumber,Dt as deriveEntriesFromViolations,De as deriveWalkPrefixes,ot as evidenceCloseFlow,q as extractFileKey,ce as extractIssueRefTokens,Xt as extractSlugSegment,$e as findCertificate,_e as findPendingWord,dt as freezeIssue,J as keyLeadingNumber,Ke as listPrompts,on as loadIssueFileFormatGuide,me as locateDispositionText,an as locateIssueFileFormatGuide,G as mapPriorityValue,k as matchMiniGlob,Gt as migrateLegacyCore,Yt as migrateLegacyFile,At as normalizeHeader,Mt as normalizeHeaderFile,Pe as parseCriteriaBlock,H as parseHeaderDialect,P as parseIssueDoc,N as parseRegistryFileName,ge as readEvidence,he as readEvidenceAnchors,ve as readPendingReason,bt as registerLintRulePack,hn as renderCriteriaBlockExample,Xe as renderEvidenceCloseBlock,mn as renderIssueTemplate,pn as renderIssueTemplateSkeleton,gn as renderRegistryReadmeStub,Ze as renderRerunEvidenceBlock,lt as rerunCloseFlow,ct as resolveCloseGates,nt as runCriterionViaVault,ze as runDocsCheck,Be as runDocsFix,_t as runHealth,Ct as runLint,nn as runNumberingAudit,tn as runNumberingCheck,en as runNumberingNext,vt as runTriage,Ve as runWriteDocsBaseline,Et as runWriteLintBaseline,Tt as scanOrphanSidecars,F as scanRegistryDir,Qt as scanScopeForFindings,qe as showPrompt,sn as sliceGuideFixtureSample,ft as unfreezeIssue,j as walkFilesUnderPrefixes,M as walkFilesUnderRoots};
70
+ `);return C===e?{outcome:`unchanged`,content:e,message:`已是规范形态`,transformations:[],sourceReplaced:!1}:{outcome:`rewritten`,content:C,message:`存量头部已清偿为规范形态`,transformations:l,sourceReplaced:_.sourceReplaced}}const Kt=/^([0-9]{3,4})-.+\.md$/;function qt(e,t){return e.scopes.filter(e=>t===e.registryDir||t.startsWith(`${e.registryDir}/`)).sort((e,t)=>t.registryDir.length-e.registryDir.length)[0]?.title??null}function Jt(e,t,n){let r=[],i=e.split(/\r?\n/);for(let e=0;e<i.length;e+=1){let a=i[e]??``,o=a.trim();if(o===``||t.includes(o))continue;if(o.startsWith(`# `)){let e=o.replace(/^#\s+/,``).replace(/^[0-9]{3,4}\s*(?:[-—―·..::-]+\s*|\s+)/,``).trim();if(e!==``&&t.includes(e))continue}if(o.startsWith(`## `)&&K.some(e=>e.pattern.test(o.slice(3).trim()))&&t.includes(`处置结果/`))continue;let s=/^\|([^|]*)\|([^|]*)\|$/.exec(o);if(s!==null){let a=(s[1]??``).trim(),c=(s[2]??``).trim(),l=/^:?-{2,}:?$/,u=/^\|([^|]*)\|([^|]*)\|$/.exec((i[e+1]??``).trim()),d=u!==null&&l.test((u[1]??``).trim())&&l.test((u[2]??``).trim());if(l.test(a)&&l.test(c)||d&&a!==``||a===``||n.includes(a)||t.includes(`**${a}**: ${c}`))continue;r.push(o.slice(0,60));continue}if(o.startsWith(`>`)){let e=o.slice(1).split(/[||]/),r=!0;for(let i of e){let e=/^([^::]{1,8})\s*[::]\s*(.+)$/.exec(i.trim());if(e===null)continue;let a=(e[1]??``).replace(/^\*+|\*+$/g,``).trim(),o=U(e[2]??``);!n.includes(a)&&!t.includes(o)&&(r=!1)}if(r)continue}let c=/^(?:\*\*|- \*\*|- )([^*::]{1,20})(?:\*\*)?\s*[::]\s*$/.exec(o);if(c!==null&&t.includes(`**${(c[1]??``).trim()}**:`)||o.startsWith(`- `)&&t.includes(o.slice(2).trim()))continue;let l=/^(?:\*\*|- \*\*|- |## )([^*::]{1,20})(?:\*\*)?\s*[::]\s*(.+)$/.exec(a);if(l!==null){let e=(l[1]??``).trim(),r=U(l[2]??``);if(n.includes(e)||t.includes(r))continue}let u=/^##\s*([^::]{1,10})\s*$/.exec(o);if(u!==null&&V.has(u[1]?.trim()??``))continue;let d=e-1;for(;d>=0&&(i[d]??``).trim()===``;)--d;let f=/^##\s*([^::]{1,10})\s*$/.exec((i[d]??``).trim());if(f!==null&&V.has(f[1]?.trim()??``)){let e=(f[1]??``).trim();if(n.includes(e)||t.includes(U(o)))continue}r.push(o.slice(0,60))}return r}function Yt(e){let t=e.file.split(`/`).pop()??``,n=Kt.exec(t);if(n===null)return{file:e.file,outcome:`skipped`,message:`文件名不属注册表编号形态(NNNN-slug.md)`,content:``,transformations:[]};let i=qt(e.config,e.file);if(i===null)return{file:e.file,outcome:`skipped`,message:`文件不在任何已登记 scope 的 registryDir 内——交人工`,content:``,transformations:[]};let a=``;try{a=r(u(e.root,e.file),`utf8`)}catch{return{file:e.file,outcome:`failed`,message:`读档失败(文件不存在或不可读)`,content:``,transformations:[]}}let o=Gt(a,{number:n[1]??``,scopeTitle:i,defaultPriority:e.defaultPriority,defaultSource:e.defaultSource,defaultFilingDate:e.defaultFilingDate,status:e.status});if(o.outcome!==`rewritten`)return{file:e.file,outcome:o.outcome,message:o.message,content:o.content,transformations:o.transformations};let s=Jt(a,o.content,o.sourceReplaced?[`状态`,`日期`,`优先级`,`来源`]:[`状态`,`日期`,`优先级`]);return s.length>0?{file:e.file,outcome:`failed`,message:`零丢失守卫失败:${s.length} 项原文内容未在产物在场(如 ${s[0]})——不落盘`,content:o.content,transformations:o.transformations}:(e.dryRun!==!0&&c(u(e.root,e.file),o.content),{file:e.file,outcome:`rewritten`,message:e.dryRun===!0?`dry-run 通过(未落盘)`:`存量头部已清偿为规范形态`,content:o.content,transformations:o.transformations})}function q(e,t){if(t.kind===`numericPrefix`)return/^(\d+)(?=[-._ ]|$)/.exec(e)?.[1]??null;if(t.kind===`compositePrefix`)return RegExp(`^(\\d+(?:[-._ ]\\d+){${t.groups-1}})(?=[-._ ]|$)`).exec(e)?.[1]??null;if(t.kind===`numericSuffix`){let t=e.replace(/\.[^.]+$/,``);return/(\d+)$/.exec(t)?.[1]??null}return new RegExp(t.pattern).exec(e)?.groups?.key??null}function J(e){let t=/^\d+/.exec(e);return t===null?null:Number.parseInt(t[0]??``,10)}function Y(e){let t=new Set;for(let n of e.basenames){let r=q(n,e.rule);r!==null&&t.add(r)}if(t.size===0)return{ok:!1,error:`该范围当前无编号文件,无法推断位数——请人工定号后落盘`};let n=``;for(let e of t)(J(e)??0)>(J(n)||0)&&(n=e);if(n===``)return{ok:!1,error:`该范围无数字前缀键,无法取号`};let r=n.length,i=new Set(e.reserved),a=0,o=0,s=(J(n)??0)+1;for(;;){let e=String(s).padStart(r,`0`);if(t.has(e)){a+=1,s+=1;continue}if(i.has(e)){o+=1,s+=1;continue}return{ok:!0,number:e,skippedOccupied:a,skippedReserved:o}}}function Xt(e){let t=/^\d+[-._ ](.+)\.[^.]+$/.exec(e);return t===null?null:t[1]??``}function Zt(e,t){if(e.length!==t.length)return!1;let n=[...e].sort(),r=[...t].sort();return n.every((e,t)=>e===r[t])}function Qt(e){let t=[],n=new Map;for(let t of e.basenames){let r=q(t,e.rule);r!==null&&n.set(r,[...n.get(r)??[],t])}let r=new Map(e.reserved.map(e=>[e.number,e.reason])),i=new Set;for(let[a,o]of n){if(o.length>=2){let n=e.allowlist.findIndex((t,n)=>!i.has(n)&&t.number===a&&Zt(t.files,o.map(t=>e.toRepoRelative(t))));n===-1?t.push({kind:`duplicate`,scopeKey:e.scopeKey,message:`scope「${e.scopeKey}」编号 ${a} 撞号:${o.join(`、`)}——先入库者保号,后到线顺延清偿`}):i.add(n);continue}let n=r.get(a);n!==void 0&&t.push({kind:`reservedOccupied`,scopeKey:e.scopeKey,message:`scope「${e.scopeKey}」保留号 ${a} 被占用(${o.join(`、`)};保留理由:${n}——保留号不回收复用,请让号重编`})}if([...n.keys()].some(e=>e.length>1&&e.startsWith(`0`))){let r=new Set([...n.keys()].map(e=>e.length));r.size>1&&t.push({kind:`widthInconsistent`,scopeKey:e.scopeKey,message:`scope「${e.scopeKey}」零填充位数混用(族内键长 ${[...r].sort((e,t)=>e-t).join(`/`)}):${[...n.keys()].slice(0,6).join(`、`)}`})}e.allowlist.forEach((r,a)=>{if(i.has(a))return;let o=r.files.filter(e=>{let t=e.split(`/`).pop()??``;return(n.get(r.number)??[]).includes(t)});t.push({kind:`allowlistStale`,scopeKey:e.scopeKey,message:`scope「${e.scopeKey}」豁免条目(${r.number},${r.status})已无对应现行撞号组——棘轮只许缩短,须同步删除该条目(登记集:${r.files.join(`、`)};磁盘现状:${o.length>0?o.join(`、`):`无`})`})});let a=[];if(e.slugDigitPolicy!==`allow`)for(let n of e.basenames){let r=Xt(n);if(r===null||r===``||!/\d/.test(r))continue;let i=e.toRepoRelative(n);e.slugDigitAllowlist.some(e=>k(e,i)?(a.push(e),!0):!1)||t.push({kind:`slugContainsDigit`,scopeKey:e.scopeKey,message:`scope「${e.scopeKey}」slug 段含数字:${n}(白名单登记请到配置 slugDigitAllowlist,逐条带理由)`})}return{findings:t,slugDigitAllowlistHits:a}}function $t(e){let t=[];for(let n of e.allowlist)e.hits.has(n)||t.push({kind:`slugDigitAllowlistStale`,scopeKey:`global`,message:`slug 白名单条目「${n}」在启用域内已匹配不到任何文件——棘轮只许缩短,须同步删除该条目`});return t}function en(e){let t=[],n=new RegExp(e.numberishPattern),r=new Map,i=new Set;for(let t of e.files){let a=t.split(`/`).pop()??``;if(!n.test(a)||e.registryDirs.some(e=>t===e||t.startsWith(`${e}/`))||e.pending.find(e=>k(e.glob,t)?(i.add(e.glob),!0):!1)!==void 0)continue;let o=t.split(`/`).slice(0,-1).join(`/`);r.set(o,[...r.get(o)??[],a])}for(let e of[...r.keys()].sort((e,t)=>e.localeCompare(t)))t.push({kind:`coverageGap`,message:`未登记编号区:目录 ${e===``?`(仓库根)`:e} 有 ${r.get(e)?.length??0} 个编号文件未被任何 scope/pending 覆盖——${(r.get(e)??[]).join(`、`)}(请在 lzi-issues.config.ts 登记 scope 或 pending 并注明理由)`});for(let n of e.pending)i.has(n.glob)||t.push({kind:`pendingStale`,message:`pending 条目「${n.glob}」已匹配不到任何编号文件——棘轮只许缩短,须同步删除该条目(登记理由:${n.reason})`});return t}const X={kind:`numericPrefix`};function Z(e,t){return j(e,[t]).map(e=>e.split(`/`).pop()??``)}function tn(e){let t=e.scopeKey!==void 0&&e.dirRel!==void 0,n=e.scopeKey===void 0&&e.dirRel===void 0;if(t||n)return{ok:!1,error:`取号须且仅须指定 --scope <key> 或 --dir <目录> 之一`};if(e.scopeKey!==void 0){let t=e.config.scopes.find(t=>t.key===e.scopeKey);if(t===void 0)return{ok:!1,error:`scope「${e.scopeKey}」未登记(请在 lzi-issues.config.ts 注册)`};let n=Y({basenames:Z(e.root,t.registryDir),rule:X,reserved:t.numbering.reserved.map(e=>e.number)});return n.ok?{ok:!0,number:n.number,target:t.key,skippedOccupied:n.skippedOccupied,skippedReserved:n.skippedReserved}:n}let r=e.config.scopes.filter(t=>t.registryDir===e.dirRel);if(r.length===0)return{ok:!1,error:`目录「${e.dirRel}」未登记为任何 scope 的注册表(请在 lzi-issues.config.ts 注册后再取号)`};let i=[...new Set(r.flatMap(e=>e.numbering.reserved.map(e=>e.number)))],a=Y({basenames:Z(e.root,e.dirRel??``),rule:X,reserved:i});return a.ok?{ok:!0,number:a.number,target:e.dirRel??``,skippedOccupied:a.skippedOccupied,skippedReserved:a.skippedReserved}:a}function nn(e){let t=e.dirRel===void 0?e.config.scopes:e.config.scopes.filter(t=>t.registryDir===e.dirRel),n=[],r=0,i=e.config.slugDigitAllowlist.map(e=>e.glob),a=new Set;for(let o of t){let t=Z(e.root,o.registryDir);r+=t.length;let s=e.dirRel===void 0?e.config.numbering.allowlist.filter(e=>e.scopeKey===o.key).map(e=>({...e,files:[...e.files]})):e.config.numbering.allowlist.filter(t=>t.scopeKey===o.key&&t.files.some(t=>t.startsWith(`${e.dirRel}/`))).map(e=>({...e,files:[...e.files]})),c=Qt({scopeKey:o.key,basenames:t,rule:X,reserved:o.numbering.reserved,allowlist:s,slugDigitPolicy:o.slugDigitPolicy,slugDigitAllowlist:i,toRepoRelative:e=>`${o.registryDir}/${e}`});n.push(...c.findings);for(let e of c.slugDigitAllowlistHits)a.add(e)}return e.dirRel===void 0&&n.push(...$t({allowlist:i,hits:a})),{findings:n,scopeCount:t.length,scannedFiles:r}}function rn(e){let t=[];for(let n of e.config.numbering.audit.roots)t.push(...M(u(e.root,n),{excludeGlobs:e.config.numbering.globalExclusions.map(e=>e.glob)}).map(e=>n===``?e:`${n}/${e}`));return{findings:en({files:t,numberishPattern:e.config.numbering.audit.numberishPattern,registryDirs:e.config.scopes.map(e=>e.registryDir),pending:e.config.numbering.audit.pending}),scannedFiles:t.length}}const Q=`<!-- issue-file-format-fixture:start -->`,$=`<!-- issue-file-format-fixture:end -->`,an=`docs/guides/issue-file-format.md`;function on(){let e=l(f(import.meta.url.replace(`file://`,``)));for(let n=0;n<10;n+=1){let n=u(e,an);if(t(n))return n;let r=l(e);if(r===e)break;e=r}throw Error(`未定位到格式指南:${an}(自模块位置向上 10 层内)`)}function sn(){return r(on(),`utf8`)}function cn(e){let t=e.indexOf(Q),n=e.indexOf($);if(t===-1)throw Error(`指南活样板起点标记缺席:${Q}`);if(n===-1)throw Error(`指南活样板终点标记缺席:${$}`);if(n<t)throw Error(`指南活样板标记次序颠倒(终点在起点之前)`);return e.slice(t+40,n).replace(/^\n+/,``).replace(/\n+$/,``)}const ln=[`## 一、问题动机`,``,`> <问题原文逐字记录,禁止转述改写>`,``,`客观证据(报错/复现步骤/涉及文件/日志路径):`,`- <证据>`,``].join(`
71
+ `),un=[`## 二、现状与证据`,``,`<排查链路与实证;无法确认根因时如实标注「根因待治理时核实」>`,``].join(`
72
+ `),dn=[`## 三、处置方向`,``,`【1】备选方案和分析`,`- <备选>:<分析>`,``,`【2】推荐方案和分析`,`- <推荐>:<分析;能自决的直接执行,裁决类停下等用户裁定>`,``].join(`
73
+ `),fn=[`## 四、处置结果/验证口径`,``,`<随治理进展回填;判据以 criteria 围栏块声明;收口由 verify-close 回填证据锚点>`,``].join(`
74
+ `);function pn(e){return e.map(e=>e.slug?`[${e.number}](./${e.number}-${e.slug}.md)`:`[${e.number}]`).join(`;`)}function mn(){return[`# <NNNN> - <一句话标题:问题是什么,禁笼统词>`,``,`**优先级**: <P1|P2|P3>`,`**状态**: ${E.red}`,`**立案日期**: <YYYY-MM-DD>`,`**所属域**: <scope title>`,`**来源**: <问题原文出处(用户裁定/会话记录/自动巡检 + 日期)>`,`**沿革**: <可选:前序 issue 引用,如 [0035](./0035-<slug>.md)>`,`**阻塞**: <可选:被阻塞于哪些 issue;全部 🟢 后方可收口>`,``,ln,un,dn,fn].join(`
75
+ `)}function hn(e){let t=[`# ${e.number} - ${e.title}`,``,`**优先级**: ${e.priority}`,`**状态**: ${E.red}`,`**立案日期**: ${e.date}`,`**所属域**: ${e.scopeTitle}`,`**来源**: ${e.source}`];return e.historyRefs!==void 0&&e.historyRefs.length>0&&t.push(`**${w}**: ${pn(e.historyRefs)}`),e.blockedByRefs!==void 0&&e.blockedByRefs.length>0&&t.push(`**${T}**: ${pn(e.blockedByRefs)}`),t.push(``),[t.join(`
76
+ `),ln,un,dn,fn].join(`
77
+ `)}function gn(){return["```criteria",`# 一行一判据:label|channel|cwd|command(四段竖线分隔,段内禁竖线)`,`# channel 限字母数字起首的 [A-Za-z0-9._-];cwd 相对仓库根;command 为退出码即判据的自动化命令`,`# summary 行唯一且非空——真跑收口 --criteria-from-issue 的机器可读载体`,`summary: 示例:单包测试全绿`,`单包测试|test|packages/demo|bun run test`,"```",``].join(`
78
+ `)}function _n(e,t){let n=t===null?"- 标准模板:`lzi-issues template`(字段契约 `--format json`);完整格式规范见 lzi-issues 仓 docs/guides/issue-file-format.md(本仓未携带指南文件,不落链接)":"- 标准模板:`lzi-issues template`;完整格式规范:[issue-file-format]("+t+`)`;return[`# ${e} issue 注册表`,``,"本目录是 lzi-issues 注册表(`NNNN-<slug>.md`,目录即编号命名空间,编号独立序列)。",``,`## 建档`,``,"- `lzi-issues create --title <标题> --scope <key>`(编号走门禁取号,禁止手工定号)",n,``,`## 状态行(处理情况第一入口)`,``,`- ${E.red}:待处理`,`- ${E.yellow}(须括注 ≥4 字挂起原因)`,`- ${E.green}(须处置段 + 证据锚点;收口走 \`lzi-issues verify-close\`)`,`- ${E.frozen}(仅经 \`lzi-issues freeze\`,理由须含「用户裁定」)`,`- ${E.canceled}(须括注取消原因)`,``,`## 正文四段`,``,`一、问题动机 → 二、现状与证据 → 三、处置方向(【1】备选方案和分析 / 【2】推荐方案和分析)→ 四、处置结果/验证口径`,``].join(`
79
+ `)}const vn=[{name:`number`,required:!0,description:`scope 内编号,4 位零填充,由编号门禁取号分配(写入时刻确定)`},{name:`slug`,required:!0,description:`ASCII kebab slug(小写字母/数字/连字符,禁中文),slug 段默认禁数字(slugDigitPolicy)`},{name:`title`,required:!0,description:`一句话标题:问题是什么,禁笼统词`},{name:`priority`,required:!0,description:`优先级,P1 | P2 | P3`},{name:`status`,required:!0,description:`状态行五态;建档固定 🔴 待处理,流转走工具(verify-close / freeze 等)`},{name:`立案日期`,required:!0,description:`YYYY-MM-DD`},{name:`所属域`,required:!0,description:`config 登记的 scope title`},{name:`来源`,required:!0,description:`问题原文出处(用户裁定 / 会话记录 / 自动巡检 + 日期)`},{name:`沿革`,required:!1,description:`前序 issue 引用,markdown 相对链接或纯编号形态`},{name:`阻塞`,required:!1,description:`被阻塞于哪些 issue;全部 🟢 后方可收口(blockingMustBeGreen 门禁)`}];export{z as ADJUDICATION_KEYWORD,g as ASCII_KEBAB_SLUG_PATTERN,T as BLOCKING_LINE_KEY,E as CANONICAL_STATUS_LABELS,St as CORE_LINT_RULES,re as CRITERIA_CHANNEL_PATTERN,ne as CRITERIA_FENCE_CLOSE_PATTERN,C as CRITERIA_FENCE_OPEN_PATTERN,K as DISPOSITION_CANDIDATES,x as DISPOSITION_PLACEHOLDER_PATTERN,te as DISPOSITION_PLACEHOLDER_WINDOW_LINES,b as DISPOSITION_TITLE_KEYWORD,S as EVIDENCE_ANCHOR_MARKERS,$ as GUIDE_FIXTURE_END_MARK,Q as GUIDE_FIXTURE_START_MARK,V as HEADING_ALONE_METADATA_KEYS,w as HISTORY_LINE_KEY,X as ISSUES_KEY_RULE,an as ISSUE_FILE_FORMAT_GUIDE_RELATIVE,oe as ISSUE_REF_TOKEN_PATTERN,ae as LEGACY_STATUS_VOCABULARY,ie as META_LINE_PREFIX_SOURCE_TEMPLATE,O as MiniGlobError,y as PENDING_REASON_PATTERN,h as REGISTRY_FILE_PATTERN,v as STATUS_LINE_LOCATE_PATTERN,_ as STATUS_LINE_PREFIX_SOURCE,ee as STATUS_LINE_SCAN_LINES,vn as TEMPLATE_FIELDS,it as acquireRunLock,I as applyGreenFlip,en as auditCoverage,It as backfillRequiredFields,le as buildMetaLineLocatePattern,Ft as canonicalizeH1,W as canonicalizeStatusValue,R as certificatePath,B as classifyIssue,D as classifyStatusLine,U as cleanValue,we as compileMiniGlob,L as computeCriterionFingerprint,Y as computeNextNumber,Dt as deriveEntriesFromViolations,De as deriveWalkPrefixes,ot as evidenceCloseFlow,q as extractFileKey,ce as extractIssueRefTokens,Xt as extractSlugSegment,$e as findCertificate,_e as findPendingWord,dt as freezeIssue,J as keyLeadingNumber,Ke as listPrompts,sn as loadIssueFileFormatGuide,me as locateDispositionText,on as locateIssueFileFormatGuide,G as mapPriorityValue,k as matchMiniGlob,Gt as migrateLegacyCore,Yt as migrateLegacyFile,At as normalizeHeader,Mt as normalizeHeaderFile,Pe as parseCriteriaBlock,H as parseHeaderDialect,P as parseIssueDoc,N as parseRegistryFileName,ge as readEvidence,he as readEvidenceAnchors,ve as readPendingReason,$t as reconcileSlugDigitAllowlist,bt as registerLintRulePack,gn as renderCriteriaBlockExample,Xe as renderEvidenceCloseBlock,hn as renderIssueTemplate,mn as renderIssueTemplateSkeleton,_n as renderRegistryReadmeStub,Ze as renderRerunEvidenceBlock,lt as rerunCloseFlow,ct as resolveCloseGates,nt as runCriterionViaVault,ze as runDocsCheck,Be as runDocsFix,_t as runHealth,Ct as runLint,rn as runNumberingAudit,nn as runNumberingCheck,tn as runNumberingNext,vt as runTriage,Ve as runWriteDocsBaseline,Et as runWriteLintBaseline,Tt as scanOrphanSidecars,F as scanRegistryDir,Qt as scanScopeForFindings,qe as showPrompt,cn as sliceGuideFixtureSample,ft as unfreezeIssue,j as walkFilesUnderPrefixes,M as walkFilesUnderRoots};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longzai-intelligence-issues/ledger",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -27,7 +27,7 @@
27
27
  "clean": "lzi-dev-cli clean"
28
28
  },
29
29
  "dependencies": {
30
- "@longzai-intelligence-issues/config": "0.0.4"
30
+ "@longzai-intelligence-issues/config": "0.0.5"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/bun": "^1.4.2"
@@ -166,6 +166,53 @@ describe('runNumberingCheck', () => {
166
166
 
167
167
  expect(result.findings.some((f) => f.kind === 'duplicate')).toBe(true);
168
168
  });
169
+
170
+ test('多 forbid scope 仓:全局 slug 白名单条目在任一 scope 命中即零 stale', () => {
171
+ writeFileSync(join(FIXTURE_ROOT, 'docs/domain-issues/0002-model-v2.md'), '');
172
+
173
+ const result = runNumberingCheck({
174
+ root: FIXTURE_ROOT,
175
+ config: parseLziIssuesConfig({
176
+ schemaVersion: 1,
177
+ scopes: CONFIG.scopes,
178
+ numbering: { audit: { roots: ['docs'], numberishPattern: '^\\d{1,4}[-._]', pending: [] } },
179
+ slugDigitAllowlist: [
180
+ { glob: 'docs/domain-issues/0002-model-v2.md', reason: 'v2 为版本语义术语' },
181
+ ],
182
+ }),
183
+ });
184
+
185
+ expect(result.findings).toEqual([]);
186
+ });
187
+
188
+ test('多 forbid scope 仓:条目指向不存在文件仍判 stale(单条不随 scope 数倍增)', () => {
189
+ const result = runNumberingCheck({
190
+ root: FIXTURE_ROOT,
191
+ config: parseLziIssuesConfig({
192
+ schemaVersion: 1,
193
+ scopes: CONFIG.scopes,
194
+ numbering: { audit: { roots: ['docs'], numberishPattern: '^\\d{1,4}[-._]', pending: [] } },
195
+ slugDigitAllowlist: [{ glob: 'docs/issues/0099-ghost.md', reason: '幽灵条目' }],
196
+ }),
197
+ });
198
+
199
+ expect(result.findings.filter((f) => f.kind === 'slugDigitAllowlistStale')).toHaveLength(1);
200
+ });
201
+
202
+ test('增量档不跑 slug 白名单失效对账(扫描面不完整防假阳性)', () => {
203
+ const result = runNumberingCheck({
204
+ root: FIXTURE_ROOT,
205
+ config: parseLziIssuesConfig({
206
+ schemaVersion: 1,
207
+ scopes: CONFIG.scopes,
208
+ numbering: { audit: { roots: ['docs'], numberishPattern: '^\\d{1,4}[-._]', pending: [] } },
209
+ slugDigitAllowlist: [{ glob: 'docs/issues/0099-ghost.md', reason: '幽灵条目' }],
210
+ }),
211
+ dirRel: 'docs/issues',
212
+ });
213
+
214
+ expect(result.findings.some((f) => f.kind === 'slugDigitAllowlistStale')).toBe(false);
215
+ });
169
216
  });
170
217
 
171
218
  describe('runNumberingAudit', () => {
@@ -6,6 +6,7 @@ import {
6
6
  extractFileKey,
7
7
  extractSlugSegment,
8
8
  keyLeadingNumber,
9
+ reconcileSlugDigitAllowlist,
9
10
  scanScopeForFindings,
10
11
  } from '@/numbering/numbering.core';
11
12
 
@@ -133,11 +134,10 @@ describe('scanScopeForFindings', () => {
133
134
  allowlist: [],
134
135
  slugDigitAllowlist: [] as string[],
135
136
  toRepoRelative: (basename: string) => `docs/issues/${basename}`,
136
- fullMode: true,
137
137
  };
138
138
 
139
139
  test('撞号(豁免精确覆盖前)', () => {
140
- const findings = scanScopeForFindings({
140
+ const { findings } = scanScopeForFindings({
141
141
  ...baseInput,
142
142
  basenames: ['0053-a.md', '0053-b.md'],
143
143
  });
@@ -148,7 +148,7 @@ describe('scanScopeForFindings', () => {
148
148
  });
149
149
 
150
150
  test('豁免精确覆盖(排序文件集双向相等)免报', () => {
151
- const findings = scanScopeForFindings({
151
+ const { findings } = scanScopeForFindings({
152
152
  ...baseInput,
153
153
  basenames: ['0053-b.md', '0053-a.md'],
154
154
  allowlist: [
@@ -166,7 +166,7 @@ describe('scanScopeForFindings', () => {
166
166
  });
167
167
 
168
168
  test('豁免文件集不精确(漂移)判 duplicate 且条目 stale', () => {
169
- const findings = scanScopeForFindings({
169
+ const { findings } = scanScopeForFindings({
170
170
  ...baseInput,
171
171
  basenames: ['0053-a.md', '0053-b.md'],
172
172
  allowlist: [
@@ -185,7 +185,7 @@ describe('scanScopeForFindings', () => {
185
185
  });
186
186
 
187
187
  test('保留号被占用(报文含理由)', () => {
188
- const findings = scanScopeForFindings({
188
+ const { findings } = scanScopeForFindings({
189
189
  ...baseInput,
190
190
  basenames: ['0081-x.md'],
191
191
  reserved: [{ number: '0081', reason: '历史空洞不回收' }],
@@ -196,7 +196,7 @@ describe('scanScopeForFindings', () => {
196
196
  });
197
197
 
198
198
  test('零填充位数混用', () => {
199
- const findings = scanScopeForFindings({
199
+ const { findings } = scanScopeForFindings({
200
200
  ...baseInput,
201
201
  basenames: ['0035-a.md', '0123-b.md', '123-c.md'],
202
202
  });
@@ -205,7 +205,7 @@ describe('scanScopeForFindings', () => {
205
205
  });
206
206
 
207
207
  test('无填充自然增长族不判混用', () => {
208
- const findings = scanScopeForFindings({
208
+ const { findings } = scanScopeForFindings({
209
209
  ...baseInput,
210
210
  basenames: ['1-a.md', '9-b.md', '35-c.md'],
211
211
  });
@@ -214,7 +214,7 @@ describe('scanScopeForFindings', () => {
214
214
  });
215
215
 
216
216
  test('豁免条目无现行撞号组判 stale(棘轮只许缩短)', () => {
217
- const findings = scanScopeForFindings({
217
+ const { findings } = scanScopeForFindings({
218
218
  ...baseInput,
219
219
  basenames: ['0001-a.md'],
220
220
  allowlist: [
@@ -232,7 +232,7 @@ describe('scanScopeForFindings', () => {
232
232
  });
233
233
 
234
234
  test('slug 含数字违规与白名单豁免', () => {
235
- const findings = scanScopeForFindings({
235
+ const { findings } = scanScopeForFindings({
236
236
  ...baseInput,
237
237
  basenames: ['0048-runs-list-grace.md', '0049-model-v2.md'],
238
238
  slugDigitPolicy: 'forbid',
@@ -243,7 +243,7 @@ describe('scanScopeForFindings', () => {
243
243
  });
244
244
 
245
245
  test('slug 含数字未豁免违规', () => {
246
- const findings = scanScopeForFindings({
246
+ const { findings } = scanScopeForFindings({
247
247
  ...baseInput,
248
248
  basenames: ['0049-model-v2.md'],
249
249
  slugDigitPolicy: 'forbid',
@@ -252,25 +252,49 @@ describe('scanScopeForFindings', () => {
252
252
  expect(findings[0]?.kind).toBe('slugContainsDigit');
253
253
  });
254
254
 
255
- test('全量档白名单失效对账(增量档关闭)', () => {
256
- const full = scanScopeForFindings({
255
+ test('slug 白名单命中集随扫描结果返回(豁免实匹配条目入集)', () => {
256
+ const { findings, slugDigitAllowlistHits } = scanScopeForFindings({
257
257
  ...baseInput,
258
- basenames: ['0001-a.md'],
258
+ basenames: ['0049-model-v2.md'],
259
259
  slugDigitPolicy: 'forbid',
260
- slugDigitAllowlist: ['docs/issues/0099-ghost.md'],
260
+ slugDigitAllowlist: ['docs/issues/0049-model-v2.md', 'docs/issues/0099-ghost.md'],
261
261
  });
262
262
 
263
- expect(full.some((f) => f.kind === 'slugDigitAllowlistStale')).toBe(true);
263
+ expect(findings).toEqual([]);
264
+ expect(slugDigitAllowlistHits).toEqual(['docs/issues/0049-model-v2.md']);
265
+ });
264
266
 
265
- const incremental = scanScopeForFindings({
267
+ test('slug 含数字但白名单空时命中集为空(豁免面无实匹配)', () => {
268
+ const { slugDigitAllowlistHits } = scanScopeForFindings({
266
269
  ...baseInput,
267
- basenames: ['0001-a.md'],
270
+ basenames: ['0049-model-v2.md'],
268
271
  slugDigitPolicy: 'forbid',
269
- slugDigitAllowlist: ['docs/issues/0099-ghost.md'],
270
- fullMode: false,
271
272
  });
272
273
 
273
- expect(incremental.some((f) => f.kind === 'slugDigitAllowlistStale')).toBe(false);
274
+ expect(slugDigitAllowlistHits).toEqual([]);
275
+ });
276
+ });
277
+
278
+ describe('reconcileSlugDigitAllowlist', () => {
279
+ test('聚合命中集内条目存活,命中不到判 stale(scopeKey 为 global)', () => {
280
+ const alive = reconcileSlugDigitAllowlist({
281
+ allowlist: ['docs/issues/0049-model-v2.md', 'packages/a/docs/issues/0001-x-2.md'],
282
+ hits: new Set(['docs/issues/0049-model-v2.md']),
283
+ });
284
+
285
+ expect(alive).toHaveLength(1);
286
+ expect(alive[0]?.kind).toBe('slugDigitAllowlistStale');
287
+ expect(alive[0]?.scopeKey).toBe('global');
288
+ expect(alive[0]?.message).toContain('packages/a/docs/issues/0001-x-2.md');
289
+ });
290
+
291
+ test('命中集覆盖全部条目零 stale', () => {
292
+ const findings = reconcileSlugDigitAllowlist({
293
+ allowlist: ['docs/issues/0049-model-v2.md'],
294
+ hits: new Set(['docs/issues/0049-model-v2.md']),
295
+ });
296
+
297
+ expect(findings).toEqual([]);
274
298
  });
275
299
  });
276
300
 
@@ -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
  }