@longzai-intelligence-github-markdown/cli 0.0.5 → 0.0.6

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.
Files changed (2) hide show
  1. package/dist/index.js +4 -4
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -219,11 +219,11 @@ ${Y.default.gray(lf+sf.repeat(a+2)+uf)}
219
219
  `,` `)}function Vp(e,t,n){let r=[];r.push(`# GitHub Repos ${e}`,``),r.push(`共 ${t.length} 仓。`,``),r.push(`| 仓库 | 分支 | 提交数 | 最近提交 |`),r.push(`| --- | --- | --- | --- |`);for(let e of t){let t=e.branch??(e.detached?`HEAD(detached)`:`(无提交)`),n=`| [${Bp(e.repo)}](${e.repo}/README.md) | ${Bp(t)} | ${e.commitCount??`-`} | ${e.lastCommitShort??`-`} |`;r.push(n)}r.push(``);let i=r.join(`
220
220
  `),a=ap(i);return{content:`${tp({feed_pipeline_id:`github-markdown`,plugin:`github`,source_id:`clones-root`,generated_at:n,content_hash:a,tags:[],org:e,repo_count:t.length})}\n${i}`,hash:a}}const Hp=`repositories`;function Up(e,t){let n=e.reduce((e,t)=>e+t.repoCount,0),r=[];r.push(`# GitHub 索引目录册`,``),r.push(`共 ${e.length} 个组织 / ${n} 个仓库。`,``),r.push(`| 组织 | 仓库数 | 目录 |`),r.push(`| --- | --- | --- |`);for(let t of e){let e=`| [${t.org}](${Hp}/${t.org}/) | ${t.repoCount} | ${Hp}/${t.org}/ |`;r.push(e)}r.push(``);let i=r.join(`
221
221
  `),a=ap(i);return{content:`${tp({kind:`catalog`,feed_pipeline_id:`github-markdown`,plugin:`github`,source_id:`clones-root`,generated_at:t,content_hash:a,tags:[],org_count:e.length,repo_total:n})}\n${i}`,hash:a}}function Wp(){return(/* @__PURE__ */ '# longzai-intelligence-github-data,,GitHub 克隆仓索引库:由 `lzi-github-markdown`(longzai-intelligence-github-markdown 仓)从本地克隆根(owner/repo 两层标准)只读扫描生成并维护。,,## 组织规则,,- 本目录是索引库:只存索引信息(git 元数据 + 指向链接),不复制任何项目内容。,- root 只保留索引库元信息(README.md/catalog.md/.gitignore)与归档子文件夹 `repositories/`、`favorites/`、`tracking/`、`pending/`;组织数据不平铺 root。,- `favorites/` 为手工维护的关注点收藏区(区内一切皆关注点:文件夹与文件均为关注点;仓库仅作叶子档案内链接);机制规则自持于区内专属说明文件。,- `tracking/` 为手工维护的外部仓跟踪登记区(`tracking/日期/加入记录` 形态,纯 markdown 记录);库内禁止任何 clone 与 fork;机制规则自持于 `tracking/README.md`。,- `pending/` 为手工维护的外部仓待入库暂存区(`pending/日期/暂存记录` 形态,避免入库动作被阻塞);正式入库以 `tracking/` 为唯一正本;机制规则自持于 `pending/README.md`。,- `repositories/<org>/<repo>/README.md` 一仓一档索引条目;`repositories/<org>/README.md` 组织索引表。,- 本库内部无任何嵌套 git 仓库(与按日一仓类数据区不同);如需版本化,整体作为单一 git 仓、且仅在显式 `--commit` 时提交。,- 跨库检索与统计走 `catalog.md`;单个组织目录自包含,可独立消费。,,## 基础配置(数据区自持,参考 lzi-insight 标准件),,- `lzi-github-markdown.config.ts`:就地配置(以工具命名,全局 ADR 0090);仅 `dataRoot: "."` 自指单字段(禁 version——代际由包 npm 版本承载,全局 ADR 0097 决策 10);机器无关、入库跟踪(决策 8);配置文件必备、缺失即报错(决策 9);在本目录直接执行 convert/verify/catalog。,- `.env` / `.env.example`:跨项目路径唯一 env 通道 LZI_GITHUB_MARKDOWN_CLONES_ROOT(本地克隆根);.example 为中性占位符模板(可入库),.env 为机器本地实值(gitignored)。,- `lzi-env.config.ts`:本数据区环境变量显式白名单(lzi-env-cli check 门禁消费;type-only import 零依赖可加载)。,- 以上配置均为机器无关或中性模板,不携带索引数据;`repositories/` 与目录册之外不新增数据类文件。,,## 存储纪律,,- 元数据一律随 markdown frontmatter(YAML),库内禁任何 `.json` 元数据文件。,- 纯索引库:无 UI 代码、无 package.json、无 devDeps。,- 幂等:`content_hash`(正文规范化 SHA-256)相同跳过、不同覆盖;重跑 `convert` 零变更时零新写入。,- 保留字:root 级 `README.md`/`catalog.md`/`.gitignore`/`repositories/`/`favorites/`/`tracking/`/`pending/`;组织目录内 `README.md`(仓库名冲突拒绝)。,- 本地克隆根只读消费(不写不抓取);非标准克隆条目(顶层仓库/非法 owner 名/空 owner)跳过并报告,不修复。,'.split(`,`)).join(`
222
- `)}var Gp=class{dataRoot;writer;constructor(e){this.dataRoot=e.dataRoot,this.writer=_p({dataRoot:this.dataRoot,partitionDir:e=>d(this.dataRoot,Hp,e),entryRelPath:(e,t)=>`${Hp}/${e}/${t.repo}/README.md`,renderEntry:zp,sortEntries:e=>[...e].sort((e,t)=>e.repo<t.repo?-1:+(e.repo>t.repo)),renderPartitionReadme:Vp,validatePartition:e=>{if(!Dp.test(e))throw Error(`组织名非法:${e}(应为 GitHub 用户/组织名字符集)`)},validateEntry:(e,t)=>{if(t.repo===`README.md`)throw Error(`仓库名与组织目录保留字冲突:${e}/${t.repo}(README.md 为组织索引文件名)`)}})}orgDir(e){return d(this.dataRoot,Hp,e)}async writeOrg(e,t,n={}){let r=await this.writer.writePartition(e,t,{dryRun:n.dryRun});return{org:e,orgDir:this.orgDir(e),written:r.written,skipped:r.skipped}}};function Kp(e){let t=d(e,Hp);if(!r(t))return[];let n=[],i=[...o(t,{withFileTypes:!0})].sort((e,t)=>e.name<t.name?-1:1);for(let e of i){if(!e.isDirectory()||e.name.startsWith(`.`))continue;let i=d(t,e.name);if(!r(d(i,`README.md`)))continue;let a=0;for(let e of o(i,{withFileTypes:!0}))e.isDirectory()&&!e.name.startsWith(`.`)&&r(d(i,e.name,`README.md`))&&(a+=1);n.push({org:e.name,repoCount:a})}return n}function qp(e){return hp({dataRoot:e.dataRoot,gitIdentity:e.gitIdentity,rootFiles:[{path:`.gitignore`,content:`# macOS
222
+ `)}const Gp=[{kind:`tracking`,title:`跟踪登记区`,dateKey:`first_tracked_at`},{kind:`pending`,title:`待入库暂存区`,dateKey:`pending_at`}];function Kp(e){return e.replace(/^['"]|['"]$/g,``)}const qp=/^\d{4}-\d{2}-\d{2}$/;function Jp(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``)}function Yp(e,t){let n=Jp(e),r=Jp(t);return n===r?n:`${n}-${r}`}function Xp(e,t){let n=d(e,t.kind),i=[],s=[];if(!r(n))return{def:t,entries:i,issues:s};let c=[...o(n,{withFileTypes:!0})].sort((e,t)=>e.name<t.name?-1:1);for(let e of c){if(e.name===`README.md`&&e.isFile())continue;if(!e.isDirectory()){s.push(`${t.kind}/ 顶层散落非目录项 ${e.name}(区根仅允许日期目录与 README.md)`);continue}if(!qp.test(e.name)){s.push(`${t.kind}/${e.name} 不是日期目录(形态 YYYY-MM-DD)`);continue}let r=o(d(n,e.name),{withFileTypes:!0});for(let o of r){let r=`${t.kind}/${e.name}/${o.name}`;if(!o.isFile()||!o.name.endsWith(`.md`)){s.push(`${r} 不是 markdown 记录文件`);continue}let c=o.name.slice(0,-3),{head:l}=rp(a(d(n,e.name,o.name),`utf8`)),u=l.org===void 0?void 0:Kp(l.org),f=l.repo===void 0?void 0:Kp(l.repo);if(u===void 0||f===void 0){s.push(`${r} frontmatter 缺必填键 org 或 repo`);continue}l.repo_url===void 0&&s.push(`${r} frontmatter 缺必填键 repo_url`);let p=l[t.dateKey],m=p===void 0?void 0:Kp(p);m===void 0?s.push(`${r} frontmatter 缺必填键 ${t.dateKey}`):m!==e.name&&s.push(`${r} frontmatter ${t.dateKey}=${m} 与所在日期目录 ${e.name} 不一致`);let h=Yp(u,f);c===h?i.push({area:t.kind,org:u,repo:f,date:e.name,file:r}):s.push(`${r} 文件名与登记名不符(期望 ${h}.md)`)}}return{def:t,entries:i,issues:s}}function Zp(e){let t=Gp.map(t=>Xp(e,t)),n=new Set(t[0].entries.map(e=>`${e.org}/${e.repo}`)),r=t[1],i=[];for(let e of r.entries)n.has(`${e.org}/${e.repo}`)&&i.push(`${e.file} 已正式入库 tracking 仍滞留 pending(转正须同作业面移除暂存条目)`);return i.length>0?[t[0],{def:r.def,entries:r.entries,issues:[...r.issues,...i]}]:t}var Qp=class{dataRoot;writer;constructor(e){this.dataRoot=e.dataRoot,this.writer=_p({dataRoot:this.dataRoot,partitionDir:e=>d(this.dataRoot,Hp,e),entryRelPath:(e,t)=>`${Hp}/${e}/${t.repo}/README.md`,renderEntry:zp,sortEntries:e=>[...e].sort((e,t)=>e.repo<t.repo?-1:+(e.repo>t.repo)),renderPartitionReadme:Vp,validatePartition:e=>{if(!Dp.test(e))throw Error(`组织名非法:${e}(应为 GitHub 用户/组织名字符集)`)},validateEntry:(e,t)=>{if(t.repo===`README.md`)throw Error(`仓库名与组织目录保留字冲突:${e}/${t.repo}(README.md 为组织索引文件名)`)}})}orgDir(e){return d(this.dataRoot,Hp,e)}async writeOrg(e,t,n={}){let r=await this.writer.writePartition(e,t,{dryRun:n.dryRun});return{org:e,orgDir:this.orgDir(e),written:r.written,skipped:r.skipped}}};function $p(e){let t=d(e,Hp);if(!r(t))return[];let n=[],i=[...o(t,{withFileTypes:!0})].sort((e,t)=>e.name<t.name?-1:1);for(let e of i){if(!e.isDirectory()||e.name.startsWith(`.`))continue;let i=d(t,e.name);if(!r(d(i,`README.md`)))continue;let a=0;for(let e of o(i,{withFileTypes:!0}))e.isDirectory()&&!e.name.startsWith(`.`)&&r(d(i,e.name,`README.md`))&&(a+=1);n.push({org:e.name,repoCount:a})}return n}function em(e){return hp({dataRoot:e.dataRoot,gitIdentity:e.gitIdentity,rootFiles:[{path:`.gitignore`,content:`# macOS
223
223
  .DS_Store
224
224
 
225
225
  # 机器本地配置(.env 本地实值;配置文件入库跟踪,不忽略)
226
226
  .env
227
- `},{path:`.env.example`,content:Ep({title:`lzi-github-markdown 就地环境配置模板`})},{path:`README.md`,content:Wp()}],scan:async()=>Kp(e.dataRoot),renderCatalog:Up,reconcile:gp({countFields:[{headKey:`org_count`,actual:e=>e.length},{headKey:`repo_total`,actual:e=>e.reduce((e,t)=>e+t.repoCount,0)}],rowPattern:/\| \[([A-Za-z0-9-]+)\]\([^)]*\) \|/g,keyOfEntry:e=>e.org,keyNoun:`组织`}),commit:{defaultEnabled:!1,message:e=>{let t=e.reduce((e,t)=>e+t.repoCount,0);return`data(catalog): 更新索引目录册(${e.length} 组织 / ${t} 仓)`}}})}var Jp=class{dataRoot;service;constructor(e){this.dataRoot=e.dataRoot,this.service=qp(e)}async scan(){return this.service.scan()}async ensureRoot(e){return this.service.ensureRoot(e)}async refresh(e={}){return this.service.refresh(e)}async check(){return this.service.check()}};function Yp(e){let t=e.org!==void 0,n=e.repo!==void 0,r=[t,n,e.all===!0].filter(Boolean).length;if(r===0)return $({code:`SELECTION_INVALID`,message:`缺少选择器:--org <name> / --repo <owner>/<name> / --all 三选一`});if(r>1)return $({code:`SELECTION_INVALID`,message:`选择器互斥:--org / --repo / --all 只能选一种`});if(t){let t=e.org;return t===void 0||!Dp.test(t)?$({code:`ORG_INVALID`,message:`--org 格式非法:${t??`(空)`}(应为 GitHub 用户/组织名)`}):Zf({kind:`org`,org:t})}if(n){let t=e.repo;if(t===void 0)return $({code:`SELECTION_INVALID`,message:`--repo 缺值(应为 <owner>/<name>)`});let n=Pp(t);return n.ok?Zf({kind:`repo`,org:n.value.org,repo:n.value.repo}):$({code:`REPO_SELECTOR_INVALID`,message:n.error.message})}return Zf({kind:`all`})}function Xp(e,t){if(e.kind===`all`)return Zf(t);let n=t.find(t=>t.org===e.org);if(n===void 0){let t=e.kind===`org`?`克隆根不存在组织 ${e.org}(可用组织见 catalog list 或检查克隆目录)`:`克隆根不存在组织 ${e.org}(--repo ${e.org}/${e.repo})`;return $({code:e.kind===`org`?`ORG_NOT_FOUND`:`REPO_NOT_FOUND`,message:t})}return e.kind===`org`?Zf([n]):n.repos.find(t=>t.repo===e.repo)===void 0?$({code:`REPO_NOT_FOUND`,message:`组织 ${e.org} 下不存在仓库 ${e.repo}(--repo ${e.org}/${e.repo})`}):Zf([n])}async function Zp(e,t){let n=Yp(t);if(!n.ok)return $(n.error);let r=Np(e.clonesRoot);if(!r.ok)return $(r.error);let i=Xp(n.value,r.value.orgs);if(!i.ok)return $(i.error);let a=new Gp({dataRoot:e.dataRoot}),o={dryRun:t.dryRun===!0,commit:t.commit===!0},s=[],c=[];for(let e of i.value){let t=[];for(let n of e.repos){let e=await Ip(n);e.ok?t.push(e.value):c.push({org:e.error.org,repo:e.error.repo,message:e.error.message})}let n=await a.writeOrg(e.org,t,{dryRun:o.dryRun});s.push({org:n.org,repos:t.length,written:n.written,skipped:n.skipped})}let l=await new Jp({dataRoot:e.dataRoot,gitIdentity:t.gitIdentity}).refresh(o);return Zf({orgs:s,skippedRepos:c,scanSkipped:r.value.skipped,catalog:l})}async function Qp(e){if(!r(e.dataRoot))return $({code:`DATA_ROOT_MISSING`,message:`数据根不存在:${e.dataRoot}`});let t=new Jp({dataRoot:e.dataRoot}),n=await t.scan(),i=[],s=[];for(let t of n){let n=d(e.dataRoot,Hp,t.org),c=o(n,{withFileTypes:!0}).filter(e=>e.isDirectory()&&!e.name.startsWith(`.`)).sort((e,t)=>e.name<t.name?-1:1),l=0;for(let e of c){let t=d(n,e.name,`README.md`);if(!r(t))continue;l+=1;let{head:o,body:s}=rp(a(t,`utf8`)),c=ap(s);o.content_hash!==void 0&&o.content_hash!==c&&i.push({file:t,expected:o.content_hash,actual:c})}let u=d(n,`README.md`);if(r(u)){let{head:e}=rp(a(u,`utf8`));e.repo_count!==void 0&&e.repo_count!==String(l)&&s.push(`repositories/${t.org}/README.md 登记仓库数 ${e.repo_count} 与实测 ${l} 不符`)}else s.push(`${Hp}/${t.org} 缺 README.md`)}let c=await t.check();s.push(...c);let l=Np(e.clonesRoot);return Zf({hashDrifts:i,issues:s,cloneSkipped:l.ok?l.value.skipped:[],ok:i.length===0&&s.length===0})}async function $p(e){let t=await Yf(e);if(!t.ok)return{ok:!1,error:t.error};let n=wp();return n.ok?{ok:!0,value:{channel:`config-file`,env:{dataRoot:t.config.dataRoot,clonesRoot:n.value.clonesRoot},configPath:t.path}}:{ok:!1,error:n.error}}function em(e){return typeof e==`object`&&!!e&&`catalogWritten`in e&&`rootFilesCreated`in e&&`committed`in e}function tm(e){return Array.isArray(e)&&e.every(e=>typeof e==`object`&&!!e&&`org`in e&&`repoCount`in e)}function nm(e){return Array.isArray(e)&&e.every(e=>typeof e==`string`)}const rm=bp({resolveRuntimeEnv:$p,createService:e=>{let t=new Jp({dataRoot:e});return{dataRoot:t.dataRoot,scan:()=>t.scan(),ensureRoot:e=>t.ensureRoot(e),refresh:e=>t.refresh(e),check:()=>t.check()}}});async function im(e,t){let n=await rm(e,t);return n.ok?e===`build`&&em(n.data)||e===`list`&&tm(n.data)||e===`check`&&nm(n.data)?{ok:!0,operation:e,data:n.data}:{ok:!1,error:{code:`CONFIG_INVALID`,message:`catalog ${e} 返回了非预期的数据形态`}}:n}const am=xp({configFilename:qf,scaffold:e=>Xf({dataRoot:e.dataRoot}),resolveRuntimeEnv:$p});function om(e,t={}){return am.configInit(e,t)}async function sm(e){let t=await am.configShow(e);return t.ok?{ok:!0,resolution:t.value}:{ok:!1,error:t.error}}function cm(e){return am.configPath(e)}const lm=vp({resolveRuntimeEnv:$p,runConvert:Zp}),um=yp({resolveRuntimeEnv:$p,runVerify:Qp}),dm=td({name:`lzi-github-markdown`,description:`本地克隆仓索引工具(convert → repositories/ 归档索引 → catalog;数据面 = 本地克隆根只读消费,ADR 0003/0005)`,interaction:Tf,color:wf}),fm=Ks([`text`,`json`]).default(`text`);function pm(e){process.stdout.write(`${JSON.stringify(e,null,2)}\n`)}const mm={TOP_LEVEL_REPO:`顶层直接是仓库(缺 owner 层)`,TOP_LEVEL_FILE:`顶层散落文件`,OWNER_NAME_INVALID:`owner 名不符合 GitHub 用户名字符集`,NO_VALID_REPOS:`目录内无有效仓库`},hm={flags:`--format <kind>`,description:`输出格式 text|json`,defaultValue:`text`},gm={flags:`--config <path>`,description:`配置文件路径(缺省自 cwd 向上查找 lzi-github-markdown.config.ts;配置文件必备,缺失即报错)`};ad(dm,{name:`convert`,description:`扫描克隆根并落仓(--org / --repo <owner>/<name> / --all 三选一;幂等重跑零变更零提交)`,options:[{flags:`--org <name>`,description:`单组织(GitHub 用户/组织名)`},{flags:`--repo <selector>`,description:`单仓库(<owner>/<name>)`},{flags:`--all`,description:`克隆根内全部组织`},{flags:`--dry-run`,description:`干跑:零落盘零提交`},{flags:`--commit`,description:`执行 git 提交(缺省零 git;数据根整体单仓 init + 显式路径提交)`},gm,hm],schema:Fs({org:K().optional(),repo:K().optional(),all:Ds().default(!1),dryRun:Ds().default(!1),commit:Ds().default(!1),config:K().optional(),format:fm}),action:async e=>{let t=await lm({org:e.org,repo:e.repo,all:e.all,dryRun:e.dryRun,commit:e.commit},e.config);if(e.format===`json`){pm(t);return}if(!t.ok){process.stderr.write(`${t.error.message}\n`),process.exitCode=1;return}for(let n of t.report.orgs)process.stdout.write(`${n.org}:${n.repos} 仓(写入 ${n.written} / 跳过 ${n.skipped}${e.dryRun?`(dry-run)`:``})\n`);for(let e of t.report.skippedRepos)process.stdout.write(`[采集失败] ${e.org}/${e.repo}:${e.message}\n`);if(t.report.scanSkipped.length>0){process.stdout.write(`[克隆根非标准条目] ${t.report.scanSkipped.length} 个(已跳过):\n`);for(let e of t.report.scanSkipped)process.stdout.write(` - ${e.entry}:${mm[e.reason]??e.reason}\n`)}let n=t.report.catalog;process.stdout.write(`目录册:${n.entries.length} 组织${n.catalogWritten?`(已更新`:`(无变化`}${n.committed?`,已提交)`:`)`}\n`)}}),ad(dm,{name:`verify`,description:`一致性体检:content_hash 复算漂移 + 组织索引↔目录册↔磁盘三方对账 + 克隆根非标准条目报告`,options:[gm,hm],schema:Fs({config:K().optional(),format:fm}),action:async e=>{let t=await um(e.config);if(e.format===`json`){pm(t);return}if(!t.ok){process.stderr.write(`${t.error.message}\n`),process.exitCode=1;return}let{hashDrifts:n,issues:r,cloneSkipped:i}=t.report;for(let e of n)process.stdout.write(`[hash 漂移] ${e.file}:登记 ${e.expected} ≠ 复算 ${e.actual}\n`);for(let e of r)process.stdout.write(`[对账] ${e}\n`);if(i.length>0){process.stdout.write(`[克隆根非标准条目] ${i.length} 个(信息性,不阻断):\n`);for(let e of i)process.stdout.write(` - ${e.entry}:${mm[e.reason]??e.reason}\n`)}n.length===0&&r.length===0?process.stdout.write(`体检通过:无哈希漂移、无对账问题
228
- `):process.exitCode=1}}),ad(dm,{name:`catalog build`,description:`扫描磁盘重建/更新总目录册并提交`,options:[gm,hm],schema:Fs({config:K().optional(),format:fm}),action:async e=>{let t=await im(`build`,e.config);if(e.format===`json`){pm(t);return}if(!t.ok){process.stderr.write(`${t.error.message}\n`),process.exitCode=1;return}let n=t.data;process.stdout.write(`目录册:${n.entries.length} 组织${n.catalogWritten?`(已更新`:`(无变化`}${n.committed?`,已提交)`:`)`}\n`)}}),ad(dm,{name:`catalog list`,description:`列出全部组织条目`,options:[gm,hm],schema:Fs({config:K().optional(),format:fm}),action:async e=>{let t=await im(`list`,e.config);if(e.format===`json`)pm(t);else if(!t.ok)process.stderr.write(`${t.error.message}\n`),process.exitCode=1;else for(let e of t.data)process.stdout.write(`${e.org}:${e.repoCount} 仓\n`)}}),ad(dm,{name:`catalog check`,description:`目录册与磁盘对账(只报告不修复)`,options:[gm,hm],schema:Fs({config:K().optional(),format:fm}),action:async e=>{let t=await im(`check`,e.config);if(e.format===`json`)pm(t);else if(!t.ok)process.stderr.write(`${t.error.message}\n`),process.exitCode=1;else if(t.data.length===0)process.stdout.write(`对账通过
229
- `);else{for(let e of t.data)process.stdout.write(`${e}\n`);process.exitCode=1}}}),ad(dm,{name:`config init`,description:`生成 lzi-github-markdown.config.ts(数据根就地配置入口,仅 dataRoot 单字段;已存在则提示,不覆盖)`,options:[{flags:`--data-root <path>`,description:`数据根(缺省 '.',即就地配置:在数据根目录执行时自指)`}],schema:Fs({dataRoot:K().optional()}),action:async e=>{let t=om(process.cwd(),{dataRoot:e.dataRoot});t.ok?process.stdout.write(`已生成:${t.path}(本地克隆根请在 .env 配置 LZI_GITHUB_MARKDOWN_CLONES_ROOT 后执行 convert)\n`):(process.stderr.write(`${t.error.message}\n`),process.exitCode=1)}}),ad(dm,{name:`config show`,description:`展示生效配置(配置文件必备承载 dataRoot,克隆根自 .env 合并;zod 校验 + 绝对路径)`,options:[gm,hm],schema:Fs({config:K().optional(),format:fm}),action:async e=>{let t=await sm(e.config);e.format===`json`?pm(t):t.ok?process.stdout.write(`通道:${t.resolution.channel}${t.resolution.configPath===void 0?``:`(${t.resolution.configPath})`}\ndataRoot=${t.resolution.env.dataRoot}\nclonesRoot=${t.resolution.env.clonesRoot}\n`):(process.stderr.write(`${t.error.message}\n`),process.exitCode=1)}}),ad(dm,{name:`config path`,description:`打印配置文件与 .env 双通道位置`,schema:Fs({}),action:async()=>{let e=cm(process.cwd());process.stdout.write(`${e.configPath}${e.configExists?``:`(不存在,可先 config init)`}\n${e.envPath}${e.envExists?``:`(不存在)`}\n`)}}),await dm.parseAsync(process.argv);export{};
227
+ `},{path:`.env.example`,content:Ep({title:`lzi-github-markdown 就地环境配置模板`})},{path:`README.md`,content:Wp()}],scan:async()=>$p(e.dataRoot),renderCatalog:Up,reconcile:gp({countFields:[{headKey:`org_count`,actual:e=>e.length},{headKey:`repo_total`,actual:e=>e.reduce((e,t)=>e+t.repoCount,0)}],rowPattern:/\| \[([A-Za-z0-9-]+)\]\([^)]*\) \|/g,keyOfEntry:e=>e.org,keyNoun:`组织`}),commit:{defaultEnabled:!1,message:e=>{let t=e.reduce((e,t)=>e+t.repoCount,0);return`data(catalog): 更新索引目录册(${e.length} 组织 / ${t} 仓)`}}})}var tm=class{dataRoot;service;constructor(e){this.dataRoot=e.dataRoot,this.service=em(e)}async scan(){return this.service.scan()}async ensureRoot(e){return this.service.ensureRoot(e)}async refresh(e={}){return this.service.refresh(e)}async check(){return this.service.check()}};function nm(e){let t=e.org!==void 0,n=e.repo!==void 0,r=[t,n,e.all===!0].filter(Boolean).length;if(r===0)return $({code:`SELECTION_INVALID`,message:`缺少选择器:--org <name> / --repo <owner>/<name> / --all 三选一`});if(r>1)return $({code:`SELECTION_INVALID`,message:`选择器互斥:--org / --repo / --all 只能选一种`});if(t){let t=e.org;return t===void 0||!Dp.test(t)?$({code:`ORG_INVALID`,message:`--org 格式非法:${t??`(空)`}(应为 GitHub 用户/组织名)`}):Zf({kind:`org`,org:t})}if(n){let t=e.repo;if(t===void 0)return $({code:`SELECTION_INVALID`,message:`--repo 缺值(应为 <owner>/<name>)`});let n=Pp(t);return n.ok?Zf({kind:`repo`,org:n.value.org,repo:n.value.repo}):$({code:`REPO_SELECTOR_INVALID`,message:n.error.message})}return Zf({kind:`all`})}function rm(e,t){if(e.kind===`all`)return Zf(t);let n=t.find(t=>t.org===e.org);if(n===void 0){let t=e.kind===`org`?`克隆根不存在组织 ${e.org}(可用组织见 catalog list 或检查克隆目录)`:`克隆根不存在组织 ${e.org}(--repo ${e.org}/${e.repo})`;return $({code:e.kind===`org`?`ORG_NOT_FOUND`:`REPO_NOT_FOUND`,message:t})}return e.kind===`org`?Zf([n]):n.repos.find(t=>t.repo===e.repo)===void 0?$({code:`REPO_NOT_FOUND`,message:`组织 ${e.org} 下不存在仓库 ${e.repo}(--repo ${e.org}/${e.repo})`}):Zf([n])}async function im(e,t){let n=nm(t);if(!n.ok)return $(n.error);let r=Np(e.clonesRoot);if(!r.ok)return $(r.error);let i=rm(n.value,r.value.orgs);if(!i.ok)return $(i.error);let a=new Qp({dataRoot:e.dataRoot}),o={dryRun:t.dryRun===!0,commit:t.commit===!0},s=[],c=[];for(let e of i.value){let t=[];for(let n of e.repos){let e=await Ip(n);e.ok?t.push(e.value):c.push({org:e.error.org,repo:e.error.repo,message:e.error.message})}let n=await a.writeOrg(e.org,t,{dryRun:o.dryRun});s.push({org:n.org,repos:t.length,written:n.written,skipped:n.skipped})}let l=await new tm({dataRoot:e.dataRoot,gitIdentity:t.gitIdentity}).refresh(o);return Zf({orgs:s,skippedRepos:c,scanSkipped:r.value.skipped,catalog:l})}async function am(e){if(!r(e.dataRoot))return $({code:`DATA_ROOT_MISSING`,message:`数据根不存在:${e.dataRoot}`});let t=new tm({dataRoot:e.dataRoot}),n=await t.scan(),i=[],s=[];for(let t of n){let n=d(e.dataRoot,Hp,t.org),c=o(n,{withFileTypes:!0}).filter(e=>e.isDirectory()&&!e.name.startsWith(`.`)).sort((e,t)=>e.name<t.name?-1:1),l=0;for(let e of c){let t=d(n,e.name,`README.md`);if(!r(t))continue;l+=1;let{head:o,body:s}=rp(a(t,`utf8`)),c=ap(s);o.content_hash!==void 0&&o.content_hash!==c&&i.push({file:t,expected:o.content_hash,actual:c})}let u=d(n,`README.md`);if(r(u)){let{head:e}=rp(a(u,`utf8`));e.repo_count!==void 0&&e.repo_count!==String(l)&&s.push(`repositories/${t.org}/README.md 登记仓库数 ${e.repo_count} 与实测 ${l} 不符`)}else s.push(`${Hp}/${t.org} 缺 README.md`)}let c=await t.check();s.push(...c);let l=Zp(e.dataRoot),u=l.reduce((e,t)=>e+t.issues.length,0),f=Np(e.clonesRoot);return Zf({hashDrifts:i,issues:s,cloneSkipped:f.ok?f.value.skipped:[],manualAreas:l,ok:i.length===0&&s.length===0&&u===0})}async function om(e){let t=await Yf(e);if(!t.ok)return{ok:!1,error:t.error};let n=wp();return n.ok?{ok:!0,value:{channel:`config-file`,env:{dataRoot:t.config.dataRoot,clonesRoot:n.value.clonesRoot},configPath:t.path}}:{ok:!1,error:n.error}}function sm(e){return typeof e==`object`&&!!e&&`catalogWritten`in e&&`rootFilesCreated`in e&&`committed`in e}function cm(e){return Array.isArray(e)&&e.every(e=>typeof e==`object`&&!!e&&`org`in e&&`repoCount`in e)}function lm(e){return Array.isArray(e)&&e.every(e=>typeof e==`string`)}const um=bp({resolveRuntimeEnv:om,createService:e=>{let t=new tm({dataRoot:e});return{dataRoot:t.dataRoot,scan:()=>t.scan(),ensureRoot:e=>t.ensureRoot(e),refresh:e=>t.refresh(e),check:()=>t.check()}}});async function dm(e,t){let n=await um(e,t);return n.ok?e===`build`&&sm(n.data)||e===`list`&&cm(n.data)||e===`check`&&lm(n.data)?{ok:!0,operation:e,data:n.data}:{ok:!1,error:{code:`CONFIG_INVALID`,message:`catalog ${e} 返回了非预期的数据形态`}}:n}const fm=xp({configFilename:qf,scaffold:e=>Xf({dataRoot:e.dataRoot}),resolveRuntimeEnv:om});function pm(e,t={}){return fm.configInit(e,t)}async function mm(e){let t=await fm.configShow(e);return t.ok?{ok:!0,resolution:t.value}:{ok:!1,error:t.error}}function hm(e){return fm.configPath(e)}const gm=vp({resolveRuntimeEnv:om,runConvert:im});async function _m(e,t){let n=await om(e);if(!n.ok)return{ok:!1,error:{message:n.error.message}};let r=Zp(n.value.env.dataRoot).filter(e=>t===void 0||e.def.kind===t);return{ok:!0,data:{dataRoot:n.value.env.dataRoot,areas:r}}}const vm=yp({resolveRuntimeEnv:om,runVerify:am}),ym=td({name:`lzi-github-markdown`,description:`本地克隆仓索引工具(convert → repositories/ 归档索引 → catalog;数据面 = 本地克隆根只读消费,ADR 0003/0005)`,interaction:Tf,color:wf}),bm=Ks([`text`,`json`]).default(`text`);function xm(e){process.stdout.write(`${JSON.stringify(e,null,2)}\n`)}const Sm={TOP_LEVEL_REPO:`顶层直接是仓库(缺 owner 层)`,TOP_LEVEL_FILE:`顶层散落文件`,OWNER_NAME_INVALID:`owner 名不符合 GitHub 用户名字符集`,NO_VALID_REPOS:`目录内无有效仓库`},Cm={flags:`--format <kind>`,description:`输出格式 text|json`,defaultValue:`text`},wm={flags:`--config <path>`,description:`配置文件路径(缺省自 cwd 向上查找 lzi-github-markdown.config.ts;配置文件必备,缺失即报错)`};ad(ym,{name:`convert`,description:`扫描克隆根并落仓(--org / --repo <owner>/<name> / --all 三选一;幂等重跑零变更零提交)`,options:[{flags:`--org <name>`,description:`单组织(GitHub 用户/组织名)`},{flags:`--repo <selector>`,description:`单仓库(<owner>/<name>)`},{flags:`--all`,description:`克隆根内全部组织`},{flags:`--dry-run`,description:`干跑:零落盘零提交`},{flags:`--commit`,description:`执行 git 提交(缺省零 git;数据根整体单仓 init + 显式路径提交)`},wm,Cm],schema:Fs({org:K().optional(),repo:K().optional(),all:Ds().default(!1),dryRun:Ds().default(!1),commit:Ds().default(!1),config:K().optional(),format:bm}),action:async e=>{let t=await gm({org:e.org,repo:e.repo,all:e.all,dryRun:e.dryRun,commit:e.commit},e.config);if(e.format===`json`){xm(t);return}if(!t.ok){process.stderr.write(`${t.error.message}\n`),process.exitCode=1;return}for(let n of t.report.orgs)process.stdout.write(`${n.org}:${n.repos} 仓(写入 ${n.written} / 跳过 ${n.skipped}${e.dryRun?`(dry-run)`:``})\n`);for(let e of t.report.skippedRepos)process.stdout.write(`[采集失败] ${e.org}/${e.repo}:${e.message}\n`);if(t.report.scanSkipped.length>0){process.stdout.write(`[克隆根非标准条目] ${t.report.scanSkipped.length} 个(已跳过):\n`);for(let e of t.report.scanSkipped)process.stdout.write(` - ${e.entry}:${Sm[e.reason]??e.reason}\n`)}let n=t.report.catalog;process.stdout.write(`目录册:${n.entries.length} 组织${n.catalogWritten?`(已更新`:`(无变化`}${n.committed?`,已提交)`:`)`}\n`)}}),ad(ym,{name:`verify`,description:`一致性体检:content_hash 复算漂移 + 组织索引↔目录册↔磁盘三方对账 + 手工区(tracking/pending)结构体检 + 克隆根非标准条目报告`,options:[wm,Cm],schema:Fs({config:K().optional(),format:bm}),action:async e=>{let t=await vm(e.config);if(e.format===`json`){xm(t);return}if(!t.ok){process.stderr.write(`${t.error.message}\n`),process.exitCode=1;return}let{hashDrifts:n,issues:r,cloneSkipped:i,manualAreas:a}=t.report;for(let e of n)process.stdout.write(`[hash 漂移] ${e.file}:登记 ${e.expected} ≠ 复算 ${e.actual}\n`);for(let e of r)process.stdout.write(`[对账] ${e}\n`);let o=0;for(let e of a){for(let t of e.issues)process.stdout.write(`[手工区] ${t}\n`);o+=e.issues.length,process.stdout.write(`[手工区] ${e.def.kind}:${e.entries.length} 条记录(${e.def.title})\n`)}if(i.length>0){process.stdout.write(`[克隆根非标准条目] ${i.length} 个(信息性,不阻断):\n`);for(let e of i)process.stdout.write(` - ${e.entry}:${Sm[e.reason]??e.reason}\n`)}n.length===0&&r.length===0&&o===0?process.stdout.write(`体检通过:无哈希漂移、无对账问题、手工区结构合规
228
+ `):process.exitCode=1}}),ad(ym,{name:`catalog build`,description:`扫描磁盘重建/更新总目录册并提交`,options:[wm,Cm],schema:Fs({config:K().optional(),format:bm}),action:async e=>{let t=await dm(`build`,e.config);if(e.format===`json`){xm(t);return}if(!t.ok){process.stderr.write(`${t.error.message}\n`),process.exitCode=1;return}let n=t.data;process.stdout.write(`目录册:${n.entries.length} 组织${n.catalogWritten?`(已更新`:`(无变化`}${n.committed?`,已提交)`:`)`}\n`)}}),ad(ym,{name:`catalog list`,description:`列出全部组织条目`,options:[wm,Cm],schema:Fs({config:K().optional(),format:bm}),action:async e=>{let t=await dm(`list`,e.config);if(e.format===`json`)xm(t);else if(!t.ok)process.stderr.write(`${t.error.message}\n`),process.exitCode=1;else for(let e of t.data)process.stdout.write(`${e.org}:${e.repoCount} 仓\n`)}}),ad(ym,{name:`catalog check`,description:`目录册与磁盘对账(只报告不修复)`,options:[wm,Cm],schema:Fs({config:K().optional(),format:bm}),action:async e=>{let t=await dm(`check`,e.config);if(e.format===`json`)xm(t);else if(!t.ok)process.stderr.write(`${t.error.message}\n`),process.exitCode=1;else if(t.data.length===0)process.stdout.write(`对账通过
229
+ `);else{for(let e of t.data)process.stdout.write(`${e}\n`);process.exitCode=1}}}),ad(ym,{name:`manual list`,description:`清点手工维护区条目(tracking 跟踪登记与 pending 待入库暂存;--area 可过滤单区)`,options:[wm,Cm,{flags:`--area <kind>`,description:`区过滤 tracking|pending(缺省两区全列)`}],schema:Fs({config:K().optional(),format:bm,area:Ks([`tracking`,`pending`]).optional()}),action:async e=>{let t=await _m(e.config,e.area);if(e.format===`json`)xm(t);else if(!t.ok)process.stderr.write(`${t.error.message}\n`),process.exitCode=1;else for(let e of t.data.areas){process.stdout.write(`${e.def.kind}(${e.def.title}):${e.entries.length} 条\n`);for(let t of e.entries)process.stdout.write(` - ${t.org}/${t.repo}:${t.date}(${t.file})\n`);for(let t of e.issues)process.stdout.write(` [问题] ${t}\n`)}}}),ad(ym,{name:`config init`,description:`生成 lzi-github-markdown.config.ts(数据根就地配置入口,仅 dataRoot 单字段;已存在则提示,不覆盖)`,options:[{flags:`--data-root <path>`,description:`数据根(缺省 '.',即就地配置:在数据根目录执行时自指)`}],schema:Fs({dataRoot:K().optional()}),action:async e=>{let t=pm(process.cwd(),{dataRoot:e.dataRoot});t.ok?process.stdout.write(`已生成:${t.path}(本地克隆根请在 .env 配置 LZI_GITHUB_MARKDOWN_CLONES_ROOT 后执行 convert)\n`):(process.stderr.write(`${t.error.message}\n`),process.exitCode=1)}}),ad(ym,{name:`config show`,description:`展示生效配置(配置文件必备承载 dataRoot,克隆根自 .env 合并;zod 校验 + 绝对路径)`,options:[wm,Cm],schema:Fs({config:K().optional(),format:bm}),action:async e=>{let t=await mm(e.config);e.format===`json`?xm(t):t.ok?process.stdout.write(`通道:${t.resolution.channel}${t.resolution.configPath===void 0?``:`(${t.resolution.configPath})`}\ndataRoot=${t.resolution.env.dataRoot}\nclonesRoot=${t.resolution.env.clonesRoot}\n`):(process.stderr.write(`${t.error.message}\n`),process.exitCode=1)}}),ad(ym,{name:`config path`,description:`打印配置文件与 .env 双通道位置`,schema:Fs({}),action:async()=>{let e=hm(process.cwd());process.stdout.write(`${e.configPath}${e.configExists?``:`(不存在,可先 config init)`}\n${e.envPath}${e.envExists?``:`(不存在)`}\n`)}}),await ym.parseAsync(process.argv);export{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longzai-intelligence-github-markdown/cli",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Longzai Intelligence GitHub Markdown 工具 CLI",
5
5
  "keywords": [
6
6
  "catalog",
@@ -33,7 +33,7 @@
33
33
  "dependencies": {
34
34
  "@longzai-intelligence-cli/core": "^0.0.10",
35
35
  "@longzai-intelligence-cli/standard": "^0.0.10",
36
- "@longzai-intelligence-github-markdown/core": "0.0.5",
36
+ "@longzai-intelligence-github-markdown/core": "0.0.6",
37
37
  "@longzai-intelligence-markdown/data-engine": "^0.0.1",
38
38
  "zod": "^4.4.3"
39
39
  }