sillyspec 3.29.3 → 3.29.4
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/asset-audit-2026-09-20/README.md +42 -0
- package/asset-audit-2026-09-20/asset-audit.cjs +253 -0
- package/asset-audit-2026-09-20/extract-candidates.cjs +37 -0
- package/asset-audit-2026-09-20/platform-archive.csv +294 -0
- package/asset-audit-2026-09-20/platform-fr.csv +12 -0
- package/asset-audit-2026-09-20/platform-gotchas.csv +6 -0
- package/asset-audit-2026-09-20/platform-knownissues.csv +23 -0
- package/asset-audit-2026-09-20/platform-modules.csv +222 -0
- package/asset-audit-2026-09-20/platform-quicklog.csv +1076 -0
- package/asset-audit-2026-09-20/platform-troubleshooting-candidates.csv +88 -0
- package/asset-audit-2026-09-20/platform-troubleshooting.csv +1 -0
- package/asset-audit-2026-09-20/sillyspec-archive.csv +108 -0
- package/asset-audit-2026-09-20/sillyspec-fr.csv +51 -0
- package/asset-audit-2026-09-20/sillyspec-gotchas.csv +4 -0
- package/asset-audit-2026-09-20/sillyspec-knownissues.csv +34 -0
- package/asset-audit-2026-09-20/sillyspec-modules.csv +30 -0
- package/asset-audit-2026-09-20/sillyspec-quicklog.csv +460 -0
- package/asset-audit-2026-09-20/sillyspec-troubleshooting.csv +69 -0
- package/asset-audit-2026-09-20/summary.json +241 -0
- package/package.json +1 -1
- package/src/cross-repo-reconcile.js +232 -46
- package/src/fr-index.js +58 -2
- package/src/index.js +2 -2
- package/src/run/bg-sync.js +231 -0
- package/src/run/complete-handlers.js +208 -5
- package/src/run/gates.js +10 -5
- package/src/run/prompt.js +1 -1
- package/src/run/shared.js +32 -0
- package/src/scope-audit.js +303 -20
- package/src/stages/quick.js +2 -0
- package/src/sync.js +12 -0
- package/src/verify-postcheck.js +9 -5
package/src/scope-audit.js
CHANGED
|
@@ -23,6 +23,13 @@
|
|
|
23
23
|
*
|
|
24
24
|
* 全 advisory(D-006):不抛错阻断、不写门禁状态;任何异常 catch 后并入 degradedReason。
|
|
25
25
|
* 纯读:不落盘、不改 guard/进度库。Windows 路径 \\→/ 归一,ESM-only,零新依赖。
|
|
26
|
+
*
|
|
27
|
+
* 跨仓分仓真实对账(2026-09-20 scope-audit-cross-repo task-02,design Wave 2):full-flow
|
|
28
|
+
* 非预执行形态下,计划侧跨仓条目(parseFileChangeListDetailed 的 .repo 标注)按 repoKey 分
|
|
29
|
+
* 组调 collectRepoActual 共享内核(cross-repo-reconcile.js,task-01 单一真相源)在该仓取
|
|
30
|
+
* actual——跨仓行从 v1 恒 untouched(⊘ 本表不含)升级为真实三态 + 行数 + crossRepo 归属;
|
|
31
|
+
* 信封增量 repos[](main 首位 + 各仓锚点/三态计数/degraded 态,--json 契约 v2 平台消费面)。
|
|
32
|
+
* degraded 仓退 v1 ⊘ 形态不炸主仓表;预执行形态不调内核(跨仓行保持清单形态)。
|
|
26
33
|
*/
|
|
27
34
|
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
28
35
|
import { createHash } from 'node:crypto'
|
|
@@ -32,8 +39,12 @@ import { computeGateProfile, resolveGateThresholds } from './quick-gate-profile.
|
|
|
32
39
|
import { loadSpanRiskPatternsAllProjects } from './span-risk-surface.js'
|
|
33
40
|
import { safeGit } from './git-helper.js'
|
|
34
41
|
import { parseFileChangeListDetailed, pathMatches } from './change-list.js'
|
|
35
|
-
import { classifyToolScaffold } from './worktree-apply.js'
|
|
42
|
+
import { classifyToolScaffold, filterDeliverableFiles } from './worktree-apply.js'
|
|
36
43
|
import { parseRepoRegistry } from './stages/plan-postcheck.js'
|
|
44
|
+
// 跨仓 per-repo 采集共享内核(2026-09-20 scope-audit-cross-repo task-02 消费 task-01 产物)。
|
|
45
|
+
// 无环确认:cross-repo-reconcile 的静态依赖链(git-helper/plan-postcheck/worktree-apply/
|
|
46
|
+
// task-review 及其传递闭包)不 import 本模块——内核绝不 import scope-audit(评审 G3 定稿)。
|
|
47
|
+
import { collectRepoActual } from './cross-repo-reconcile.js'
|
|
37
48
|
import { _readWorktreeMeta } from './contract-matrix.js'
|
|
38
49
|
|
|
39
50
|
/** quick 会话 id 形态(与 run/command.js :652 QUICK_SID_RE 同款——crypto.randomUUID 前 8 hex) */
|
|
@@ -572,6 +583,25 @@ function sumTotals(rows) {
|
|
|
572
583
|
return { additions, deletions }
|
|
573
584
|
}
|
|
574
585
|
|
|
586
|
+
/**
|
|
587
|
+
* per-repo 汇总(信封 repos[].totals,2026-09-20 scope-audit-cross-repo task-02 --json 契约 v2):
|
|
588
|
+
* files/additions/deletions 沿 sumTotals 口径(binary 与 null 降级不计行数)+ 三态计数
|
|
589
|
+
* (verdict === 'planned'/'unplanned'/'untouched' 各自计数;行无 verdict 时不计三态——计划侧
|
|
590
|
+
* 降级形态的主仓行只有 files/additions/deletions,不出伪三态)。main 条目只喂主仓行、跨仓
|
|
591
|
+
* 条目只喂该仓行(调用方保证切片,函数本身不区分)。
|
|
592
|
+
*/
|
|
593
|
+
function repoTotals(rows) {
|
|
594
|
+
const list = Array.isArray(rows) ? rows : []
|
|
595
|
+
const t = { files: list.length, ...sumTotals(list.filter(r => r && typeof r === 'object')), planned: 0, unplanned: 0, untouched: 0 }
|
|
596
|
+
for (const r of list) {
|
|
597
|
+
if (!r || typeof r !== 'object') continue
|
|
598
|
+
if (r.verdict === 'planned') t.planned++
|
|
599
|
+
else if (r.verdict === 'unplanned') t.unplanned++
|
|
600
|
+
else if (r.verdict === 'untouched') t.untouched++
|
|
601
|
+
}
|
|
602
|
+
return t
|
|
603
|
+
}
|
|
604
|
+
|
|
575
605
|
/**
|
|
576
606
|
* quick 模式对账:重跑 auditQuickCompletion 同款窗口归属(不改其判定语义,只消费结果)。
|
|
577
607
|
* 他者会话声明索引与 --done 收尾同源实时采集(complete-handlers :1050 同款 merge),保证
|
|
@@ -926,20 +956,34 @@ async function computeFullFlowAudit({ cwd, specBase, changeName, platformOpts, c
|
|
|
926
956
|
const snap = readScopeSnapshot(changeDirInfo.dir, runtimeRoot, changeName)
|
|
927
957
|
if (snap && snap.rows.length > 0) {
|
|
928
958
|
const settleLabel = changeDirInfo.archived ? '已归档' : 'execute 已收尾(分支已清理,待 verify/archive)'
|
|
959
|
+
// repos[] 回放透传(2026-09-20 scope-audit-cross-repo,D-002@v1):新快照(跨仓对账上线后
|
|
960
|
+
// execute --done 落盘,complete.js `...snap` 展开零改动自动携带)原样回放;旧快照无该键
|
|
961
|
+
// → undefined(JSON.stringify 自动省略——单仓/旧快照零新增字段,additive 契约)。
|
|
962
|
+
const snapRepos = Array.isArray(snap.repos) ? snap.repos : undefined
|
|
963
|
+
// 旧快照跨仓注记(R-07):快照冻结于跨仓对账上线前(跨仓行恒 untouched 的 v1 ⊘ 形态、
|
|
964
|
+
// 无 repos 键)——照旧回放不重算(「快照说什么是什么」契约不破),注记指明人工渠道
|
|
965
|
+
// (该仓锚点锡点冻结在 execute-runs reviews,可人工 git diff 核实)。
|
|
966
|
+
const legacyCrossNote = !snapRepos && snap.rows.some(r => r && r.crossRepo)
|
|
967
|
+
? ';快照冻结于跨仓对账上线前——跨仓段未按仓对账(⊘ 行为历史冻结值;如需跨仓真实态,该仓锚点锡点在 execute-runs reviews,可人工 git diff)'
|
|
968
|
+
: ''
|
|
929
969
|
// 行数补采:快照可能落盘于 apply 后、tag 锚落地前的窗口(行数列全 —);tag 锚
|
|
930
970
|
// (quick-df1fed77)恢复 merge-base 后,对冻结文件集按锚补采行数——文件集不重算
|
|
931
971
|
// (冻结语义不变),仅行数从 — 复活;含主仓后续演进(同文件被再改会计入),口径标注。
|
|
932
972
|
let rows = snap.rows
|
|
933
973
|
let snapDegraded = snap.degradedReason || null
|
|
934
|
-
|
|
974
|
+
// 补采只针对主仓行(评审 G1):needsStats 排除 crossRepo 行——补采对主仓根跑,跨仓行
|
|
975
|
+
// null 档会被主仓盘面误判成 {0,0,'deleted'} 伪数据(违反「不出伪数据」);跨仓行数
|
|
976
|
+
// 维持快照冻结值。
|
|
977
|
+
const needsStats = rows.some(r => r && !r.crossRepo && !Number.isFinite(r.additions) && r.kind !== 'binary')
|
|
935
978
|
if (needsStats && actual && actual.ok && actual.baseAnchor) {
|
|
936
|
-
const paths = rows.map(r => (r && r.path ? toPosix(r.path) : '')).filter(Boolean)
|
|
979
|
+
const paths = rows.filter(r => r && !r.crossRepo).map(r => (r && r.path ? toPosix(r.path) : '')).filter(Boolean)
|
|
937
980
|
// 审查 C-F04:补采执行根走共享 resolveDiffRoot(form=worktree 时必须对 worktree 跑,
|
|
938
981
|
// 与主链路 :629/:636 同源配对——直用 cwd 会基点×根错位)
|
|
939
982
|
const stats = collectNumstatByPath(resolveDiffRoot(sb, changeName, actual.form, cwd), paths, { baseRef: actual.baseAnchor })
|
|
940
983
|
let recovered = 0
|
|
941
984
|
rows = rows.map(r => {
|
|
942
985
|
if (!r || !r.path || Number.isFinite(r.additions) || r.kind === 'binary') return r
|
|
986
|
+
if (r.crossRepo) return r // 跨仓行维持快照冻结值(G1:主仓根补采会产伪数据)
|
|
943
987
|
const st = stats.get(toPosix(r.path))
|
|
944
988
|
if (!st) return r
|
|
945
989
|
recovered++
|
|
@@ -953,12 +997,13 @@ async function computeFullFlowAudit({ cwd, specBase, changeName, platformOpts, c
|
|
|
953
997
|
baseAnchor: actual.baseAnchor,
|
|
954
998
|
totals: { files: rows.length, ...sumTotals(rows) },
|
|
955
999
|
rows,
|
|
1000
|
+
repos: snapRepos,
|
|
956
1001
|
excluded: snap.excluded && Array.isArray(snap.excluded.foreignDeclared)
|
|
957
1002
|
? snap.excluded
|
|
958
1003
|
: { foreignDeclared: [] },
|
|
959
1004
|
patchSha256: snap.patchSha256 || null,
|
|
960
1005
|
patchStatus: snap.patchStatus || null,
|
|
961
|
-
note: `${settleLabel}——execute --done 时点冻结快照${snap.savedAt ? '(' + String(snap.savedAt).replace('T', ' ').slice(0, 19) + ' 落盘)' : ''}:文件集封闭在 apply 时点,主仓后续新文件不进表;行数按锚 ${actual.baseAnchor.slice(0, 7)}→当前工作树
|
|
1006
|
+
note: `${settleLabel}——execute --done 时点冻结快照${snap.savedAt ? '(' + String(snap.savedAt).replace('T', ' ').slice(0, 19) + ' 落盘)' : ''}:文件集封闭在 apply 时点,主仓后续新文件不进表;行数按锚 ${actual.baseAnchor.slice(0, 7)}→当前工作树 补采(同文件后续演进会计入)${legacyCrossNote}`,
|
|
962
1007
|
}
|
|
963
1008
|
}
|
|
964
1009
|
return {
|
|
@@ -970,12 +1015,13 @@ async function computeFullFlowAudit({ cwd, specBase, changeName, platformOpts, c
|
|
|
970
1015
|
? snap.totals
|
|
971
1016
|
: { files: snap.rows.length, ...sumTotals(snap.rows) },
|
|
972
1017
|
rows: snap.rows,
|
|
1018
|
+
repos: snapRepos,
|
|
973
1019
|
excluded: snap.excluded && Array.isArray(snap.excluded.foreignDeclared)
|
|
974
1020
|
? snap.excluded
|
|
975
1021
|
: { foreignDeclared: [] },
|
|
976
1022
|
patchSha256: snap.patchSha256 || null,
|
|
977
1023
|
patchStatus: snap.patchStatus || null,
|
|
978
|
-
note: `${settleLabel}——execute --done 时点冻结快照${snap.savedAt ? '(' + String(snap.savedAt).replace('T', ' ').slice(0, 19) + ' 落盘)' : ''}:范围封闭在 apply 时点,主仓后续改动不反映到本表;需要看当前工作区实时状态请跑 git status`,
|
|
1024
|
+
note: `${settleLabel}——execute --done 时点冻结快照${snap.savedAt ? '(' + String(snap.savedAt).replace('T', ' ').slice(0, 19) + ' 落盘)' : ''}:范围封闭在 apply 时点,主仓后续改动不反映到本表;需要看当前工作区实时状态请跑 git status${legacyCrossNote}`,
|
|
979
1025
|
}
|
|
980
1026
|
}
|
|
981
1027
|
// 快照缺失 → 实时开放区间兜底(下方主链路),note 追加漂移警告
|
|
@@ -1018,12 +1064,90 @@ async function computeFullFlowAudit({ cwd, specBase, changeName, platformOpts, c
|
|
|
1018
1064
|
notes.push(`实际侧另有 ${actual.foreignExcluded} 个文件按他者会话声明退栈未进本表——「计划未动」行标注「疑似他者已实现·已退栈」者即此类(声明即归属,git diff 核实)`)
|
|
1019
1065
|
}
|
|
1020
1066
|
|
|
1021
|
-
//
|
|
1022
|
-
//
|
|
1067
|
+
// —— 计划侧跨仓段分仓真实对账(2026-09-20 scope-audit-cross-repo task-02,design Wave 2)——
|
|
1068
|
+
// 旧形态「⊘ 行不在本仓对账面(实际侧只采主仓 git)」升级:plannedEntries 按 e.repo 分组,
|
|
1069
|
+
// 每组调 collectRepoActual 共享内核(task-01)在**该仓**取 actual(锚点四级:A reviews-range
|
|
1070
|
+
// > B head~1-window > C head-uncommitted-window > degraded),主仓同款三类差集出真实三态行
|
|
1071
|
+
// + 行数;degraded 仓(未注册/路径不可达/git 双源失败)退 v1 ⊘ 形态(untouched + crossRepo)
|
|
1072
|
+
// + 降级注记进 note,不炸主仓表(D-006 fail-soft)。
|
|
1073
|
+
// 行数由本集成层对内核产物跑 collectNumstatByPath(内核不 import scope-audit 防环,评审
|
|
1074
|
+
// G3——调用方职责);anchor.base 为 null(B/C 档降级锚)→ numstat 空 Map → degradedStat
|
|
1075
|
+
// 该仓根兜底(行数 null 降级档,不出伪数据)。跨仓行过滤口径与主仓 actual 侧一致
|
|
1076
|
+
// (filterDeliverableFiles:排 .sillyspec 运行时面/平台自装脚手架),unplanned 行的
|
|
1077
|
+
// classifyToolScaffold 软桶与主仓 unplanned 行同款。
|
|
1023
1078
|
const crossRepoKeys = [...new Set(plannedEntries.map(e => e.repo).filter(Boolean))]
|
|
1079
|
+
const crossRepoRows = []
|
|
1080
|
+
const crossRepoEntries = []
|
|
1024
1081
|
if (crossRepoKeys.length > 0) {
|
|
1025
1082
|
const crossCount = plannedEntries.filter(e => e.repo).length
|
|
1026
|
-
notes.push(`计划侧含 ${crossCount} 个跨仓文件(repo:${crossRepoKeys.join('、')}
|
|
1083
|
+
notes.push(`计划侧含 ${crossCount} 个跨仓文件(repo:${crossRepoKeys.join('、')})——已按 local.yaml repos 注册表分仓对账(各仓锚点档见 repos[].anchor)`)
|
|
1084
|
+
for (const repoKey of crossRepoKeys) {
|
|
1085
|
+
const entries = plannedEntries.filter(e => e.repo === repoKey)
|
|
1086
|
+
const repoRows = []
|
|
1087
|
+
let repoActual = null
|
|
1088
|
+
let repoFailure = null
|
|
1089
|
+
try {
|
|
1090
|
+
repoActual = collectRepoActual({ repoKey, specBase: sb, cwd, runtimeRoot, changeName })
|
|
1091
|
+
} catch (e) {
|
|
1092
|
+
repoFailure = e && e.message ? String(e.message).split('\n')[0] : String(e)
|
|
1093
|
+
}
|
|
1094
|
+
if (repoFailure || !repoActual || repoActual.degradedReason) {
|
|
1095
|
+
// degraded 仓(内核 fail-soft 产物或调用点异常):v1 ⊘ 形态回退——该组行恒 untouched,
|
|
1096
|
+
// 降级原因进 note 与 repos[] 条目(平台消费方可感知),主仓表不受影响。
|
|
1097
|
+
const reason = repoFailure
|
|
1098
|
+
? `内核采集异常: ${repoFailure}`
|
|
1099
|
+
: (repoActual && repoActual.degradedReason) || '跨仓采集不可用'
|
|
1100
|
+
for (const e of entries) {
|
|
1101
|
+
repoRows.push({ path: e.path, planned: e.operation || null, additions: 0, deletions: 0, kind: 'modified', verdict: 'untouched', crossRepo: repoKey })
|
|
1102
|
+
}
|
|
1103
|
+
notes.push(`跨仓 ${repoKey} 对账降级: ${reason}`)
|
|
1104
|
+
crossRepoEntries.push({
|
|
1105
|
+
key: repoKey,
|
|
1106
|
+
repoPath: repoActual ? repoActual.repoPath : null,
|
|
1107
|
+
anchor: repoActual ? repoActual.anchor : { source: 'degraded', base: null, head: null, label: 'degraded' },
|
|
1108
|
+
totals: repoTotals(repoRows),
|
|
1109
|
+
degraded: true,
|
|
1110
|
+
degradedReason: reason,
|
|
1111
|
+
})
|
|
1112
|
+
crossRepoRows.push(...repoRows)
|
|
1113
|
+
continue
|
|
1114
|
+
}
|
|
1115
|
+
// 真实三态:该仓 actual(filterDeliverableFiles 过滤,主仓口径)× 该组声明面 pathMatches
|
|
1116
|
+
// 双向容差(glob/目录前缀兼容,design 清单可写 glob)→ planned / unplanned / untouched
|
|
1117
|
+
const repoFiles = [...new Set(filterDeliverableFiles(repoActual.files).filter(Boolean))].map(toPosix).sort()
|
|
1118
|
+
const repoStats = collectNumstatByPath(repoActual.repoPath, repoFiles, { baseRef: repoActual.anchor.base })
|
|
1119
|
+
const repoMatched = new Set()
|
|
1120
|
+
for (const f of repoFiles) {
|
|
1121
|
+
const path = toPosix(f)
|
|
1122
|
+
const st = repoStats.get(path) || degradedStat(repoActual.repoPath, path)
|
|
1123
|
+
const row = { path, planned: null, additions: st.additions, deletions: st.deletions, kind: st.kind, verdict: 'unplanned', crossRepo: repoKey }
|
|
1124
|
+
const entry = entries.find(e => pathMatches(path, e.path))
|
|
1125
|
+
if (entry) {
|
|
1126
|
+
repoMatched.add(entry.path)
|
|
1127
|
+
row.planned = entry.operation || null
|
|
1128
|
+
row.verdict = 'planned'
|
|
1129
|
+
} else {
|
|
1130
|
+
// 工具/平台脚手架软桶(主仓 unplanned 行同款):CLI/平台自装文件不占「计划外」逐条清单
|
|
1131
|
+
const facility = classifyToolScaffold(path)
|
|
1132
|
+
if (facility) row.facility = facility
|
|
1133
|
+
}
|
|
1134
|
+
repoRows.push(row)
|
|
1135
|
+
}
|
|
1136
|
+
// 声明未动:该仓清单文件无实际改动 → 补行(行数 0/0,主仓补行同款;crossRepo 恒带)
|
|
1137
|
+
for (const e of entries) {
|
|
1138
|
+
if (repoMatched.has(e.path)) continue
|
|
1139
|
+
repoRows.push({ path: e.path, planned: e.operation || null, additions: 0, deletions: 0, kind: 'modified', verdict: 'untouched', crossRepo: repoKey })
|
|
1140
|
+
}
|
|
1141
|
+
crossRepoEntries.push({
|
|
1142
|
+
key: repoKey,
|
|
1143
|
+
repoPath: repoActual.repoPath,
|
|
1144
|
+
anchor: repoActual.anchor,
|
|
1145
|
+
totals: repoTotals(repoRows),
|
|
1146
|
+
degraded: false,
|
|
1147
|
+
degradedReason: null,
|
|
1148
|
+
})
|
|
1149
|
+
crossRepoRows.push(...repoRows)
|
|
1150
|
+
}
|
|
1027
1151
|
}
|
|
1028
1152
|
|
|
1029
1153
|
// —— 三态判定 ——
|
|
@@ -1055,19 +1179,40 @@ async function computeFullFlowAudit({ cwd, specBase, changeName, platformOpts, c
|
|
|
1055
1179
|
}
|
|
1056
1180
|
// 计划未动:清单文件无实际改动 → 补行(行数 0/0);计划侧降级时不产(plannedEntries 为空天然不跑)
|
|
1057
1181
|
for (const e of plannedEntries) {
|
|
1182
|
+
// 跨仓条目:由上方跨仓链路产出行(真实三态或 degraded ⊘ 形态)——主仓补行不接管
|
|
1183
|
+
// (旧行为的 untouched+crossRepo 补行即跨仓恒「计划未动」的失真源头,task-02 接管点)
|
|
1184
|
+
if (e.repo) continue
|
|
1058
1185
|
if (matchedPlanned.has(e.path)) continue
|
|
1059
1186
|
const row = { path: e.path, planned: e.operation || null, additions: 0, deletions: 0, kind: 'modified', verdict: 'untouched' }
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
} else {
|
|
1064
|
-
// 退栈排除 + 盘面已存在 → 他者会话已写盘(改进点 3);NEW: 前缀剥除后比对(pathMatches 同款口径)
|
|
1065
|
-
const norm = toPosix(e.path).replace(/^NEW:/, '')
|
|
1066
|
-
if (foreignExcludedSet.has(norm) && existsSync(join(numstatRoot, norm))) row.suspectedForeignDone = true
|
|
1067
|
-
}
|
|
1187
|
+
// 退栈排除 + 盘面已存在 → 他者会话已写盘(改进点 3);NEW: 前缀剥除后比对(pathMatches 同款口径)
|
|
1188
|
+
const norm = toPosix(e.path).replace(/^NEW:/, '')
|
|
1189
|
+
if (foreignExcludedSet.has(norm) && existsSync(join(numstatRoot, norm))) row.suspectedForeignDone = true
|
|
1068
1190
|
rows.push(row)
|
|
1069
1191
|
}
|
|
1070
1192
|
|
|
1193
|
+
// 信封 repos[](--json 契约 v2,仅计划侧含跨仓条目时输出——单仓变更/预执行视图零新增字段,
|
|
1194
|
+
// additive):main 条目首位(repoPath=null 不冗余;anchor.source='main-<form>' 包装主仓
|
|
1195
|
+
// baseAnchor;totals 只计主仓行——跨仓行并入前取)+ 各跨仓仓条目(内核透传锚点/该仓行三态
|
|
1196
|
+
// 计数/degraded 态)。顶层 totals 保持全表合计(下方 sumTotals(rows)——跨仓行并入后自然
|
|
1197
|
+
// 计入,多仓变更合计随真实化变化正是修复目标:现状恒 0/0 是失真)。
|
|
1198
|
+
let reposOut = null
|
|
1199
|
+
if (crossRepoKeys.length > 0) {
|
|
1200
|
+
reposOut = [
|
|
1201
|
+
{
|
|
1202
|
+
key: 'main',
|
|
1203
|
+
repoPath: null,
|
|
1204
|
+
anchor: { source: `main-${actual.form}`, base: actual.baseAnchor || null, head: null, label: `${actual.form} 主仓锚` },
|
|
1205
|
+
totals: repoTotals(rows),
|
|
1206
|
+
degraded: false,
|
|
1207
|
+
degradedReason: null,
|
|
1208
|
+
},
|
|
1209
|
+
...crossRepoEntries,
|
|
1210
|
+
]
|
|
1211
|
+
}
|
|
1212
|
+
// 跨仓行并入总表(主仓行在前、跨仓组按 repoKey 声明序追加——顶层 totals 与 rows 消费方
|
|
1213
|
+
// 无序假设,渲染面 task-03 按 crossRepo 分组)
|
|
1214
|
+
rows.push(...crossRepoRows)
|
|
1215
|
+
|
|
1071
1216
|
// 冻结 patch(collectPatch,execute --done 落盘方消费):与行数同根同锚(numstatRoot +
|
|
1072
1217
|
// baseAnchor;无锚 HEAD 兜底同口径),全窗口 diff + untracked 自拼 hunk(buildFrozenPatch)
|
|
1073
1218
|
const frozenPatch = collectPatch
|
|
@@ -1081,6 +1226,7 @@ async function computeFullFlowAudit({ cwd, specBase, changeName, platformOpts, c
|
|
|
1081
1226
|
baseAnchor: actual.baseAnchor || (usedHeadFallback ? 'head-uncommitted-window' : null),
|
|
1082
1227
|
totals: { files: rows.length, ...sumTotals(rows) },
|
|
1083
1228
|
rows,
|
|
1229
|
+
...(reposOut ? { repos: reposOut } : {}),
|
|
1084
1230
|
excluded: { foreignDeclared: [] },
|
|
1085
1231
|
frozenPatch,
|
|
1086
1232
|
note: notes.length > 0 ? notes.join(';') : null,
|
|
@@ -1112,8 +1258,19 @@ async function computeFullFlowAudit({ cwd, specBase, changeName, platformOpts, c
|
|
|
1112
1258
|
* 增量字段:跨仓条目标 repoKey / 退栈排除但已写盘的 planned 文件 / 计划外行里的工具平台
|
|
1113
1259
|
* 脚手架桶——均为按存在性读取,缺省无字段零回归)
|
|
1114
1260
|
* excluded: { foreignDeclared: Array<{ file: string, sessions: string[] }> },
|
|
1261
|
+
* repos?: Array<{ key: 'main'|string, repoPath: string|null,
|
|
1262
|
+
* anchor: { source: string, base: string|null, head: string|null, label: string },
|
|
1263
|
+
* totals: { files: number, additions: number, deletions: number,
|
|
1264
|
+
* planned: number, unplanned: number, untouched: number },
|
|
1265
|
+
* degraded: boolean, degradedReason: string|null }>,
|
|
1115
1266
|
* note?: string|null,
|
|
1116
1267
|
* gateProfile?: object|null }>}
|
|
1268
|
+
* repos(2026-09-20 scope-audit-cross-repo task-02,--json 契约 v2 增量信封,消费方=平台):
|
|
1269
|
+
* 仅 full-flow 模式且计划侧含跨仓条目、非预执行形态时输出(单仓变更/预执行视图零新增字段,
|
|
1270
|
+
* additive);main 条目始终首位(只计主仓行),跨仓条目按内核透传锚点档 + 该仓行三态计数,
|
|
1271
|
+
* degraded 仓(未注册/不可达/git 不可用)totals 恒全 untouched、degradedReason 非空。跨仓行
|
|
1272
|
+
* verdict 为真实三态(design 接口定义「行级」表);degraded 仓/预执行/旧快照回放行保持 v1
|
|
1273
|
+
* ⊘ 语义(恒 untouched + crossRepo)。settled 快照回放原样透传 snap.repos(旧快照无键不输出)。
|
|
1117
1274
|
* gateProfile(2026-09-14-quick-exit-tiered-gates task-03 增量字段,消费方按存在性读取):
|
|
1118
1275
|
* 仅 quick 模式携带——实时态透传 auditQuickCompletion 挂的 review.gateProfile(不重复计算);
|
|
1119
1276
|
* 冻结重放态透传 quicklog/patches 记录冻结值(旧记录无 → rows 文件清单现算重放)。full-flow
|
|
@@ -1177,6 +1334,11 @@ export async function computeChangeScopeAudit({ cwd, specBase, changeName, platf
|
|
|
1177
1334
|
* 出完整变化内容(git 原生 diff 格式)。锚点/执行根与表格行数同口径——worktree 形态 A 对
|
|
1178
1335
|
* worktree 工作树跑、quick 对会话根跑、归档冻结形态用快照补采基点;无锚时 HEAD 未提交窗口兜底。
|
|
1179
1336
|
*
|
|
1337
|
+
* 跨仓行路由(2026-09-20 scope-audit-cross-repo task-03,D-002@v1):filePath 命中结果 rows 的
|
|
1338
|
+
* crossRepo 行 → 跳过主仓冻结 patch 捷径与主仓锚链,在该仓根出 diff——A 档(reviews 锡点
|
|
1339
|
+
* base..head 封闭区间=跨仓版冻结档)优先,B 档实时窗口兜底,C 档注记看本体;旧快照(无 repos
|
|
1340
|
+
* 键)锚点不可得 → 提示人工渠道。主仓行路径零变化。
|
|
1341
|
+
*
|
|
1180
1342
|
* untracked 新文件不在 git diff 内(整个文件都是新增)→ note 提示看文件本体;该锚点窗口
|
|
1181
1343
|
* 无改动 → diff 空串 + note。纯读 fail-soft。
|
|
1182
1344
|
*
|
|
@@ -1193,6 +1355,87 @@ export async function getFileDiff({ cwd, specBase, changeName, platformOpts, fil
|
|
|
1193
1355
|
const norm = toPosix(filePath)
|
|
1194
1356
|
const sb = specBase || join(cwd, '.sillyspec')
|
|
1195
1357
|
|
|
1358
|
+
// —— 跨仓行路由(2026-09-20 scope-audit-cross-repo task-03,D-002@v1)——
|
|
1359
|
+
// filePath 命中结果 rows 的跨仓行(crossRepo 字段)→ **先于主仓冻结 patch 捷径**改在该仓
|
|
1360
|
+
// 出 diff(防两仓同相对路径被主仓 patch 误切 + 跨仓内容不在主仓 patch 内会被误报「窗口外
|
|
1361
|
+
// 文件」,评审交叉点⑤)。A 档(repos[].anchor 的 base+head = execute task reviews 锡点
|
|
1362
|
+
// 区间)优先 `git diff base..head -- file` 在该仓根执行(封闭区间 = 跨仓版冻结档,收尾后
|
|
1363
|
+
// 不漂);B 档实时窗口兜底;C 档仅 status 面(无 diff 锚)→ 注记看本体。主仓行不进本分支,
|
|
1364
|
+
// 全链零变化。
|
|
1365
|
+
const crossRow = Array.isArray(result.rows)
|
|
1366
|
+
? result.rows.find(x => x && typeof x === 'object' && toPosix(x.path) === norm && x.crossRepo)
|
|
1367
|
+
: null
|
|
1368
|
+
if (crossRow) {
|
|
1369
|
+
const repoKey = crossRow.crossRepo
|
|
1370
|
+
// 无 repos 键 = 旧快照回放态(v1:跨仓行恒 untouched 无锚)——该仓锚点不可得,不猜:
|
|
1371
|
+
// 按快照契约提示人工渠道(R-07 同款语义:锚点锡点冻在 execute-runs reviews)
|
|
1372
|
+
if (!Array.isArray(result.repos)) {
|
|
1373
|
+
return { ok: true, mode: result.mode, root: cwd, baseRef: null, anchorLabel: null, diff: null,
|
|
1374
|
+
note: '跨仓行快照冻结于跨仓对账上线前——请到该仓人工 git diff' }
|
|
1375
|
+
}
|
|
1376
|
+
const repoEntry = result.repos.find(x => x && typeof x === 'object' && x.key === repoKey)
|
|
1377
|
+
if (!repoEntry) {
|
|
1378
|
+
return { ok: false, mode: result.mode, root: cwd, baseRef: null, anchorLabel: null, diff: null,
|
|
1379
|
+
note: `跨仓 ${repoKey} 的 repos 信封条目缺失——锚点不可得` }
|
|
1380
|
+
}
|
|
1381
|
+
if (repoEntry.degraded || !repoEntry.repoPath) {
|
|
1382
|
+
return { ok: false, mode: result.mode, root: repoEntry.repoPath || cwd, baseRef: null, anchorLabel: null, diff: null,
|
|
1383
|
+
note: `跨仓 ${repoKey} 不可达${repoEntry.degradedReason ? '——' + repoEntry.degradedReason : ''}` }
|
|
1384
|
+
}
|
|
1385
|
+
const anchor = repoEntry.anchor && typeof repoEntry.anchor === 'object' ? repoEntry.anchor : null
|
|
1386
|
+
// A 档:reviews base..head 封闭区间(冻结语义,无后续演进混入)
|
|
1387
|
+
if (anchor && anchor.base && anchor.head) {
|
|
1388
|
+
const range = `${anchor.base}..${anchor.head}`
|
|
1389
|
+
const rangeLabel = '跨仓 reviews 锡点区间(冻结语义,无后续演进混入)'
|
|
1390
|
+
const rd = safeGit(repoEntry.repoPath, ['diff', '--no-color', range, '--', norm], { timeout: 30 * 1000 })
|
|
1391
|
+
if (rd.error) {
|
|
1392
|
+
return { ok: false, mode: result.mode, root: repoEntry.repoPath, baseRef: range, anchorLabel: '跨仓 reviews 锡点区间', diff: null,
|
|
1393
|
+
note: `git diff 执行失败: ${rd.error}` }
|
|
1394
|
+
}
|
|
1395
|
+
const diff = rd.value || ''
|
|
1396
|
+
if (diff) {
|
|
1397
|
+
return { ok: true, mode: result.mode, root: repoEntry.repoPath, baseRef: range, anchorLabel: rangeLabel, diff, note: null }
|
|
1398
|
+
}
|
|
1399
|
+
// 空 diff:盘上存在且未跟踪 → 新文件看本体(tracked 查询同主仓逻辑);否则区间内无改动
|
|
1400
|
+
if (existsSync(join(repoEntry.repoPath, norm))) {
|
|
1401
|
+
const tracked = safeGit(repoEntry.repoPath, ['ls-files', '--', norm], { timeout: 15 * 1000 })
|
|
1402
|
+
if (!tracked || !tracked.value || !tracked.value.trim()) {
|
|
1403
|
+
return { ok: true, mode: result.mode, root: repoEntry.repoPath, baseRef: range, anchorLabel: '跨仓 reviews 锡点区间', diff: null,
|
|
1404
|
+
note: '未跟踪新文件——不在 git diff 内,文件全部行为新增;直接查看文件本体' }
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
return { ok: true, mode: result.mode, root: repoEntry.repoPath, baseRef: range, anchorLabel: '跨仓 reviews 锡点区间', diff: '',
|
|
1408
|
+
note: '该文件在此区间无 diff(未改动)' }
|
|
1409
|
+
}
|
|
1410
|
+
// C 档:仅 status 面(该仓无 diff 锚——未提交窗口),git diff 无意义 → 直接指引看本体
|
|
1411
|
+
if (anchor && anchor.source === 'head-uncommitted-window') {
|
|
1412
|
+
return { ok: true, mode: result.mode, root: repoEntry.repoPath, baseRef: 'HEAD', anchorLabel: anchor.label || 'HEAD 未提交窗口', diff: null,
|
|
1413
|
+
note: '该仓无 diff 锚(未提交窗口)——直接查看文件本体' }
|
|
1414
|
+
}
|
|
1415
|
+
// B 档(head~1-window)及未知降级锚(base=null):实时窗口兜底——diff HEAD 与主仓无锚
|
|
1416
|
+
// 兜底同口径,anchorLabel 带该仓降级锚档位、note 注明含后续演进
|
|
1417
|
+
const liveLabel = anchor && typeof anchor.label === 'string' && anchor.label ? anchor.label : 'HEAD 未提交窗口(降级锚兜底)'
|
|
1418
|
+
const ld = safeGit(repoEntry.repoPath, ['diff', '--no-color', 'HEAD', '--', norm], { timeout: 30 * 1000 })
|
|
1419
|
+
if (ld.error) {
|
|
1420
|
+
return { ok: false, mode: result.mode, root: repoEntry.repoPath, baseRef: 'HEAD', anchorLabel: liveLabel, diff: null,
|
|
1421
|
+
note: `git diff 执行失败: ${ld.error}` }
|
|
1422
|
+
}
|
|
1423
|
+
const liveDiff = ld.value || ''
|
|
1424
|
+
if (liveDiff) {
|
|
1425
|
+
return { ok: true, mode: result.mode, root: repoEntry.repoPath, baseRef: 'HEAD', anchorLabel: liveLabel, diff: liveDiff,
|
|
1426
|
+
note: '跨仓降级锚(非 reviews 锡点区间)——实时窗口 diff,含该仓后续演进' }
|
|
1427
|
+
}
|
|
1428
|
+
if (existsSync(join(repoEntry.repoPath, norm))) {
|
|
1429
|
+
const tracked = safeGit(repoEntry.repoPath, ['ls-files', '--', norm], { timeout: 15 * 1000 })
|
|
1430
|
+
if (!tracked || !tracked.value || !tracked.value.trim()) {
|
|
1431
|
+
return { ok: true, mode: result.mode, root: repoEntry.repoPath, baseRef: 'HEAD', anchorLabel: liveLabel, diff: null,
|
|
1432
|
+
note: '未跟踪新文件——不在 git diff 内,文件全部行为新增;直接查看文件本体' }
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
return { ok: true, mode: result.mode, root: repoEntry.repoPath, baseRef: 'HEAD', anchorLabel: liveLabel, diff: '',
|
|
1436
|
+
note: '该文件在此锚点窗口内无 diff(未改动)' }
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1196
1439
|
// —— 真·当时内容比对(quick-359a48f1):已收尾变更优先冻结 patch 切片 ——
|
|
1197
1440
|
// ① quick 记录态自带 frozenPatchPath;② full-flow 变更目录 scope-audit.patch。
|
|
1198
1441
|
// patch 冻结在收尾时点,不含后续演进——实时锚 diff 只作 patch 缺失的兜底(口径标注)。
|
|
@@ -1333,12 +1576,28 @@ export function renderScopeAuditTable(result, opts = {}) {
|
|
|
1333
1576
|
|
|
1334
1577
|
const maxRows = Number.isFinite(opts.maxRows) && opts.maxRows > 0 ? opts.maxRows : Infinity
|
|
1335
1578
|
const shown = rows.slice(0, maxRows)
|
|
1579
|
+
// ── 跨仓行 label 判定基(2026-09-20 scope-audit-cross-repo task-03,design Wave 2 渲染面)──
|
|
1580
|
+
// repos[] 信封存在(task-02 起 full-flow 真实对账产出 / 新快照回放)→ 非 degraded 仓的跨仓行
|
|
1581
|
+
// 出真实三态带仓标(`✓ 计划内 [key]` 形态);degraded 仓与**无 repos 键**的旧形态(v1 快照
|
|
1582
|
+
// 回放 / 预执行清单视图——行 verdict 恒 untouched,与真实 untouched 在行内不可区分)保留
|
|
1583
|
+
// v1 ⊘ 标记(历史形态兼容,不假装对账过)。
|
|
1584
|
+
const reposEnvelope = Array.isArray(r.repos) ? r.repos.filter(x => x && typeof x === 'object') : null
|
|
1585
|
+
const crossEnvelopeEntries = reposEnvelope && reposEnvelope.length > 0
|
|
1586
|
+
? reposEnvelope.filter(x => x.key && x.key !== 'main')
|
|
1587
|
+
: []
|
|
1588
|
+
const hasCrossEnvelope = crossEnvelopeEntries.length > 0
|
|
1589
|
+
const degradedRepoKeys = new Set(crossEnvelopeEntries.filter(x => x.degraded).map(x => x.key))
|
|
1590
|
+
const crossRepoRowLabel = (row) => {
|
|
1591
|
+
if (!hasCrossEnvelope || degradedRepoKeys.has(row.crossRepo)) return CROSS_REPO_LABEL
|
|
1592
|
+
const base = row.facility ? FACILITY_LABEL : VERDICT_LABEL[row.verdict]
|
|
1593
|
+
return base ? `${base} [${row.crossRepo}]` : CROSS_REPO_LABEL
|
|
1594
|
+
}
|
|
1336
1595
|
for (const row of shown) {
|
|
1337
1596
|
if (!row || typeof row !== 'object') continue
|
|
1338
1597
|
const stat = row.kind === 'binary'
|
|
1339
1598
|
? { a: 'BIN', d: 'BIN' }
|
|
1340
1599
|
: { a: fmtCount(row.additions) ?? '—', d: fmtCount(row.deletions) ?? '—' }
|
|
1341
|
-
const label = row.crossRepo ?
|
|
1600
|
+
const label = row.crossRepo ? crossRepoRowLabel(row)
|
|
1342
1601
|
: row.suspectedForeignDone ? SUSPECTED_FOREIGN_LABEL
|
|
1343
1602
|
: row.facility ? FACILITY_LABEL
|
|
1344
1603
|
: VERDICT_LABEL[row.verdict] || ATTRIBUTION_LABEL[row.attribution] || ''
|
|
@@ -1353,8 +1612,11 @@ export function renderScopeAuditTable(result, opts = {}) {
|
|
|
1353
1612
|
// ⚠️ 出口指引(full-flow 三态面):计划外补声明、计划未动确认遗漏。
|
|
1354
1613
|
// 行级标注三分(scope-audit-cross-repo-blindness 改进点 1/2/3):跨仓行/疑似他者已实现行/
|
|
1355
1614
|
// 工具设施桶各有去向说明,不混入笼统的「计划外/计划未动」计数逼用户排查噪音。
|
|
1356
|
-
|
|
1357
|
-
|
|
1615
|
+
// 跨仓行不占主仓笼统 ⚠️ 计数(task-03):非 degraded 仓行已有 per-repo 汇总(见下方跨仓
|
|
1616
|
+
// 汇总段)、degraded 仓行有 ⊘ 降级行——planned/unplanned/untouched 三态与 facility 桶的
|
|
1617
|
+
// 主仓笼统计数一律排除 crossRepo 行(现状 unplanned 未排除致跨仓行冒充主仓计划外)。
|
|
1618
|
+
const unplanned = rows.filter(x => x && x.verdict === 'unplanned' && !x.facility && !x.crossRepo).length
|
|
1619
|
+
const facility = rows.filter(x => x && x.verdict === 'unplanned' && x.facility && !x.crossRepo).length
|
|
1358
1620
|
const untouchedReal = rows.filter(x => x && x.verdict === 'untouched' && !x.suspectedForeignDone && !x.crossRepo).length
|
|
1359
1621
|
const suspectedForeign = rows.filter(x => x && x.suspectedForeignDone).length
|
|
1360
1622
|
const crossRepo = rows.filter(x => x && x.crossRepo).length
|
|
@@ -1371,7 +1633,28 @@ export function renderScopeAuditTable(result, opts = {}) {
|
|
|
1371
1633
|
lines.push(` ℹ️ 工具/平台设施 ${facility} 文件(CLI/平台自装脚手架,非本变更改动,不占计划外)`)
|
|
1372
1634
|
}
|
|
1373
1635
|
if (crossRepo > 0) {
|
|
1374
|
-
|
|
1636
|
+
// 跨仓汇总段(task-03,design Wave 2):有 repos[] 信封 → 聚合计数行 + 逐仓一行真实汇总
|
|
1637
|
+
// (锚点档 anchor.label + 三态计数 + 行数);degraded 仓逐仓一行降级原因。无 repos 键
|
|
1638
|
+
// (v1 快照回放 / 预执行清单视图——跨仓行 ⊘ 本表不含)保留现行单行文案(历史形态)。
|
|
1639
|
+
// 全 degraded 形态下所有跨仓行仍是 ⊘(本表不含)→ 聚合行保留 v1 文案(计数可见 + 逐字
|
|
1640
|
+
// 兼容既有断言),非全 degraded 时行已真实进表 → 聚合行改「已按仓对账」措辞。
|
|
1641
|
+
if (hasCrossEnvelope) {
|
|
1642
|
+
const allDegraded = crossEnvelopeEntries.every(x => x.degraded)
|
|
1643
|
+
lines.push(allDegraded
|
|
1644
|
+
? ` ℹ️ 跨仓 ${crossRepo} 文件(⊘ 行——本表不含,请到对应仓跑 scope-audit 对账)`
|
|
1645
|
+
: ` ℹ️ 跨仓 ${crossRepo} 文件——已按 local.yaml repos 分仓对账(逐仓汇总如下)`)
|
|
1646
|
+
for (const re of crossEnvelopeEntries) {
|
|
1647
|
+
if (re.degraded) {
|
|
1648
|
+
lines.push(` ℹ️ 跨仓 ${re.key}:⊘ 对账降级——${re.degradedReason || '未知原因'}`)
|
|
1649
|
+
continue
|
|
1650
|
+
}
|
|
1651
|
+
const t = re.totals && typeof re.totals === 'object' ? re.totals : {}
|
|
1652
|
+
const anchorTxt = re.anchor && typeof re.anchor.label === 'string' && re.anchor.label ? re.anchor.label : '—'
|
|
1653
|
+
lines.push(` ℹ️ 跨仓 ${re.key}:锚点 ${anchorTxt}——✓ ${t.planned ?? 0} / ⚠️ 计划外 ${t.unplanned ?? 0} / ⚠️ 未动 ${t.untouched ?? 0}(+${t.additions ?? 0}/-${t.deletions ?? 0})`)
|
|
1654
|
+
}
|
|
1655
|
+
} else {
|
|
1656
|
+
lines.push(` ℹ️ 跨仓 ${crossRepo} 文件(⊘ 行——本表不含,请到对应仓跑 scope-audit 对账)`)
|
|
1657
|
+
}
|
|
1375
1658
|
}
|
|
1376
1659
|
const undeclared = rows.filter(x => x && x.attribution === 'undeclared').length
|
|
1377
1660
|
if (undeclared > 0) lines.push(` ⚠️ 未声明 ${undeclared} 文件——超出 allowedFiles 声明面,补 --files 声明或注明原因`)
|
package/src/stages/quick.js
CHANGED
|
@@ -70,6 +70,8 @@ step 3 --done 审计会把 src 核心文件(CLI / 状态机 / 注入框架等
|
|
|
70
70
|
### 📌 末步预告:结果摘要四字段(step3 --done 硬校验,缺一被拒)
|
|
71
71
|
下一步(step3)--done 必须给四字段结果摘要(推荐 \`--req/--cause/--solution/--result\` 四参数,CLI 自动合成;兼容 \`--output "需求:… 根因:… 方案:… 结果:…"\`)。其中「结果:验证情况(测试数 / lint / typecheck / 部署状态)」的素材在**本步**产生——实现摘要里请顺手记下具体验证数据(如「3 个单测通过、lint 0 告警」),step3 直接引用,避免到收尾时回忆不起来或被硬校验打回重跑。
|
|
72
72
|
|
|
73
|
+
⚠️ 文件括注 \`--file-notes "path::注 || path2"\` 也只随 step3 --done 同一命令传才生效——**本步与 step1 的命令带了会被直接拒绝执行**(白跑一轮)。括注内容留到 step3 收尾时连同四字段一起传(2026-09-20 用户反馈:提示此前只在 step3 prompt 可见,第一次传白跑一轮)。
|
|
74
|
+
|
|
73
75
|
### 铁律
|
|
74
76
|
- 不要修改无关文件
|
|
75
77
|
- 不要编造不存在的 CLI 子命令
|
package/src/sync.js
CHANGED
|
@@ -2281,6 +2281,18 @@ export async function syncSpecTreeOnly(changeName, cwd, opts = {}) {
|
|
|
2281
2281
|
return syncSpecTree(safePlatformSpecDir(cwd) || join(cwd, '.sillyspec'), platform, changeName, { signal: opts.signal });
|
|
2282
2282
|
}
|
|
2283
2283
|
|
|
2284
|
+
/**
|
|
2285
|
+
* 平台连接预判(run/bg-sync.js 父侧 spawn 前的免开销短路):与 _getPlatform 同源判据
|
|
2286
|
+
* 的布尔化——env 两键齐全,或 cwd/.sillyspec/local.yaml platform 段带 url+token。
|
|
2287
|
+
* 未连接时不为一次注定 no-op 的同步 spawn 后台子进程(本地独立用户零开销零行为变化)。
|
|
2288
|
+
* 只做连接性预判不携带凭据:子进程自行经 _getPlatform 取全量配置(判据同源必一致)。
|
|
2289
|
+
*/
|
|
2290
|
+
export function peekPlatformConnected(cwd) {
|
|
2291
|
+
if (process.env.SILLYHUB_PLATFORM_URL && process.env.SILLYHUB_PLATFORM_TOKEN) return true;
|
|
2292
|
+
const p = readLocalYaml(cwd).platform;
|
|
2293
|
+
return Boolean(p && p.url && p.token);
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2284
2296
|
// TBD-hub-api: approve/reject 端点路径与请求体以 SillyHub 仓库实际 API 为准;
|
|
2285
2297
|
// 对齐时只改本函数(_submitApproval),无需动 approve/reject 入口。
|
|
2286
2298
|
/**
|
package/src/verify-postcheck.js
CHANGED
|
@@ -2909,21 +2909,25 @@ export function reconcileTargetFiles({ cwd, specBase = null, changeName = null,
|
|
|
2909
2909
|
const decl = collectDeclaredTargetFiles(sb, changeName)
|
|
2910
2910
|
// —— 跨仓 per-repo 对账(坑 cross-repo-reconcile-blindness,2026-09-15 复盘兑现 D-004 分期:
|
|
2911
2911
|
// 此前 22 个跨仓文件在归档表全标「计划未动」、跨仓全靠人工到对应仓解释)——advisory 不阻断:
|
|
2912
|
-
//
|
|
2913
|
-
//
|
|
2914
|
-
//
|
|
2912
|
+
// 锚点档经内核分级(A reviews 锡点区间 > B HEAD~1..HEAD 窗口 > C 未提交窗口,runtimeRoot/
|
|
2913
|
+
// changeName 传参贯通喂 A 档 reviews 解析——评审 G2;锚点脆弱期会产②类假信号),只报告不
|
|
2914
|
+
// 翻转主仓状态。结果经 crossRepo 字段(gates 渲染明细)与 notes(摘要随各状态出口流动)
|
|
2915
|
+
// 双通道出口。
|
|
2915
2916
|
let crossRepo = []
|
|
2916
2917
|
if (decl.crossRepoCards.length > 0) {
|
|
2917
2918
|
try {
|
|
2918
|
-
crossRepo = reconcileCrossRepoDeclarations({ specBase: sb, cwd, declarationsByRepo: decl.crossRepoDeclarations })
|
|
2919
|
+
crossRepo = reconcileCrossRepoDeclarations({ specBase: sb, cwd, declarationsByRepo: decl.crossRepoDeclarations, runtimeRoot: rt, changeName })
|
|
2919
2920
|
} catch { crossRepo = [] /* 跨仓对账异常不拖垮主仓链(advisory 定位) */ }
|
|
2920
2921
|
for (const label of decl.crossRepoCards) {
|
|
2921
2922
|
notes.push(`跨仓 task 卡 ${label} 不进主仓对账(主仓 actual 对不上跨仓声明,D-004)——其声明已转 per-repo 对账(见下方跨仓对账段)`)
|
|
2922
2923
|
}
|
|
2923
2924
|
for (const r of crossRepo) {
|
|
2925
|
+
// 锚点档动态化(task-03):优先内核透传的 anchor.label(A/B/C/degraded 四档人类可读描述);
|
|
2926
|
+
// 旧形态无 anchor 字段 → 防御性回退原文案(该仓最近提交窗口)
|
|
2927
|
+
const anchorTxt = r.anchor && typeof r.anchor.label === 'string' && r.anchor.label ? r.anchor.label : '该仓最近提交窗口'
|
|
2924
2928
|
notes.push(r.degradedReason
|
|
2925
2929
|
? `跨仓 ${r.repo}:${r.degradedReason}`
|
|
2926
|
-
: `跨仓 ${r.repo} 对账:声明 ${r.declaredCount} / 实测 ${r.actualCount} / 对上 ${r.matched.length} / ②缺 ${r.missing.length} / ③多 ${r.undeclared.length}${r.scaffoldCount > 0 ? `(另有 ${r.scaffoldCount} 脚手架已聚合)` : ''}——advisory
|
|
2930
|
+
: `跨仓 ${r.repo} 对账:声明 ${r.declaredCount} / 实测 ${r.actualCount} / 对上 ${r.matched.length} / ②缺 ${r.missing.length} / ③多 ${r.undeclared.length}${r.scaffoldCount > 0 ? `(另有 ${r.scaffoldCount} 脚手架已聚合)` : ''}——advisory(锚点=${anchorTxt})`)
|
|
2927
2931
|
}
|
|
2928
2932
|
}
|
|
2929
2933
|
if (decl.cardCount === 0) {
|