@andyqiu/codeforge 0.6.5 → 0.6.7
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/agents/codeforge.md +3 -5
- package/agents/coder-deep.md +1 -1
- package/agents/coder-quick.md +1 -1
- package/agents/coder.md +1 -1
- package/agents/reviewer-lite.md +30 -1
- package/agents/reviewer.md +30 -1
- package/bin/codeforge-doctor.mjs +187 -0
- package/bin/codeforge.mjs +21 -0
- package/dist/index.js +511 -260
- package/install.mjs +97 -6
- package/package.json +1 -1
package/install.mjs
CHANGED
|
@@ -492,15 +492,33 @@ function installMdDirs({ targetRoot }) {
|
|
|
492
492
|
const pruned = pruneStale({ dstPath, prevList, sourceSet })
|
|
493
493
|
|
|
494
494
|
// 步骤 4:upsert —— 把 sourceSet 文件复制到目标(覆盖同名,不动其他文件)
|
|
495
|
+
// ADR:install-manifest-actual-copied-consistency —— manifest 记录实际落盘(事实),非源扫描意图
|
|
495
496
|
let count = 0
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
497
|
+
const copiedOk = [] // 仅含 post-copy existsSync 复核通过的文件名
|
|
498
|
+
if (!DRY_RUN) {
|
|
499
|
+
// 真实 copy:收集 copiedOk,逐文件复核,复核失败 fail-loud
|
|
500
|
+
for (const base of sourceFiles) {
|
|
501
|
+
const src = path.join(srcPath, base)
|
|
502
|
+
const dst = path.join(dstPath, base)
|
|
503
|
+
fs.copyFileSync(src, dst)
|
|
504
|
+
if (!fs.existsSync(dst)) {
|
|
505
|
+
// copy 后文件仍不存在:可能是 FS 异常(NFS/虚拟 FS 等),fail-loud
|
|
506
|
+
throw new Error(`copy 后文件不存在(existsSync 复核失败):${dst}`)
|
|
507
|
+
}
|
|
508
|
+
copiedOk.push(base)
|
|
509
|
+
count++
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
// DRY_RUN:run() 已 vlog,不执行 copy,copiedOk 保持空(dry-run 不落盘)
|
|
513
|
+
for (const base of sourceFiles) {
|
|
514
|
+
vlog(`[dry-run] cp ${path.join(srcPath, base)} ${dstPath}/`)
|
|
515
|
+
count++
|
|
516
|
+
}
|
|
500
517
|
}
|
|
501
518
|
|
|
502
|
-
// 步骤 5:新 manifest =
|
|
503
|
-
|
|
519
|
+
// 步骤 5:新 manifest = copiedOk(实际落盘文件,非 sourceFiles 意图)
|
|
520
|
+
// DRY_RUN 下 copiedOk 为空,由 run() 包裹的 writeMdManifest 负责 vlog 不落盘
|
|
521
|
+
nextManifest[dstName] = DRY_RUN ? [...sourceFiles] : copiedOk
|
|
504
522
|
|
|
505
523
|
vok(`${srcName}/ → ${dstPath} (${count} 个 .md upsert, ${pruned} 个过期清理, 第三方文件保留)`)
|
|
506
524
|
}
|
|
@@ -551,6 +569,77 @@ function writeMdManifest(targetRoot, data) {
|
|
|
551
569
|
atomicWriteJson(p, data)
|
|
552
570
|
}
|
|
553
571
|
|
|
572
|
+
// compareManifestVsDisk:三方比对(manifest / disk / source)
|
|
573
|
+
// ADR:codeforge-doctor-command — doctor 与 install 共享的纯函数
|
|
574
|
+
// 返回:
|
|
575
|
+
// missing[] = manifest 登记但 disk 无(来源2:外部腐蚀)
|
|
576
|
+
// unmanaged[] = source 有(经 shouldCopyMd 过滤)但 disk 无(来源1/2:未安装 / 被删)
|
|
577
|
+
// stale[] = disk 有 & source 有 但 manifest 无(manifest 失配,可能未更新)
|
|
578
|
+
// 第三方文件(disk 有但不在 manifest 也不在 source)→ 不纳入任何桶(静默跳过)
|
|
579
|
+
function compareManifestVsDisk({ targetRoot, sourceRoot }) {
|
|
580
|
+
const manifest = readMdManifest(targetRoot)
|
|
581
|
+
const missing = []
|
|
582
|
+
const unmanaged = []
|
|
583
|
+
const stale = []
|
|
584
|
+
|
|
585
|
+
for (const [srcName, dstName] of MD_COPY_DIRS) {
|
|
586
|
+
const srcPath = path.join(sourceRoot, srcName)
|
|
587
|
+
const dstPath = path.join(targetRoot, dstName)
|
|
588
|
+
|
|
589
|
+
// source 扫描(经 shouldCopyMd 过滤)
|
|
590
|
+
const sourceFiles = new Set()
|
|
591
|
+
try {
|
|
592
|
+
const entries = fs.readdirSync(srcPath)
|
|
593
|
+
for (const base of entries) {
|
|
594
|
+
if (!shouldCopyMd(base)) continue
|
|
595
|
+
try { if (!fs.statSync(path.join(srcPath, base)).isFile()) continue } catch { continue }
|
|
596
|
+
sourceFiles.add(base)
|
|
597
|
+
}
|
|
598
|
+
} catch {
|
|
599
|
+
// 源目录不存在(发布包未含该目录)→ sourceFiles 为空,正常走后续逻辑
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// manifest 登记列表
|
|
603
|
+
const manifestList = manifest[dstName] ?? []
|
|
604
|
+
const manifestSet = new Set(manifestList)
|
|
605
|
+
|
|
606
|
+
// disk 实际文件(目录不存在时视为空)
|
|
607
|
+
const diskSet = new Set()
|
|
608
|
+
try {
|
|
609
|
+
const diskEntries = fs.readdirSync(dstPath)
|
|
610
|
+
for (const base of diskEntries) {
|
|
611
|
+
try { if (!fs.statSync(path.join(dstPath, base)).isFile()) continue } catch { continue }
|
|
612
|
+
diskSet.add(base)
|
|
613
|
+
}
|
|
614
|
+
} catch {
|
|
615
|
+
// 目录不存在 → diskSet 为空
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// D1:manifest 登记但 disk 无
|
|
619
|
+
for (const base of manifestList) {
|
|
620
|
+
if (!diskSet.has(base)) {
|
|
621
|
+
missing.push({ dir: dstName, file: base })
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// D2:source 有但 disk 无(未安装或被删)
|
|
626
|
+
for (const base of sourceFiles) {
|
|
627
|
+
if (!diskSet.has(base)) {
|
|
628
|
+
unmanaged.push({ dir: dstName, file: base })
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// D3:disk 有 & source 有 但 manifest 无(manifest 失配)
|
|
633
|
+
for (const base of diskSet) {
|
|
634
|
+
if (sourceFiles.has(base) && !manifestSet.has(base)) {
|
|
635
|
+
stale.push({ dir: dstName, file: base })
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return { missing, unmanaged, stale }
|
|
641
|
+
}
|
|
642
|
+
|
|
554
643
|
// 差集清理:prevList − sourceSet,仅删目标目录里确实存在的文件。返回清理计数。
|
|
555
644
|
// 第三方文件(如 teamkit-git.md)从不写入 manifest → 不在 prevList → 永不被删。
|
|
556
645
|
function pruneStale({ dstPath, prevList, sourceSet }) {
|
|
@@ -826,6 +915,8 @@ export {
|
|
|
826
915
|
readMdManifest,
|
|
827
916
|
writeMdManifest,
|
|
828
917
|
MD_MANIFEST_REL,
|
|
918
|
+
compareManifestVsDisk,
|
|
919
|
+
MD_COPY_DIRS,
|
|
829
920
|
}
|
|
830
921
|
|
|
831
922
|
// main 守卫:仅作为脚本直接运行时执行
|