sillyspec 3.32.1 → 3.32.3
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/.claude/skills/sillyspec-explore/SKILL.md +6 -1
- package/.claude/skills/sillyspec-flow/SKILL.md +1 -0
- package/package.json +2 -2
- package/src/cross-repo-reconcile.js +33 -0
- package/src/doctor-diagnostics.js +84 -4
- package/src/flow-parity.js +19 -26
- package/src/flow-review.js +4 -0
- package/src/flow.js +17 -10
- package/src/friction-tally.js +2 -1
- package/src/git-helper.js +23 -1
- package/src/index.js +23 -11
- package/src/init.js +5 -1
- package/src/knowledge-graph.js +918 -0
- package/src/knowledge-match.js +11 -6
- package/src/knowledge-vector.js +17 -3
- package/src/module-impact.js +3 -1
- package/src/progress/stage-machine.js +8 -1
- package/src/run/code-face-key.js +84 -0
- package/src/run/complete.js +32 -9
- package/src/run/gate-snapshot.js +19 -0
- package/src/run/gates.js +109 -67
- package/src/run/green-cache.js +11 -19
- package/src/run/verify-quality-scan.js +88 -45
- package/src/scope-audit.js +50 -24
- package/src/sentinel-assertions.js +27 -2
- package/src/spec-sync.js +98 -20
- package/src/stages/brainstorm.js +2 -1
- package/src/stages/cmd-existence.js +5 -1
- package/src/stages/explore.js +5 -1
- package/src/stages/knowledge.js +11 -1
- package/src/test-bindings.js +2 -0
- package/src/verify-postcheck.js +141 -18
- package/src/verify-probes.js +141 -0
- package/src/watcher.js +6 -2
- package/src/wt-commit.js +82 -3
|
@@ -50,7 +50,12 @@ sillyspec run explore --status # 查看阶段状态
|
|
|
50
50
|
|
|
51
51
|
**探索问题空间**:问澄清问题、挑战假设、重新定义问题
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
**调查兵器(话题→兵器映射,全只读)**:按话题选兵器,别只用 rg 词面考古——
|
|
54
|
+
|
|
55
|
+
- 词面考古("这段代码在哪/怎么写的")→ `rg` / `ls` / `cat`
|
|
56
|
+
- 坑史检索("这话题以前踩过什么坑")→ `sillyspec knowledge search --query "<关键词>"`(四层召回)
|
|
57
|
+
- 关系面(影响面/历史决策/需求谱系)→ `sillyspec knowledge graph impact <文件|模块|变更名>`(多跳闭包 + 防复潮可达的已否决方案)、`neighbors <锚点>` / `path <A> <B>`(推理链:"这条 FR 被谁取代""决策钉在哪个文件")、`summary`(知识面健康度)——**比较方案前先查 impact 里的 rejected/⚰️死路条目,已否决的路不重新兜售**(除非复潮条件明确满足);全部支持 `--json`
|
|
58
|
+
- 在途面(出方向建议前)→ `sillyspec status` 活跃变更一览——结论别跟在途工作撞车
|
|
54
59
|
|
|
55
60
|
**比较选项**:头脑风暴多种方案、建对比表、画权衡分析
|
|
56
61
|
|
|
@@ -40,6 +40,7 @@ sillyspec flow start --change <变更名> --input "<动机与背景>
|
|
|
40
40
|
改代码、写测试。治理工件(proposal/requirements/design)CLI 机器起草,你只需要:
|
|
41
41
|
|
|
42
42
|
- 填 design.md 四节 AGENT 槽(做法概述/接口契约/边界并发四问/风险与死路)——每节至少一行,写「不适用:<理由>」也算答,空槽 flow done 拒收
|
|
43
|
+
- 变更目录若已有收割生成的 decisions.md(frontmatter `author: flow-machine-draft`),它是底稿不是终稿:槽4 作答含「放弃方案」的,收口前把每个放弃方案拆成独立 rejected 条目(问题=决策专属问句、否决理由=放弃理由、复潮条件=重提前提),风险与对策留在主条目——rejected 条目才走蒸馏防复潮通道,埋在散文里的否决理由进不了通道;无实质放弃方案则不动
|
|
43
44
|
- 干活以 tasks.md 为进度源:做一件 → 勾一格(`- [ ]` → `- [x]`)→ 继续下一条,勿攒一把勾(勾选是收口哨兵的证据面——逐 task 核提交 token/review.json);`flow status --change <名>` 为自愿查看/恢复面(②阶段给下一任务指针与进度,非协议必需——D-007)
|
|
44
45
|
- 交付代码用显式 pathspec 提交(`git commit -m "..." -- 文件1 文件2`;patch 冻结面=baseline..HEAD 提交面,未提交的代码不进审计件)
|
|
45
46
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sillyspec",
|
|
3
|
-
"version": "3.32.
|
|
3
|
+
"version": "3.32.3",
|
|
4
4
|
"description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
|
|
5
5
|
"icon": "logo.jpg",
|
|
6
6
|
"homepage": "https://sillyspec.ppdmq.top/",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"scripts": {
|
|
27
|
-
"test:core": "node --test test/preview-migration.test.mjs test/preview-progress.test.mjs test/preview-outlet.test.mjs test/preview-gc.test.mjs test/preview-gate-isolation.test.mjs test/db-engine.test.mjs test/db-concurrency.test.mjs test/db-atomic-write.test.mjs test/platform-sync-schema.test.mjs test/platform-sync-serialization.test.mjs test/change-ownership-guards.test.mjs test/watcher.test.mjs test/watcher-alerts.test.mjs test/watcher-timeline.test.mjs test/sentinel-rules.test.mjs test/handoff.test.mjs test/execution-mode-render.test.mjs test/knowledge-inbox.test.mjs test/knowledge-baseline.test.mjs test/quick-laststep-fourfields-preview.test.mjs test/doctor-align-execute-progress.test.mjs test/test-ledger.test.mjs test/test-bindings.test.mjs test/flow-draft-binding-extract.test.mjs test/gate-snapshot-lifecycle.test.mjs test/gate-snapshot-cleanup.test.mjs test/verify-trace-residual.test.mjs test/platform-creds-fallback.test.mjs test/archive-timeline-bake.test.mjs test/doc-ref-check.test.mjs test/flow-status-json.test.mjs test/flow-status-title.test.mjs test/flow-resume-title.test.mjs test/datetime-timeago.test.mjs test/resume-domain-flip.test.mjs test/fr-regress-cap-drop.test.mjs test/fr-priority-overlap.test.mjs test/archive-commit-suggest-race.test.mjs test/flow-done-fail-brief.test.mjs test/verify-probes-refresh-backup.test.mjs test/taskcard-yaml-triage-validate.test.mjs test/gate-last-reader.test.mjs test/status-multi-active-list.test.mjs test/gate-docs-only.test.mjs test/probe7-fr-prefill.test.mjs test/plan-fill-batch-config.test.mjs test/step-guide-prune.test.mjs test/gate-stage-review-hint.test.mjs test/design-ref-suggest.test.mjs test/flow-friction-batch3.test.mjs test/wave-auto-adopt.test.mjs test/test-incremental-rerun.test.mjs",
|
|
27
|
+
"test:core": "node --test test/preview-migration.test.mjs test/preview-progress.test.mjs test/preview-outlet.test.mjs test/preview-gc.test.mjs test/preview-gate-isolation.test.mjs test/db-engine.test.mjs test/db-concurrency.test.mjs test/db-atomic-write.test.mjs test/platform-sync-schema.test.mjs test/platform-sync-serialization.test.mjs test/change-ownership-guards.test.mjs test/watcher.test.mjs test/watcher-alerts.test.mjs test/watcher-timeline.test.mjs test/sentinel-rules.test.mjs test/handoff.test.mjs test/execution-mode-render.test.mjs test/knowledge-inbox.test.mjs test/knowledge-baseline.test.mjs test/quick-laststep-fourfields-preview.test.mjs test/doctor-align-execute-progress.test.mjs test/test-ledger.test.mjs test/test-bindings.test.mjs test/flow-draft-binding-extract.test.mjs test/gate-snapshot-lifecycle.test.mjs test/gate-snapshot-cleanup.test.mjs test/verify-trace-residual.test.mjs test/platform-creds-fallback.test.mjs test/archive-timeline-bake.test.mjs test/doc-ref-check.test.mjs test/flow-status-json.test.mjs test/flow-status-title.test.mjs test/flow-resume-title.test.mjs test/datetime-timeago.test.mjs test/resume-domain-flip.test.mjs test/fr-regress-cap-drop.test.mjs test/fr-priority-overlap.test.mjs test/archive-commit-suggest-race.test.mjs test/flow-done-fail-brief.test.mjs test/verify-probes-refresh-backup.test.mjs test/taskcard-yaml-triage-validate.test.mjs test/gate-last-reader.test.mjs test/status-multi-active-list.test.mjs test/gate-docs-only.test.mjs test/probe7-fr-prefill.test.mjs test/plan-fill-batch-config.test.mjs test/step-guide-prune.test.mjs test/gate-stage-review-hint.test.mjs test/design-ref-suggest.test.mjs test/flow-friction-batch3.test.mjs test/wave-auto-adopt.test.mjs test/test-incremental-rerun.test.mjs test/knowledge-graph.test.mjs test/explore-graph-guidance.test.mjs",
|
|
28
28
|
"test": "node test/run-tests.mjs",
|
|
29
29
|
"lint": "node test/check-syntax.mjs",
|
|
30
30
|
"eval:dry": "node eval/run.mjs --dry-run",
|
|
@@ -33,6 +33,7 @@ import { join, isAbsolute, resolve } from 'path'
|
|
|
33
33
|
import { gitQuiet } from './git-helper.js'
|
|
34
34
|
import { parseRepoRegistry } from './stages/plan-postcheck.js'
|
|
35
35
|
import { filterDeliverableFiles, classifyToolScaffold } from './worktree-apply.js'
|
|
36
|
+
import { crossWorktreePath } from './worktree-cross.js'
|
|
36
37
|
import { resolveLatestExecuteRunIdWithTasks, readReview } from './task-review.js'
|
|
37
38
|
|
|
38
39
|
// 与 verify-postcheck normalizeReconcilePath 同口径(本地实现防环):剥 ./ 前缀、反斜杠归一
|
|
@@ -228,6 +229,38 @@ export function collectRepoActual({ repoKey, specBase, cwd, runtimeRoot = null,
|
|
|
228
229
|
}
|
|
229
230
|
}
|
|
230
231
|
|
|
232
|
+
// 3b) B' 档 worktree-baseline 窗口(2026-10-09-verify-reuse-friction FR-07):跨仓
|
|
233
|
+
// worktree 在场时(verify 常跑在 apply 前)交付面在 worktree——meta.baseHash..工作树
|
|
234
|
+
// 全量窗口,多笔提交与未提交改动全覆盖;原 B 档 HEAD~1..HEAD 只看跨仓主副本最近一笔
|
|
235
|
+
// 提交,对 worktree 多笔交付是假信号(2026-10-09 tombstone 取证:声明 4 实测 0)。
|
|
236
|
+
// meta 缺失/worktree 不在场/不可读 → 回退 B 档(不回退语义)。
|
|
237
|
+
try {
|
|
238
|
+
const wtPath = crossWorktreePath(specBase, changeName, repoKey)
|
|
239
|
+
const metaPath = join(wtPath, 'meta.json')
|
|
240
|
+
if (existsSync(metaPath)) {
|
|
241
|
+
const meta = JSON.parse(readFileSync(metaPath, 'utf8'))
|
|
242
|
+
const base = meta && meta.baseHash
|
|
243
|
+
if (base && typeof base === 'string' && /^[0-9a-f]{7,40}$/i.test(base)) {
|
|
244
|
+
const union2 = new Set()
|
|
245
|
+
// baseline→工作树(已提交+已改)∪ 未跟踪新文件;两查任一可用即成窗
|
|
246
|
+
const d1 = gitQuiet(wtPath, ['diff', '--name-only', base], { timeout: GIT_TIMEOUT })
|
|
247
|
+
const d2 = gitQuiet(wtPath, ['ls-files', '--others', '--exclude-standard'], { timeout: GIT_TIMEOUT })
|
|
248
|
+
if (d1 !== null || d2 !== null) {
|
|
249
|
+
for (const src of [d1, d2]) {
|
|
250
|
+
for (const f of String(src || '').split('\n')) {
|
|
251
|
+
const n = normalizeRepoPath(f)
|
|
252
|
+
if (n && n !== 'meta.json' && !n.startsWith('.sillyspec/')) union2.add(n)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
collectStatusInto(repoPath, union2)
|
|
256
|
+
return { repo: repoKey, repoPath,
|
|
257
|
+
anchor: { source: 'worktree-baseline-window', base, head: null, label: `跨仓 worktree baseline 窗口(base=${base.slice(0, 8)}…HEAD+工作树;多笔提交全覆盖)` },
|
|
258
|
+
files: [...union2].sort(), degradedReason: null }
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} catch { /* meta 不可读/键缺失 → 回退 B 档 */ }
|
|
263
|
+
|
|
231
264
|
// 4) B/C 档:diff HEAD~1..HEAD(现行 reconcile 口径)∪ status;diff 失败仅 status → C 档
|
|
232
265
|
const union = new Set()
|
|
233
266
|
const diffOut = gitQuiet(repoPath, ['diff', '--name-only', 'HEAD~1..HEAD'], { timeout: GIT_TIMEOUT })
|
|
@@ -1177,10 +1177,20 @@ function detectArchiveIntegrity(cwd, authoritySpecDir) {
|
|
|
1177
1177
|
for (const name of names) {
|
|
1178
1178
|
const dir = join(archiveDir, name);
|
|
1179
1179
|
const reasons = [];
|
|
1180
|
-
// ② plan.md
|
|
1180
|
+
// ② plan.md 在场性——thin 协议归档豁免(2026-10-09-archive-integrity-thin-aware):
|
|
1181
|
+
// flow-state.yaml 在场 = flow 轻量道归档,plan.md 非其契约面(厚道五阶段 plan 阶段产物);
|
|
1182
|
+
// 2026-09-29 thin-default 后轻量道成默认路径,一刀切要求 plan.md 把 172 份正常 thin 归档
|
|
1183
|
+
// 全判欠账(检查模型漂移,非真欠账)。任务未勾检查不豁免——thin 也要全勾。
|
|
1184
|
+
const isThinArchive = existsSync(join(dir, 'flow-state.yaml'));
|
|
1185
|
+
// thin 勾选契约 epoch(2026-10-09-archive-integrity-thin-aware):2026-10-07-thin-tasks-v3 起
|
|
1186
|
+
// tasks.md checkbox 才是进度状态机/完成契约;此前 thin 归档的 tasks.md 是机器占位稿(0 勾为
|
|
1187
|
+
// 当时常态,完成证据=flow done 六子步——change.patch/verify-result 在场性),勾选检查豁免。
|
|
1188
|
+
// 实证:2026-09-25 thin 试验批 17 份 + 09-22/24/27/10-03 共 36 份 0-N 勾归档全属此形态。
|
|
1189
|
+
const THIN_TASKS_EPOCH = '2026-10-07';
|
|
1190
|
+
const isPreEpochThin = isThinArchive && String(name) < THIN_TASKS_EPOCH;
|
|
1181
1191
|
const planPath = join(dir, 'plan.md');
|
|
1182
1192
|
const hasPlan = existsSync(planPath);
|
|
1183
|
-
if (!hasPlan) reasons.push('plan.md 缺失(归档自愈基准文件)');
|
|
1193
|
+
if (!hasPlan && !isThinArchive) reasons.push('plan.md 缺失(归档自愈基准文件)');
|
|
1184
1194
|
// ①③ 任务注册表:tasks.md 优先回退 plan.md;存在但不可读必须显式报(区分「缺席」与「读不了」)。
|
|
1185
1195
|
// 2026-08-20-task-truth-unify 之前的旧归档完成态勾在 plan.md(tasks.md 是留给后人的未勾任务清单)——
|
|
1186
1196
|
// tasks.md 有 checkbox 行但 0 勾、而 plan.md 有 ≥1 勾时,完成证据在 plan.md,切 plan.md 为完成源
|
|
@@ -1200,7 +1210,9 @@ function detectArchiveIntegrity(cwd, authoritySpecDir) {
|
|
|
1200
1210
|
return { total: t, checked: c };
|
|
1201
1211
|
};
|
|
1202
1212
|
const regPath = existsSync(tasksPath) ? tasksPath : hasPlan ? planPath : null;
|
|
1203
|
-
if (regPath) {
|
|
1213
|
+
if (regPath && !isPreEpochThin) {
|
|
1214
|
+
// ^ pre-epoch thin 跳过勾选检查(占位稿形态,见 THIN_TASKS_EPOCH 注释);
|
|
1215
|
+
// 注册表「存在但不可读」显式报的语义对 pre-epoch thin 同样不适用(其不可读非欠账面)
|
|
1204
1216
|
try {
|
|
1205
1217
|
let stat = readRegistry(regPath);
|
|
1206
1218
|
if (regPath === tasksPath && stat.total > 0 && stat.checked === 0 && hasPlan) {
|
|
@@ -1506,7 +1518,8 @@ export async function runDoctorDiagnostics({ cwd }) {
|
|
|
1506
1518
|
)
|
|
1507
1519
|
} catch { gateLeakRuntimeRoot = null }
|
|
1508
1520
|
const gateSnapshotLeak = detectGateSnapshotLeak({ runtimeRoot: gateLeakRuntimeRoot })
|
|
1509
|
-
const
|
|
1521
|
+
const knowledgeGraphIntegrity = detectKnowledgeGraphIntegrity(cwd, authoritySpecDir || join(cwd, '.sillyspec'))
|
|
1522
|
+
const dimensions = [multiDb, pointerHealth, changesSplit, changeDb, executeMismatch, docBloat, repoNativeChain, lifecycleDoc, worktreeHealth, buildEnv, mcpEndpoints, applyManifestDrift, selfMaintenanceTax, archiveIntegrity, ceremonyShadow, gateSnapshotLeak, knowledgeGraphIntegrity];
|
|
1510
1523
|
|
|
1511
1524
|
return {
|
|
1512
1525
|
dimensions,
|
|
@@ -2168,3 +2181,70 @@ export async function gcUnstampedExecuteRuns({ cwd, specDir = null, confirm = fa
|
|
|
2168
2181
|
count: candidates.length,
|
|
2169
2182
|
}
|
|
2170
2183
|
}
|
|
2184
|
+
|
|
2185
|
+
// ══ 2026-10-08-knowledge-graph(task-05,FR-03):知识图完整性面 knowledge_graph_integrity ══
|
|
2186
|
+
// 六检查项(design §4):graph-dangling-route / graph-doc-dangling-ref / graph-dangling-anchor /
|
|
2187
|
+
// graph-orphan-entry / graph-module-doc-gap / graph-changelog-dangling——全 warning 不阻断
|
|
2188
|
+
// (沿 apply_manifest_drift advisory 先例)。图=解析时内存派生(D-003:不落盘不刷新)。
|
|
2189
|
+
// 悬空目标可能活在跨仓(local.yaml repos 注册的其他仓)——聚合计数+样本,不逐条刷屏。
|
|
2190
|
+
export function detectKnowledgeGraphIntegrity(cwd, specDir) {
|
|
2191
|
+
const base = { name: 'knowledge_graph_integrity', label: '知识图完整性', safe_actions: [] }
|
|
2192
|
+
try {
|
|
2193
|
+
if (!specDir || !existsSync(join(specDir, 'knowledge'))) {
|
|
2194
|
+
return { ...base, findings: ['无 knowledge/ 目录——skipped'], pass: true, severity: null, skipped: true }
|
|
2195
|
+
}
|
|
2196
|
+
// 动态 import 防加载面放大(doctor 主路径不触发图构建成本)
|
|
2197
|
+
const { graphOrphans, graphDangling, buildKnowledgeGraph } = requireKnowledgeGraph()
|
|
2198
|
+
const graph = buildKnowledgeGraph(specDir)
|
|
2199
|
+
const findings = []
|
|
2200
|
+
const sample = (arr, n = 3) => arr.slice(0, n).map(String)
|
|
2201
|
+
|
|
2202
|
+
// ① route 悬空(弱边):INDEX 路由行指向不存在锚点——条目节点存在性由建图保证,
|
|
2203
|
+
// 此处查目标手册文件本体(file 属性)是否在场
|
|
2204
|
+
const routeTargets = new Set()
|
|
2205
|
+
for (const e of (graph.byEdgeType.get('route') || [])) routeTargets.add(graph.nodes.get(e.t)?.attrs?.file || '')
|
|
2206
|
+
const danglingRoute = [...routeTargets].filter((f) => f && !existsSync(join(specDir, 'knowledge', f)))
|
|
2207
|
+
if (danglingRoute.length > 0) findings.push(`graph-dangling-route:${danglingRoute.length} 条 INDEX 路由指向不存在文件(${sample(danglingRoute).join('、')})`)
|
|
2208
|
+
|
|
2209
|
+
// ②③ 引用/锚点悬空(中/强边):文件系统存在性(跨仓目标聚合计数不展开)
|
|
2210
|
+
const dangling = graphDangling(graph)
|
|
2211
|
+
// 跨仓分流(2026-10-09-graph-docrefs-noise):crossRepo 目标活在独立仓(backend/frontend 等),
|
|
2212
|
+
// 计数注记不点名;本仓悬空点名样本。文档引用面的裸名短引用与跨仓引用在 graphDangling 已豁免。
|
|
2213
|
+
const byStrength = { strong: [], medium: [], weak: [] }
|
|
2214
|
+
let crossRepoCount = 0
|
|
2215
|
+
for (const d of dangling) {
|
|
2216
|
+
if (d.crossRepo) { crossRepoCount++; continue }
|
|
2217
|
+
byStrength[d.strength]?.push(`${d.edge.type}→${d.missing}`)
|
|
2218
|
+
}
|
|
2219
|
+
if (byStrength.strong.length > 0) findings.push(`graph-dangling-anchor:${byStrength.strong.length} 条强边锚点指向本仓不存在文件${crossRepoCount > 0 ? `(另有 ${crossRepoCount} 条跨仓目标不点名——平台代码在独立仓)` : ''}(样本 ${sample(byStrength.strong).join('、')})`)
|
|
2220
|
+
if (byStrength.medium.length > 0) findings.push(`graph-doc-dangling-ref:${byStrength.medium.length} 条文档引用悬空(样本 ${sample(byStrength.medium).join('、')})`)
|
|
2221
|
+
|
|
2222
|
+
// ④ 孤儿条目
|
|
2223
|
+
const orphans = graphOrphans(graph)
|
|
2224
|
+
if (orphans.length > 0) findings.push(`graph-orphan-entry:${orphans.length} 个零关联节点(样本 ${sample(orphans.map((o) => o.node.label)).join('、')})`)
|
|
2225
|
+
|
|
2226
|
+
// ⑤ 模块文档缺口 / ⑥ changelog 行悬空:单一源判定(2026-10-08-graph-summary-consistency)
|
|
2227
|
+
// ——graphModuleDocGaps/graphChangelogDanglings 只在 knowledge-graph.js 定义,summary 四计数
|
|
2228
|
+
// 消费同一函数(口径一处定义两处消费,doctor↔summary 交叉断言钉死,防手抄副本漂移)
|
|
2229
|
+
const { graphModuleDocGaps, graphChangelogDanglings } = requireKnowledgeGraph()
|
|
2230
|
+
const docGap = graphModuleDocGaps(graph)
|
|
2231
|
+
if (docGap.length > 0) findings.push(`graph-module-doc-gap:${docGap.length} 个模块缺卡片或 changelog(样本 ${sample(docGap.map((n) => n.id.replace(/^module:/, ''))).join('、')})`)
|
|
2232
|
+
|
|
2233
|
+
const chlogDangling = graphChangelogDanglings(graph)
|
|
2234
|
+
if (chlogDangling.length > 0) findings.push(`graph-changelog-dangling:${chlogDangling.length} 条 changelog 行指向不存在变更/ql(样本 ${sample(chlogDangling).join('、')})`)
|
|
2235
|
+
|
|
2236
|
+
if (findings.length === 0) {
|
|
2237
|
+
return { ...base, findings: [`图完整性六检查通过(${graph.stats.nodeCount} 节点 / ${graph.stats.edgeCount} 边)`], pass: true, severity: null }
|
|
2238
|
+
}
|
|
2239
|
+
return { ...base, findings, pass: false, severity: CHECK_SEVERITY.WARNING }
|
|
2240
|
+
} catch (e) {
|
|
2241
|
+
return { ...base, findings: [`图构建降级(${e?.message || e})——skipped`], pass: true, severity: null, skipped: true }
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
// ESM 同步 require 桥:doctor 主体是同步管线,图模块按需加载(失败走上方 catch 的 skipped 分支)
|
|
2246
|
+
import { createRequire as _kgCreateRequire } from 'module'
|
|
2247
|
+
const _kgRequire = _kgCreateRequire(import.meta.url)
|
|
2248
|
+
function requireKnowledgeGraph() {
|
|
2249
|
+
return _kgRequire('./knowledge-graph.js')
|
|
2250
|
+
}
|
package/src/flow-parity.js
CHANGED
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
* 厚道有轻量变更缺的审计资产;机器合成勿手改。
|
|
11
11
|
* ③ harvestSlot4Decision:design 槽4(风险与死路)实质作答收割合成 decisions.md——轻量变更决策
|
|
12
12
|
* 产出为零的补口(死路与风险取舍正是 decisions.md 该记的内容;已有文件不覆盖)。
|
|
13
|
-
* ④ writeCloseTraceArtifacts / buildThinSnapshotRows
|
|
14
|
-
* (2026-10-
|
|
15
|
-
*
|
|
13
|
+
* ④ writeCloseTraceArtifacts / buildThinSnapshotRows:收口留痕单套写
|
|
14
|
+
* (2026-10-09-close-trace-single-set)——thin flow done 与 heavy execute --done 共用
|
|
15
|
+
* 一处只写 change.patch + change-patch.json 两件(对账面并入 scopeAudit 子对象;
|
|
16
|
+
* 此前 2026-10-07-unify-close-trace 的四件双轨形态收口为单套,旧名读侧兼容兜存量)。
|
|
16
17
|
*/
|
|
17
18
|
import { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs'
|
|
18
19
|
import { join, relative } from 'node:path'
|
|
@@ -358,21 +359,21 @@ export function projectTraceFaceRows(rows) {
|
|
|
358
359
|
}
|
|
359
360
|
|
|
360
361
|
/**
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
362
|
+
* 收口留痕单套写(2026-10-09-close-trace-single-set):thin(flow done)与 heavy
|
|
363
|
+
* (execute --done)统一只落 change.patch + change-patch.json 两件。原对账快照面
|
|
364
|
+
* (scope-audit.json + scope-audit.patch,2026-10-07-unify-close-trace 起与沉淀资产面
|
|
365
|
+
* 双轨并写)停写:对账数据并入 change-patch.json 的 scopeAudit 子对象,顶级键
|
|
366
|
+
* (change/baseline/head/files/totals/savedAt/note/moduleScope 等)原位不动——
|
|
367
|
+
* fr-index/knowledge-graph/flow 漂移检测与平台 assets.py/parser.py 等顶级字段读方零改动。
|
|
368
|
+
* patchText 一次 sha256 锚定 patch 与 patchStatus;patchText 空/null → failed、不落
|
|
369
|
+
* patch 文件。读侧兼容链(scopeAudit 子对象 > 旧 scope-audit.json > .runtime 快照 >
|
|
370
|
+
* thin 回放 > 实时区间)兜存量归档,旧名只读不写。
|
|
370
371
|
*
|
|
371
372
|
* @param {string} changeDir 变更目录(活跃或归档侧——归档竞态由调用方解析)
|
|
372
|
-
* @param {object} snapObj
|
|
373
|
-
* rows/excluded/note[/repos];closedBy
|
|
374
|
-
*
|
|
375
|
-
* @param {object} meta change-patch.json
|
|
373
|
+
* @param {object} snapObj scopeAudit 子对象主体(mode/ok/degradedReason/baseAnchor/
|
|
374
|
+
* totals/rows/excluded/note[/repos];closedBy 由调用方携带标收尾通道——读侧回放按它
|
|
375
|
+
* 标注,缺省 'execute --done' 兼容旧快照)
|
|
376
|
+
* @param {object} meta change-patch.json 顶级增量键(note/moduleScope 等,展开在 totals 后)
|
|
376
377
|
* @returns {{ patchStatus: 'ok'|'failed', patchSha256: string|null }}
|
|
377
378
|
*/
|
|
378
379
|
export function writeCloseTraceArtifacts({ changeDir, change, baseline, head, files, metaTotals, patchText, savedAt, snapObj, meta = {} }) {
|
|
@@ -382,10 +383,7 @@ export function writeCloseTraceArtifacts({ changeDir, change, baseline, head, fi
|
|
|
382
383
|
const patchSha256 = patchBody
|
|
383
384
|
? createHash('sha256').update(patchBody.replace(/\r\n/g, '\n'), 'utf8').digest('hex')
|
|
384
385
|
: null
|
|
385
|
-
if (patchOk)
|
|
386
|
-
writeFileSync(join(changeDir, 'change.patch'), patchBody)
|
|
387
|
-
writeFileSync(join(changeDir, 'scope-audit.patch'), patchBody)
|
|
388
|
-
}
|
|
386
|
+
if (patchOk) writeFileSync(join(changeDir, 'change.patch'), patchBody)
|
|
389
387
|
writeFileSync(join(changeDir, 'change-patch.json'), JSON.stringify({
|
|
390
388
|
change,
|
|
391
389
|
baseline: baseline ?? null,
|
|
@@ -396,15 +394,10 @@ export function writeCloseTraceArtifacts({ changeDir, change, baseline, head, fi
|
|
|
396
394
|
: { files: fileList.length, additions: 0, deletions: 0 },
|
|
397
395
|
savedAt,
|
|
398
396
|
...meta,
|
|
397
|
+
scopeAudit: { ...snapObj },
|
|
399
398
|
...(patchSha256 ? { patchSha256 } : {}),
|
|
400
399
|
patchStatus: patchOk ? 'ok' : 'failed',
|
|
401
400
|
}, null, 2) + '\n')
|
|
402
|
-
writeFileSync(join(changeDir, 'scope-audit.json'), JSON.stringify({
|
|
403
|
-
...snapObj,
|
|
404
|
-
...(patchSha256 ? { patchSha256 } : {}),
|
|
405
|
-
patchStatus: patchOk ? 'ok' : 'failed',
|
|
406
|
-
savedAt,
|
|
407
|
-
}, null, 2) + '\n')
|
|
408
401
|
return { patchStatus: patchOk ? 'ok' : 'failed', patchSha256 }
|
|
409
402
|
}
|
|
410
403
|
|
package/src/flow-review.js
CHANGED
|
@@ -244,6 +244,10 @@ export function renderReviewerTaskbook({ change, changeDir, head = null }) {
|
|
|
244
244
|
` 5. 生成物同步(如涉及):类型/契约文件与实现对齐?`,
|
|
245
245
|
``,
|
|
246
246
|
`【纪律】只读——禁止修改任何文件、禁止 git 操作;唯一产物是 review.json。`,
|
|
247
|
+
// 代行惯例(坑 flow-done-sentinel-token-split ②,2026-10-08 实证):宿主无已注册子代理
|
|
248
|
+
// 通道时无法照做「起子代理」——主会话按检查单代行自审可接受,reviewer 字段如实标注
|
|
249
|
+
// 降级(如 inline-self(环境无已注册子代理)),独立性承诺的折扣显式留痕。
|
|
250
|
+
`【无子代理通道时】宿主 agents 目录为空 → 主会话可按检查单代行自审,reviewer 字段如实标注(如 "inline-self(环境无已注册子代理,主会话代行)")——降级留痕,勿伪装 subagent。`,
|
|
247
251
|
``,
|
|
248
252
|
`【产物 schema(写到 ${join(changeDir, 'review.json')},UTF-8 JSON)】`,
|
|
249
253
|
`{`,
|
package/src/flow.js
CHANGED
|
@@ -286,7 +286,9 @@ export async function flowKnowledgeDigest({ specBase, change, changeDir, input,
|
|
|
286
286
|
}
|
|
287
287
|
const { matchKnowledgeHybrid } = await import('./knowledge-vector.js')
|
|
288
288
|
const { deathPathNote } = await import('./knowledge-match.js')
|
|
289
|
-
|
|
289
|
+
// scope 遍历召回(2026-10-08-knowledge-graph,FR-04):touched=机器算的图查询键
|
|
290
|
+
// (filesOverride ∪ design 交付表——上方既有变量),词面三层零命中时沿强边保底防复潮
|
|
291
|
+
const km = await matchKnowledgeHybrid(knowledgeRoot, `${change}\n${input || ''}`, { cwd: dirname(specBase), scopeFiles: touched })
|
|
290
292
|
// 防复潮面 = 死路注记 ∪ 有主题重叠的 rejected(2026-09-28-knowledge-inject-ranking 起 rejected∪死路;
|
|
291
293
|
// 2026-09-28-knowledge-gate-denoise 收紧:score 零的 rejected 与查询零主题重叠——空标题条目靠状态
|
|
292
294
|
// 蹭进回显是行为实测三例的噪音源,不再注入)
|
|
@@ -993,7 +995,10 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
993
995
|
if (_tickLog) {
|
|
994
996
|
const _commitTexts = String(_tickLog).split('\x1e').map(x => x.trim()).filter(Boolean).join('\n')
|
|
995
997
|
const _evidencedTasks = new Set()
|
|
996
|
-
|
|
998
|
+
// 连写组展开(坑 flow-done-sentinel-token-split):task-01/02/03 的 02/03 缩写不带
|
|
999
|
+
// task- 前缀、裸正则提取不到——与哨兵判定同口径先展开再提。
|
|
1000
|
+
const { expandTaskShorthand } = await import('./sentinel-assertions.js')
|
|
1001
|
+
for (const m of expandTaskShorthand([_commitTexts])[0].matchAll(/task-(\d{1,2})/gi)) _evidencedTasks.add(String(Number(m[1])).padStart(2, '0'))
|
|
997
1002
|
if (_evidencedTasks.size > 0) {
|
|
998
1003
|
let _tasksMd = readFileSync(_tasksMdPath, 'utf8')
|
|
999
1004
|
let _autoTicked = 0
|
|
@@ -1416,6 +1421,10 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1416
1421
|
console.warn(`⚠️ --accept-dirty-gap:显式接受 ${freeze.dirtyWarned.length} 个未提交交付文件不进冻结面(缺口数随 change-patch.json acceptedDirtyGap 留痕)`)
|
|
1417
1422
|
}
|
|
1418
1423
|
}
|
|
1424
|
+
// 收口留痕四名全排除(2026-10-09-close-trace-single-set 后只写 change.patch/
|
|
1425
|
+
// change-patch.json 两件,但旧轮冻结件里可能仍有 scope-audit.json/.patch——
|
|
1426
|
+
// legacy 防护保留):上一轮留痕件作为 untracked 新文件被全文自嵌入即自引用循环
|
|
1427
|
+
// (旧 patch 内容混进新 patch,三评 P1 根因)。
|
|
1419
1428
|
const ownFiles = [...new Set([...freeze.files, ...changeDirFiles])]
|
|
1420
1429
|
.filter((f) => f && !f.endsWith('change.patch') && !f.endsWith('change-patch.json')
|
|
1421
1430
|
&& !f.endsWith('scope-audit.json') && !f.endsWith('scope-audit.patch'))
|
|
@@ -1432,8 +1441,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1432
1441
|
const toPosix = (p) => String(p).replace(/\\/g, '/')
|
|
1433
1442
|
// 复审 P2/P3a 清偿:工作树口径集 = 治理工件目录(含已提交后又改的槽位/勾选——工作树
|
|
1434
1443
|
// diff 能捕捉提交后编辑)∪ 独占树未提交交付(dirtyAdded);其余交付面走提交区间。
|
|
1435
|
-
// 排除项与 ownFiles
|
|
1436
|
-
// untracked 新文件被全文自嵌入(自引用循环:旧 patch 内容混进新 patch,三评 P1 根因)。
|
|
1444
|
+
// 排除项与 ownFiles 同款(四名全排,含 scope-audit.* 旧轮冻结件 legacy 防护)。
|
|
1437
1445
|
const dirFilesForPatch = changeDirFiles.filter((f) => !f.endsWith('change.patch') && !f.endsWith('change-patch.json')
|
|
1438
1446
|
&& !f.endsWith('scope-audit.json') && !f.endsWith('scope-audit.patch'))
|
|
1439
1447
|
const worktreeSet = new Set([...dirFilesForPatch, ...((freeze.dirtyAdded) || [])].map(toPosix))
|
|
@@ -1464,10 +1472,9 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1464
1472
|
if (rec.lines.length > 0) for (const l of rec.lines) console.log(l)
|
|
1465
1473
|
moduleScope = { modules: rec.modules, uncoveredDirs: rec.uncoveredDirs, moduleMaps: rec.moduleMaps }
|
|
1466
1474
|
} catch { /* 对账 best-effort(三键空数组兜底) */ }
|
|
1467
|
-
//
|
|
1468
|
-
//
|
|
1469
|
-
//
|
|
1470
|
-
// (同一 patchText 一次 sha256),新变更两条通道留痕对称,读侧回退退化为存量兜底。
|
|
1475
|
+
// 收口留痕单套写(2026-10-09-close-trace-single-set):共用 writeCloseTraceArtifacts
|
|
1476
|
+
// 只写 change.patch + change-patch.json 两件——对账面数据并入 JSON 的 scopeAudit
|
|
1477
|
+
// 子对象(thin 通道同样有对账面),旧名 scope-audit.* 停写、读侧兼容兜存量归档。
|
|
1471
1478
|
let planEntries = []
|
|
1472
1479
|
try {
|
|
1473
1480
|
const { parseFileChangeListDetailed } = await import('./change-list.js')
|
|
@@ -1503,7 +1510,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1503
1510
|
...(acceptDirtyGap && freeze.dirtyWarned.length > 0 ? { acceptedDirtyGap: freeze.dirtyWarned.length } : {}),
|
|
1504
1511
|
},
|
|
1505
1512
|
})
|
|
1506
|
-
console.log(`📦 变更 patch
|
|
1513
|
+
console.log(`📦 变更 patch 留档(单套):change.patch + change-patch.json(${ownFiles.length} 文件,+${additions}/-${deletions},对账面并入 scopeAudit 子对象)${closeTrace.patchStatus === 'ok' ? ',sha256 已锚' : '——patch 采集失败已留痕'}`)
|
|
1507
1514
|
// design 声明面自证(2026-09-25-platform-feedback-batch2 E):design.md 文件变更清单
|
|
1508
1515
|
// 声明的交付文件是否都在冻结面——不在=承诺改了但没交付(承诺未兑现面,advisory 不
|
|
1509
1516
|
// 阻断——可能是范围裁剪了但 design 没同步更新)
|
|
@@ -1679,7 +1686,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1679
1686
|
try {
|
|
1680
1687
|
const { harvestSlot4Decision } = await import('./flow-parity.js')
|
|
1681
1688
|
const h = harvestSlot4Decision({ changeDir, change })
|
|
1682
|
-
if (h.harvested) console.log(`🌱 槽4(风险与死路)收割 → decisions.md
|
|
1689
|
+
if (h.harvested) console.log(`🌱 槽4(风险与死路)收割 → decisions.md(底稿——作答里的放弃方案请拆成 rejected 条目再收口;随蒸馏链进 knowledge)`)
|
|
1683
1690
|
} catch { /* 收割 best-effort */ }
|
|
1684
1691
|
try {
|
|
1685
1692
|
const { distillIntoKnowledge } = await import('./decision-distill.js')
|
package/src/friction-tally.js
CHANGED
|
@@ -30,7 +30,7 @@ import { withFileLock } from './quicklog.js'
|
|
|
30
30
|
import { resolveRuntimeRoot, resolveQuickSessionsDir } from './run/shared.js'
|
|
31
31
|
|
|
32
32
|
/** 摩擦事件类型枚举(D-004):审查打回走专属 review_rejected,不与 gate_rollback 重复计 */
|
|
33
|
-
const FRICTION_TYPES = new Set(['gate_rollback', 'verify_run_failed', 'review_rejected'])
|
|
33
|
+
const FRICTION_TYPES = new Set(['gate_rollback', 'verify_run_failed', 'review_rejected', 'gate_snapshot_fallback'])
|
|
34
34
|
|
|
35
35
|
/** history 截尾上限(同 verify-lint-tally 的 20 条口径——台账防病态体量,不承载完整审计) */
|
|
36
36
|
const FRICTION_HISTORY_CAP = 20
|
|
@@ -43,6 +43,7 @@ const FRICTION_LABELS = [
|
|
|
43
43
|
['gate_rollback', 'gate 回滚'],
|
|
44
44
|
['verify_run_failed', '验证失败'],
|
|
45
45
|
['review_rejected', '审查打回'],
|
|
46
|
+
['gate_snapshot_fallback', '快照回退主仓'],
|
|
46
47
|
]
|
|
47
48
|
|
|
48
49
|
/**
|
package/src/git-helper.js
CHANGED
|
@@ -187,10 +187,32 @@ export function stageArchiveArtifacts(cwd) {
|
|
|
187
187
|
const skipped = []
|
|
188
188
|
const existing = candidates.filter(p => existsSync(p))
|
|
189
189
|
if (existing.length === 0) return { staged, skipped }
|
|
190
|
-
|
|
190
|
+
// 2026-10-09-verify-papercuts ④:排除嵌套运行时异物——归档变更目录内嵌套的 .sillyspec/
|
|
191
|
+
// (历史 cwd 漂移事故的遗留 db/日志)会被目录级 add 一并扫进暂存(当日实证:他变更的
|
|
192
|
+
// sillyspec.db 与 spec-sync-bg.log 被夹带提交)。pathspec exclude 精确拦截该形态,正常
|
|
193
|
+
// 归档产物(md/json/patch)零影响;检出异物在场时点名提示清理(不静默吞)。
|
|
194
|
+
const excludes = [
|
|
195
|
+
':(exclude).sillyspec/changes/archive/*/.sillyspec',
|
|
196
|
+
':(exclude).sillyspec/changes/archive/*/.sillyspec/**',
|
|
197
|
+
]
|
|
198
|
+
let nestedJunk = []
|
|
199
|
+
try {
|
|
200
|
+
const archRoot = join(specBase, 'changes', 'archive')
|
|
201
|
+
for (const e of readdirSync(archRoot, { withFileTypes: true })) {
|
|
202
|
+
if (e.isDirectory() && existsSync(join(archRoot, e.name, '.sillyspec'))) {
|
|
203
|
+
nestedJunk.push(join(archRoot, e.name, '.sillyspec'))
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch { /* 归档目录不可读 → 跳过提示 */ }
|
|
207
|
+
const r = safeGit(cwd, ['add', ...existing, ...excludes])
|
|
191
208
|
if (r && r.error) {
|
|
192
209
|
console.warn(` ⚠️ 归档产物 git add 失败(可手动处理):${String(r.error).slice(0, 120)}`)
|
|
193
210
|
return { staged, skipped: existing }
|
|
194
211
|
}
|
|
212
|
+
if (nestedJunk.length > 0) {
|
|
213
|
+
console.warn(` ⚠️ 检出 ${nestedJunk.length} 个归档目录内嵌套 .sillyspec/ 运行时异物(已从本次暂存排除):`)
|
|
214
|
+
for (const j of nestedJunk.slice(0, 5)) console.warn(` - ${j}`)
|
|
215
|
+
console.warn(` 处置:确认无归属后删除目录;若已被 git 跟踪先 git rm -r --cached <路径>(不该入库的 db/日志)`)
|
|
216
|
+
}
|
|
195
217
|
return { staged: existing, skipped }
|
|
196
218
|
}
|
package/src/index.js
CHANGED
|
@@ -1334,7 +1334,7 @@ task done 四合一(r5l 方案1):review write(落 review.json+自动勾
|
|
|
1334
1334
|
process.exit(2);
|
|
1335
1335
|
}
|
|
1336
1336
|
assertSafeChangeName(vpChange, '--change 变更名');
|
|
1337
|
-
const { runVerifyProbes, renderVerifyProbesReport, generateVerifyResultSkeleton, resolveVerifyProbesSpecBase, writeVerifyFacts, formatPlatformPathNote, backupVerifyResult, refreshProbeSections } = await import('./verify-probes.js');
|
|
1337
|
+
const { runVerifyProbes, renderVerifyProbesReport, generateVerifyResultSkeleton, resolveVerifyProbesSpecBase, writeVerifyFacts, formatPlatformPathNote, backupVerifyResult, refreshProbeSections, applySkeletonPreservingHuman } = await import('./verify-probes.js');
|
|
1338
1338
|
// spec 根统一走漂移锚定(坑 worktree-spec-artifact-misplace):在 worktree 内跑时锚回主仓,
|
|
1339
1339
|
// 探针读取与 --init 骨架都落主仓——与 plan/execute/verify/archive 的 command.js 守卫同口径。
|
|
1340
1340
|
// resolvePlatformSpecDir 仍先调(保留平台接管 fail-closed 检查副作用),但仅 pointer 存在时
|
|
@@ -1429,21 +1429,29 @@ task done 四合一(r5l 方案1):review write(落 review.json+自动勾
|
|
|
1429
1429
|
await mirrorInitArtifact(vpReportPath, 'verify-result.md', vpInjected);
|
|
1430
1430
|
console.log(`\n📄 存量旧格式 verify-result.md 已补注入探针预填段: ${vpReportPath}(正文其余未动;补注后按新预填段如实核对结论)${vpPlatformNote}`);
|
|
1431
1431
|
} else if (vpForce) {
|
|
1432
|
-
// quick-B:--force 覆盖重生成全骨架(含接口矩阵等 CLI
|
|
1432
|
+
// quick-B:--force 覆盖重生成全骨架(含接口矩阵等 CLI 预填段)。
|
|
1433
1433
|
// 2026-10-06-verify-friction-fix:覆盖前自动备份(postmortem 实证 38 格手填复核成果
|
|
1434
1434
|
// 被清后只能靠对话记录重建——「从 git 或备份找回」的话术备份此前并不存在);备份
|
|
1435
1435
|
// 失败时醒目提示手动复制再重跑(fail-soft 但不静默)。
|
|
1436
|
+
// 2026-10-09-verify-papercuts ②:语义升级——机器段全量重刷 + 人工面按段携载
|
|
1437
|
+
// (applySkeletonPreservingHuman:结论槽/移交项表/矩阵已填行不再被 wipe),备份
|
|
1438
|
+
// 保留为兜底通道。
|
|
1436
1439
|
const { resolveRuntimeRoot: vpForceRTR } = await import('./run/shared.js');
|
|
1437
1440
|
const vpBackupPath = backupVerifyResult({ mdPath: vpReportPath, runtimeRoot: vpForceRTR({}, vpSpecBase), changeName: vpChange });
|
|
1438
1441
|
const vpSkeleton = generateVerifyResultSkeleton(vpResult);
|
|
1439
|
-
|
|
1440
|
-
|
|
1442
|
+
const vpOldText = readFileSync(vpReportPath, 'utf8');
|
|
1443
|
+
const vpApplied = applySkeletonPreservingHuman(vpOldText, vpSkeleton);
|
|
1444
|
+
writeFileSync(vpReportPath, vpApplied.text);
|
|
1445
|
+
await mirrorInitArtifact(vpReportPath, 'verify-result.md', vpApplied.text);
|
|
1441
1446
|
if (vpBackupPath) {
|
|
1442
|
-
console.log(`\n🗄️ --force 覆盖前已自动备份: ${vpBackupPath}
|
|
1447
|
+
console.log(`\n🗄️ --force 覆盖前已自动备份: ${vpBackupPath}(携载语义之外的极端形态从这里找回)`);
|
|
1443
1448
|
} else {
|
|
1444
1449
|
console.error(`\n⚠️ 自动备份失败——若 verify-result.md 含手填内容,请立即手动复制保存后重跑本命令。`);
|
|
1445
1450
|
}
|
|
1446
|
-
|
|
1451
|
+
const vpCarriedNote = vpApplied.carriedSections.length > 0
|
|
1452
|
+
? `已填人工面按段携载保留:${vpApplied.carriedSections.join('、')}(携载表格行 ${vpApplied.carriedRows})——与机器段新渲染不符的携载内容由 gate 一致性抽查把关`
|
|
1453
|
+
: `未发现已填人工面(全文占位形态——行为与旧版全量重置一致)`;
|
|
1454
|
+
console.log(`\n📄 --force 已重生成 verify-result.md 骨架: ${vpReportPath}(探针与预填段已刷新;${vpCarriedNote})${vpPlatformNote}`);
|
|
1447
1455
|
} else {
|
|
1448
1456
|
console.log(`\nℹ️ verify-result.md 已存在,不覆盖: ${vpReportPath}${vpPlatformNote}(预填段过期需刷新时用 --init --force)`);
|
|
1449
1457
|
}
|
|
@@ -2172,9 +2180,10 @@ task done 四合一(r5l 方案1):review write(落 review.json+自动勾
|
|
|
2172
2180
|
const fpSpecBase = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
|
|
2173
2181
|
const fpDir = join(fpSpecBase, 'changes', fpChange);
|
|
2174
2182
|
mkdirSync(fpDir, { recursive: true });
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
const
|
|
2183
|
+
// 本地墙钟(datetime.js 坑 taskcard-created-at-utc 同族:frontmatter 人读时间禁
|
|
2184
|
+
// toISOString 写 UTC 裸形状——无时区标记被读取端按本地解析,2026-10-09 平台时间线 born 锚偏 8h 实证)
|
|
2185
|
+
const { nowWallClock } = await import('./datetime.js');
|
|
2186
|
+
const fpStamp = nowWallClock();
|
|
2178
2187
|
const generated = [];
|
|
2179
2188
|
const mk = (fname, body) => {
|
|
2180
2189
|
const p = join(fpDir, fname);
|
|
@@ -3373,8 +3382,11 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
3373
3382
|
else if (wtCommitFlags[i] === '--pathspec-from-file' && wtCommitFlags[i + 1]) wtCommitPathspecFile = wtCommitFlags[++i];
|
|
3374
3383
|
}
|
|
3375
3384
|
if (!wtCommitChange) {
|
|
3376
|
-
|
|
3377
|
-
|
|
3385
|
+
// FR-08(2026-10-09-verify-reuse-friction / D-006@v1):跨仓 <change>--<repoKey>
|
|
3386
|
+
// 注册表校验剥后缀——逻辑单点在 wt-commit.js(导出 inferChangeFromWorktreeCwd
|
|
3387
|
+
// 供三态回归直测,index.js dispatch 不留内联逻辑)
|
|
3388
|
+
const { inferChangeFromWorktreeCwd } = await import('./wt-commit.js')
|
|
3389
|
+
wtCommitChange = await inferChangeFromWorktreeCwd(dir)
|
|
3378
3390
|
}
|
|
3379
3391
|
try {
|
|
3380
3392
|
const { runWtCommit } = await import('./wt-commit.js');
|
package/src/init.js
CHANGED
|
@@ -270,11 +270,12 @@ export function injectClaudePointer(projectDir) {
|
|
|
270
270
|
|
|
271
271
|
// ── 检测工具 ──
|
|
272
272
|
|
|
273
|
-
function detectTools(projectDir) {
|
|
273
|
+
export function detectTools(projectDir) {
|
|
274
274
|
const found = [];
|
|
275
275
|
if (existsSync(join(projectDir, '.claude'))) found.push('claude');
|
|
276
276
|
if (existsSync(join(projectDir, '.cursor'))) found.push('cursor');
|
|
277
277
|
if (existsSync(join(projectDir, '.openclaw'))) found.push('openclaw');
|
|
278
|
+
if (existsSync(join(projectDir, '.zcode'))) found.push('zcode');
|
|
278
279
|
if (existsSync(join(projectDir, 'AGENTS.md'))) found.push('codex');
|
|
279
280
|
if (existsSync(join(projectDir, 'GEMINI.md'))) found.push('gemini');
|
|
280
281
|
if (existsSync(join(projectDir, 'INSTRUCTIONS.md'))) found.push('opencode');
|
|
@@ -480,6 +481,9 @@ async function doInstall(projectDir, tools, subprojects = [], specDir = null, op
|
|
|
480
481
|
codex: '.codex/skills',
|
|
481
482
|
openclaw: '.openclaw/skills',
|
|
482
483
|
opencode: '.opencode/skills',
|
|
484
|
+
// zcode(坑 init-skills-sync-no-zcode,2026-10-08 实证:命令卡面有落点、技能复制面漏——
|
|
485
|
+
// .zcode/skills 已在场时 detectTools 不发现 + 显式 --tool zcode 也拿不到技能,双层缺口)
|
|
486
|
+
zcode: '.zcode/skills',
|
|
483
487
|
}
|
|
484
488
|
const skillsSource = join(__dirname, '..', '.claude', 'skills');
|
|
485
489
|
if (existsSync(skillsSource)) {
|