sillyspec 3.32.0 → 3.32.2
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/package.json +2 -2
- package/src/doctor-diagnostics.js +84 -4
- package/src/flow-parity.js +137 -1
- package/src/flow-review.js +4 -0
- package/src/flow.js +67 -25
- package/src/foreign-declared.js +7 -3
- package/src/init.js +5 -1
- package/src/knowledge-graph.js +918 -0
- package/src/knowledge-match.js +11 -6
- package/src/knowledge-stats.js +39 -0
- package/src/knowledge-vector.js +17 -3
- package/src/run/complete.js +38 -13
- package/src/scope-audit.js +129 -3
- package/src/sentinel-assertions.js +27 -2
- package/src/stages/explore.js +5 -1
- package/src/stages/knowledge.js +11 -1
- package/src/verify-postcheck.js +11 -3
- package/src/watcher.js +6 -2
|
@@ -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
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sillyspec",
|
|
3
|
-
"version": "3.32.
|
|
3
|
+
"version": "3.32.2",
|
|
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",
|
|
@@ -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,15 +10,21 @@
|
|
|
10
10
|
* 厚道有轻量变更缺的审计资产;机器合成勿手改。
|
|
11
11
|
* ③ harvestSlot4Decision:design 槽4(风险与死路)实质作答收割合成 decisions.md——轻量变更决策
|
|
12
12
|
* 产出为零的补口(死路与风险取舍正是 decisions.md 该记的内容;已有文件不覆盖)。
|
|
13
|
+
* ④ writeCloseTraceArtifacts / buildThinSnapshotRows:双通道收尾留痕统一写
|
|
14
|
+
* (2026-10-07-unify-close-trace)——thin flow done 与 heavy execute --done 共用一处一次
|
|
15
|
+
* 写齐四件(沉淀资产面 + 对账快照面,sha256 双套同锚),新变更两通道留痕对称。
|
|
13
16
|
*/
|
|
14
17
|
import { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs'
|
|
15
18
|
import { join, relative } from 'node:path'
|
|
19
|
+
import { createHash } from 'node:crypto'
|
|
16
20
|
import { execFileSync } from 'node:child_process'
|
|
17
21
|
import { splitOwnVsForeignDiffFiles, commitAttributionForChange, isForeignByCommit, parseChangeNamesFromSubject } from './foreign-declared.js'
|
|
18
22
|
import { collectModuleMaps, normalizeMapPath } from './module-resolve.js'
|
|
19
23
|
import { writeAtomicSync } from './fs-atomic.js'
|
|
20
24
|
import { readV2SectionAnswer } from './flow-review.js'
|
|
21
25
|
import { DESIGN_QUESTIONS } from './flow-draft.js'
|
|
26
|
+
import { pathMatches } from './change-list.js'
|
|
27
|
+
import { classifyToolScaffold, filterDeliverableFiles } from './worktree-apply.js'
|
|
22
28
|
|
|
23
29
|
/**
|
|
24
30
|
* 模块文档对账(advisory)+ 结构化落盘面(2026-09-27-thin-module-scope-persist)。
|
|
@@ -252,7 +258,11 @@ export function collectFreezeFiles({ cwd, specBase, change, committed, exclusive
|
|
|
252
258
|
const deliverable = (f) => !String(f).replace(/\\/g, '/').startsWith('.sillyspec/')
|
|
253
259
|
let dirty = []
|
|
254
260
|
try {
|
|
255
|
-
|
|
261
|
+
// -uall(2026-10-08-thin-done-dirty-gate-and-paren-attribution):全新目录会折叠成
|
|
262
|
+
// `dir/` token(porcelain 缺省形态)——readFileSync(目录) 静默 skip,--freeze-dirty
|
|
263
|
+
// 声称并入却漏目录内交付文件(dg-freeze 夹具实证 src/ 折叠)。逐文件展开与
|
|
264
|
+
// scope-audit/flow.js 状态采集口径对齐。
|
|
265
|
+
const out = execFileSync('git', ['status', '--porcelain', '--untracked-files=all'], { cwd, encoding: 'utf8', timeout: 30000, windowsHide: true })
|
|
256
266
|
for (const line of String(out).split('\n')) {
|
|
257
267
|
if (!line || line.length < 4) continue
|
|
258
268
|
const p = line.slice(3).trim().replace(/^"|"$/g, '')
|
|
@@ -272,6 +282,132 @@ export function collectFreezeFiles({ cwd, specBase, change, committed, exclusive
|
|
|
272
282
|
return { files: [...committed], dirtyAdded: [], dirtyWarned: ownDirty }
|
|
273
283
|
}
|
|
274
284
|
|
|
285
|
+
/**
|
|
286
|
+
* thin 通道三态行构造(writeCloseTraceArtifacts 的 snapObj.rows 数据源,纯函数):
|
|
287
|
+
* ownFiles 过 filterDeliverableFiles(与 scope-audit actual 侧同口径——治理工件目录不进
|
|
288
|
+
* 快照行)× design 清单 pathMatches 判三态(planned/unplanned;与主链路三态判定同款);
|
|
289
|
+
* stats(collectNumstatByPath 产物 Map)缺档落 null 行数(不出伪数据);planEntries 空/缺省
|
|
290
|
+
* → 实际侧 only 行(无 verdict,与主链路计划侧降级同语义)。清单条目未命中冻结面 →
|
|
291
|
+
* untouched 0/0 补行(跨仓条目带 crossRepo 保持 ⊘ 形态,主链路补行同语义)。
|
|
292
|
+
* @returns {{ rows: Array<object>, totals: { files: number, additions: number, deletions: number } }}
|
|
293
|
+
*/
|
|
294
|
+
export function buildThinSnapshotRows({ ownFiles, stats, planEntries }) {
|
|
295
|
+
const plan = Array.isArray(planEntries) ? planEntries : []
|
|
296
|
+
const statMap = stats instanceof Map ? stats : new Map()
|
|
297
|
+
const rowFiles = filterDeliverableFiles([...new Set((Array.isArray(ownFiles) ? ownFiles : [])
|
|
298
|
+
.map((f) => String(f).replace(/\\/g, '/')).filter(Boolean))]).sort()
|
|
299
|
+
const rows = []
|
|
300
|
+
const matched = new Set()
|
|
301
|
+
for (const f of rowFiles) {
|
|
302
|
+
const st = statMap.get(f) || {}
|
|
303
|
+
const row = {
|
|
304
|
+
path: f,
|
|
305
|
+
additions: Number.isFinite(st.additions) ? st.additions : null,
|
|
306
|
+
deletions: Number.isFinite(st.deletions) ? st.deletions : null,
|
|
307
|
+
kind: st.kind || 'modified',
|
|
308
|
+
}
|
|
309
|
+
if (plan.length > 0) {
|
|
310
|
+
const entry = plan.find((e) => e && pathMatches(f, e.path))
|
|
311
|
+
if (entry) {
|
|
312
|
+
matched.add(entry.path)
|
|
313
|
+
row.planned = entry.operation || null
|
|
314
|
+
row.verdict = 'planned'
|
|
315
|
+
} else {
|
|
316
|
+
row.planned = null
|
|
317
|
+
row.verdict = 'unplanned'
|
|
318
|
+
const facility = classifyToolScaffold(f)
|
|
319
|
+
if (facility) row.facility = facility
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
rows.push(row)
|
|
323
|
+
}
|
|
324
|
+
for (const e of plan) {
|
|
325
|
+
if (!e || matched.has(e.path)) continue
|
|
326
|
+
rows.push({
|
|
327
|
+
path: e.path, planned: e.operation || null, additions: 0, deletions: 0,
|
|
328
|
+
kind: 'modified', verdict: 'untouched',
|
|
329
|
+
...(e.repo ? { crossRepo: e.repo } : {}),
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
let additions = 0
|
|
333
|
+
let deletions = 0
|
|
334
|
+
for (const r of rows) {
|
|
335
|
+
if (Number.isFinite(r.additions)) additions += r.additions
|
|
336
|
+
if (Number.isFinite(r.deletions)) deletions += r.deletions
|
|
337
|
+
}
|
|
338
|
+
return { rows, totals: { files: rows.length, additions, deletions } }
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* heavy 通道沉淀资产面投影(writeCloseTraceArtifacts 的 files/totals 数据源,纯函数;
|
|
343
|
+
* 2026-10-07-unify-close-trace,评审 P3 覆盖清偿抽纯函数):rows → 主仓实改行——
|
|
344
|
+
* untouched 0/0 是声明不是改动、crossRepo 行 patch 不在主仓,均不入;verdict 缺失行
|
|
345
|
+
* (计划侧降级形态)按实改保留;null 行数不计合计(不出伪数据)。
|
|
346
|
+
* @returns {{ rows: object[], files: string[], totals: { files: number, additions: number, deletions: number } }}
|
|
347
|
+
*/
|
|
348
|
+
export function projectTraceFaceRows(rows) {
|
|
349
|
+
const face = (Array.isArray(rows) ? rows : [])
|
|
350
|
+
.filter((r) => r && typeof r === 'object' && !r.crossRepo && r.verdict !== 'untouched')
|
|
351
|
+
let additions = 0
|
|
352
|
+
let deletions = 0
|
|
353
|
+
for (const r of face) {
|
|
354
|
+
if (Number.isFinite(r.additions)) additions += r.additions
|
|
355
|
+
if (Number.isFinite(r.deletions)) deletions += r.deletions
|
|
356
|
+
}
|
|
357
|
+
return { rows: face, files: face.map((r) => r.path), totals: { files: face.length, additions, deletions } }
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* 双通道收尾留痕统一写(2026-10-07-unify-close-trace,缺陷 thin-flow-done-no-scope-
|
|
362
|
+
* audit-snapshot 修复方向③):thin(flow done)与 heavy(execute --done)此前各写各的
|
|
363
|
+
* 留痕(沉淀资产面 change.patch/change-patch.json vs 对账快照面 scope-audit.patch/
|
|
364
|
+
* scope-audit.json),消费方各认一份致每类变更恰好一张卡失真——本函数一处写齐四件,
|
|
365
|
+
* sha256/patchStatus 双套同锚(同一 patchText 一次计算:change.patch ≡ scope-audit.patch
|
|
366
|
+
* 字节一致、两 json 同 hash,两套留痕不可能漂移)。patchText 空/null → 双套同标 failed、
|
|
367
|
+
* 不落 patch 文件(heavy 旧「空串当 ok」形态收口)。读侧兼容链(快照 > change-patch
|
|
368
|
+
* 回放 > 实时区间,2026-10-07-scope-audit-thin-patch-replay)不动——本函数让新变更两套
|
|
369
|
+
* 齐备,回退退化为存量归档兜底。
|
|
370
|
+
*
|
|
371
|
+
* @param {string} changeDir 变更目录(活跃或归档侧——归档竞态由调用方解析)
|
|
372
|
+
* @param {object} snapObj scope-audit.json 主体(mode/ok/degradedReason/baseAnchor/totals/
|
|
373
|
+
* rows/excluded/note[/repos];closedBy 由调用方携带标收尾通道——读侧回放按它标注,
|
|
374
|
+
* 缺省 'execute --done' 兼容旧快照)
|
|
375
|
+
* @param {object} meta change-patch.json 增量键(note/moduleScope 等,展开在 totals 后)
|
|
376
|
+
* @returns {{ patchStatus: 'ok'|'failed', patchSha256: string|null }}
|
|
377
|
+
*/
|
|
378
|
+
export function writeCloseTraceArtifacts({ changeDir, change, baseline, head, files, metaTotals, patchText, savedAt, snapObj, meta = {} }) {
|
|
379
|
+
const fileList = Array.isArray(files) ? files : []
|
|
380
|
+
const patchOk = typeof patchText === 'string' && patchText.length > 0
|
|
381
|
+
const patchBody = patchOk ? (patchText.endsWith('\n') ? patchText : patchText + '\n') : null
|
|
382
|
+
const patchSha256 = patchBody
|
|
383
|
+
? createHash('sha256').update(patchBody.replace(/\r\n/g, '\n'), 'utf8').digest('hex')
|
|
384
|
+
: null
|
|
385
|
+
if (patchOk) {
|
|
386
|
+
writeFileSync(join(changeDir, 'change.patch'), patchBody)
|
|
387
|
+
writeFileSync(join(changeDir, 'scope-audit.patch'), patchBody)
|
|
388
|
+
}
|
|
389
|
+
writeFileSync(join(changeDir, 'change-patch.json'), JSON.stringify({
|
|
390
|
+
change,
|
|
391
|
+
baseline: baseline ?? null,
|
|
392
|
+
head: head ?? null,
|
|
393
|
+
files: fileList,
|
|
394
|
+
totals: metaTotals && typeof metaTotals === 'object'
|
|
395
|
+
? metaTotals
|
|
396
|
+
: { files: fileList.length, additions: 0, deletions: 0 },
|
|
397
|
+
savedAt,
|
|
398
|
+
...meta,
|
|
399
|
+
...(patchSha256 ? { patchSha256 } : {}),
|
|
400
|
+
patchStatus: patchOk ? 'ok' : 'failed',
|
|
401
|
+
}, 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
|
+
return { patchStatus: patchOk ? 'ok' : 'failed', patchSha256 }
|
|
409
|
+
}
|
|
410
|
+
|
|
275
411
|
/**
|
|
276
412
|
* 实测面回填(修复③:ledger 子步断点续跑 skip 后回执不失忆)——从 verify-runs 最近
|
|
277
413
|
* test-result.json(带 change 键)重建摘要;无匹配返回 null。
|
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
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
* 幂等循 task-done 先例:子步各查自身完成标记,中断半态重入断点续,中段失败精确报告。
|
|
27
27
|
*/
|
|
28
28
|
import { existsSync, readFileSync, writeFileSync, readdirSync, appendFileSync, renameSync, statSync } from 'node:fs'
|
|
29
|
-
import { createHash } from 'node:crypto'
|
|
30
29
|
import { join, relative, dirname } from 'node:path'
|
|
31
30
|
import yaml from 'js-yaml'
|
|
32
31
|
import { git, gitQuiet } from './git-helper.js'
|
|
@@ -287,7 +286,9 @@ export async function flowKnowledgeDigest({ specBase, change, changeDir, input,
|
|
|
287
286
|
}
|
|
288
287
|
const { matchKnowledgeHybrid } = await import('./knowledge-vector.js')
|
|
289
288
|
const { deathPathNote } = await import('./knowledge-match.js')
|
|
290
|
-
|
|
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 })
|
|
291
292
|
// 防复潮面 = 死路注记 ∪ 有主题重叠的 rejected(2026-09-28-knowledge-inject-ranking 起 rejected∪死路;
|
|
292
293
|
// 2026-09-28-knowledge-gate-denoise 收紧:score 零的 rejected 与查询零主题重叠——空标题条目靠状态
|
|
293
294
|
// 蹭进回显是行为实测三例的噪音源,不再注入)
|
|
@@ -863,7 +864,7 @@ export function remainingSubstepsAtFail({ snapshotSubsteps = {}, doneList = [],
|
|
|
863
864
|
return SUBSTEPS.filter((k) => k !== failed && snapshotSubsteps?.[k] !== 'done' && !doneNow.has(k))
|
|
864
865
|
}
|
|
865
866
|
|
|
866
|
-
export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null, confirmArchive = true, freezeDirty = false, allowBatchTick = false }) {
|
|
867
|
+
export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null, confirmArchive = true, freezeDirty = false, acceptDirtyGap = false, allowBatchTick = false }) {
|
|
867
868
|
const changeDir = join(specBase, 'changes', change)
|
|
868
869
|
const st = readFlowState(changeDir)
|
|
869
870
|
if (!st) {
|
|
@@ -994,7 +995,10 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
994
995
|
if (_tickLog) {
|
|
995
996
|
const _commitTexts = String(_tickLog).split('\x1e').map(x => x.trim()).filter(Boolean).join('\n')
|
|
996
997
|
const _evidencedTasks = new Set()
|
|
997
|
-
|
|
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'))
|
|
998
1002
|
if (_evidencedTasks.size > 0) {
|
|
999
1003
|
let _tasksMd = readFileSync(_tasksMdPath, 'utf8')
|
|
1000
1004
|
let _autoTicked = 0
|
|
@@ -1396,11 +1400,30 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1396
1400
|
}
|
|
1397
1401
|
if (freeze.dirtyWarned.length > 0) {
|
|
1398
1402
|
console.warn(`⚠️ ${freeze.dirtyWarned.length} 个未提交交付文件未入冻结面(共享主仓无法归属)——三选一:`)
|
|
1399
|
-
console.warn(` ①
|
|
1403
|
+
console.warn(` ① 先提交这些文件(信息尾缀带变更名)后重跑 ② 确认全部归属本变更:重跑 flow done --freeze-dirty 并入冻结 ③ 确认接受审计缺口:重跑 flow done --accept-dirty-gap(缺口数随 change-patch.json 留痕)。Git 中间提交可归档后压扁(见收口指引)`)
|
|
1400
1404
|
console.warn(` ${freeze.dirtyWarned.slice(0, 3).join('、')}${freeze.dirtyWarned.length > 3 ? ' 等' : ''}`)
|
|
1405
|
+
// 收口阻断门(2026-10-08-thin-done-dirty-gate-and-paren-attribution,坑①修复:
|
|
1406
|
+
// multi-agent-platform docs/sillyspec/thin-done-src-commit-order-and-attribution-paren.md)
|
|
1407
|
+
// ——旧行为警告后继续归档,autopilot 单跑到底把三选一变成不可达选项(归档即拒收重入)。
|
|
1408
|
+
// 缺口在场且未显式处置 → 停在 patch 子步标记之前(半态可重入,与 review 中断同款
|
|
1409
|
+
// exit 语义):处置后重跑,patch 全量重建自动并入已提交 src。
|
|
1410
|
+
if (!freezeDirty && !acceptDirtyGap) {
|
|
1411
|
+
console.error('⛔ 未提交交付缺口未显式处置——收口阻断(不归档;处置后重跑同一条命令从断点续):')
|
|
1412
|
+
console.error(` ① git commit 这些文件(信息尾缀带 (${change}) 或 (${change}),重跑本命令——冻结面自动并入`)
|
|
1413
|
+
console.error(` ② 全部归属本变更:flow done --change ${change} --freeze-dirty`)
|
|
1414
|
+
console.error(` ③ 接受审计缺口:flow done --change ${change} --accept-dirty-gap`)
|
|
1415
|
+
reportMidFail('patch')
|
|
1416
|
+
process.exit(1)
|
|
1417
|
+
}
|
|
1418
|
+
if (acceptDirtyGap) {
|
|
1419
|
+
// 双 flag 同给时 --freeze-dirty 优先(exclusive 路径 dirtyWarned 恒空,本分支不进
|
|
1420
|
+
// ——设计接口契约已披露;无告警代码即无死代码,二轮评审 P3 清偿)
|
|
1421
|
+
console.warn(`⚠️ --accept-dirty-gap:显式接受 ${freeze.dirtyWarned.length} 个未提交交付文件不进冻结面(缺口数随 change-patch.json acceptedDirtyGap 留痕)`)
|
|
1422
|
+
}
|
|
1401
1423
|
}
|
|
1402
1424
|
const ownFiles = [...new Set([...freeze.files, ...changeDirFiles])]
|
|
1403
|
-
.filter((f) => f && !f.endsWith('change.patch') && !f.endsWith('change-patch.json')
|
|
1425
|
+
.filter((f) => f && !f.endsWith('change.patch') && !f.endsWith('change-patch.json')
|
|
1426
|
+
&& !f.endsWith('scope-audit.json') && !f.endsWith('scope-audit.patch'))
|
|
1404
1427
|
if (ownFiles.length === 0) {
|
|
1405
1428
|
console.log('📦 patch 留档跳过:本变更可归属文件面为空')
|
|
1406
1429
|
patchOk = true
|
|
@@ -1416,7 +1439,8 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1416
1439
|
// diff 能捕捉提交后编辑)∪ 独占树未提交交付(dirtyAdded);其余交付面走提交区间。
|
|
1417
1440
|
// 排除项与 ownFiles 同款(change.patch/change-patch.json)——否则上一轮冻结件作为
|
|
1418
1441
|
// untracked 新文件被全文自嵌入(自引用循环:旧 patch 内容混进新 patch,三评 P1 根因)。
|
|
1419
|
-
const dirFilesForPatch = changeDirFiles.filter((f) => !f.endsWith('change.patch') && !f.endsWith('change-patch.json')
|
|
1442
|
+
const dirFilesForPatch = changeDirFiles.filter((f) => !f.endsWith('change.patch') && !f.endsWith('change-patch.json')
|
|
1443
|
+
&& !f.endsWith('scope-audit.json') && !f.endsWith('scope-audit.patch'))
|
|
1420
1444
|
const worktreeSet = new Set([...dirFilesForPatch, ...((freeze.dirtyAdded) || [])].map(toPosix))
|
|
1421
1445
|
const committedFace = freeze.files.map(toPosix).filter((f) => !worktreeSet.has(f))
|
|
1422
1446
|
const frozenCommitted = buildFrozenPatch(cwd, committedFace, { baseRef: st.baseline_commit, headRef: headCommit })
|
|
@@ -1445,28 +1469,46 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1445
1469
|
if (rec.lines.length > 0) for (const l of rec.lines) console.log(l)
|
|
1446
1470
|
moduleScope = { modules: rec.modules, uncoveredDirs: rec.uncoveredDirs, moduleMaps: rec.moduleMaps }
|
|
1447
1471
|
} catch { /* 对账 best-effort(三键空数组兜底) */ }
|
|
1448
|
-
|
|
1472
|
+
// 双通道收尾留痕统一(2026-10-07-unify-close-trace):共用 writeCloseTraceArtifacts
|
|
1473
|
+
// 一次写齐四件——沉淀资产面(change.patch + change-patch.json,键结构/口径不变)+
|
|
1474
|
+
// 对账快照面(scope-audit.json + scope-audit.patch,thin 通道新增):同点同锚
|
|
1475
|
+
// (同一 patchText 一次 sha256),新变更两条通道留痕对称,读侧回退退化为存量兜底。
|
|
1476
|
+
let planEntries = []
|
|
1477
|
+
try {
|
|
1478
|
+
const { parseFileChangeListDetailed } = await import('./change-list.js')
|
|
1479
|
+
planEntries = parseFileChangeListDetailed(join(changeDir, 'design.md'), { keepSillyspecDocs: true })
|
|
1480
|
+
} catch { /* 清单缺失/不可解析 → 实际侧 only 快照行(无 verdict,与主链路降级同语义) */ }
|
|
1481
|
+
const { buildThinSnapshotRows, writeCloseTraceArtifacts } = await import('./flow-parity.js')
|
|
1482
|
+
const { rows: snapRows, totals: snapTotals } = buildThinSnapshotRows({ ownFiles, stats, planEntries })
|
|
1483
|
+
const closeTrace = writeCloseTraceArtifacts({
|
|
1484
|
+
changeDir,
|
|
1449
1485
|
change,
|
|
1450
1486
|
baseline: st.baseline_commit,
|
|
1451
1487
|
head: typeof head === 'string' ? head.trim() : null,
|
|
1452
1488
|
files: ownFiles,
|
|
1453
|
-
|
|
1489
|
+
metaTotals: { files: ownFiles.length, additions, deletions },
|
|
1490
|
+
patchText: typeof frozen === 'string' && frozen ? frozen : null,
|
|
1454
1491
|
savedAt: new Date().toISOString(),
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
meta
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1492
|
+
snapObj: {
|
|
1493
|
+
mode: 'full-flow', ok: true, degradedReason: null,
|
|
1494
|
+
baseAnchor: st.baseline_commit || null,
|
|
1495
|
+
totals: snapTotals,
|
|
1496
|
+
rows: snapRows,
|
|
1497
|
+
excluded: { foreignDeclared: [] },
|
|
1498
|
+
note: 'flow done 时点冻结(本文件落盘时采集)',
|
|
1499
|
+
closedBy: 'flow done',
|
|
1500
|
+
},
|
|
1501
|
+
meta: {
|
|
1502
|
+
note: 'flow done 时点冻结(本变更可归属面:baseline..HEAD 提交面过滤 .sillyspec/ 非本变更目录但保留 .sillyspec/docs/ 交付文档 + 本变更目录工作树件;含未提交与 untracked,排除 flow-state.yaml 运行态)',
|
|
1503
|
+
// 模块对账结构化面(2026-09-27-thin-module-scope-persist):done 时点口径
|
|
1504
|
+
// (与 files[] 同时点语义,不随模块图后续变更回写);modules[] 空≠无影响
|
|
1505
|
+
...moduleScope,
|
|
1506
|
+
// dirty 缺口显式接受留痕(2026-10-08-thin-done-dirty-gate-and-paren-attribution):
|
|
1507
|
+
// --accept-dirty-gap 放行时缺口数入档(审计面可见,非只 console)
|
|
1508
|
+
...(acceptDirtyGap && freeze.dirtyWarned.length > 0 ? { acceptedDirtyGap: freeze.dirtyWarned.length } : {}),
|
|
1509
|
+
},
|
|
1510
|
+
})
|
|
1511
|
+
console.log(`📦 变更 patch 留档(双轨统一):change.patch + change-patch.json(${ownFiles.length} 文件,+${additions}/-${deletions})+ scope-audit.json + scope-audit.patch(对账快照同点冻结)${closeTrace.patchStatus === 'ok' ? ',sha256 已锚' : '——patch 采集失败已留痕'}`)
|
|
1470
1512
|
// design 声明面自证(2026-09-25-platform-feedback-batch2 E):design.md 文件变更清单
|
|
1471
1513
|
// 声明的交付文件是否都在冻结面——不在=承诺改了但没交付(承诺未兑现面,advisory 不
|
|
1472
1514
|
// 阻断——可能是范围裁剪了但 design 没同步更新)
|
|
@@ -2051,7 +2093,7 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
|
|
|
2051
2093
|
console.log('🔄 --refreeze:patch 子步标记已重置,本次 done 将重新冻结(change.patch 按 baseline..HEAD 最新面重建)')
|
|
2052
2094
|
}
|
|
2053
2095
|
}
|
|
2054
|
-
return cmdFlowDone({ change, cwd, specBase, runtimeRootOpt, freezeDirty: hasFlag('--freeze-dirty'), allowBatchTick: hasFlag('--allow-batch-tick') })
|
|
2096
|
+
return cmdFlowDone({ change, cwd, specBase, runtimeRootOpt, freezeDirty: hasFlag('--freeze-dirty'), acceptDirtyGap: hasFlag('--accept-dirty-gap'), allowBatchTick: hasFlag('--allow-batch-tick') })
|
|
2055
2097
|
}
|
|
2056
2098
|
if (sub === 'amend-draft') {
|
|
2057
2099
|
// 机器稿唯一留痕修改通道(R7 切片三 / FR-08):重锚哈希 + ledger amendment 审计;
|
package/src/foreign-declared.js
CHANGED
|
@@ -204,12 +204,16 @@ function loadOwnDeclaredSet(specBase, runtimeRoot, currentChangeName) {
|
|
|
204
204
|
|
|
205
205
|
/**
|
|
206
206
|
* 提交标题 → 变更名数组(2026-10-05-diff-commit-attribution):项目提交惯例 message 尾部
|
|
207
|
-
*
|
|
208
|
-
*
|
|
207
|
+
* 携带变更名括号后缀(多变更并列各自提取;归档提交 chore(archive): <名> 的标题首部同名
|
|
208
|
+
* 形态不提取——归档提交触碰的是治理面移动,交付文件归属看交付提交)。
|
|
209
|
+
* 括号形态双收(2026-10-08-thin-done-dirty-gate-and-paren-attribution,坑
|
|
210
|
+
* thin-done-src-commit-order-and-attribution-paren:中文输入法惯例写全角
|
|
211
|
+
* `(thin <名>;…)`,旧正则只认半角 → drift 归因静默失灵):开/闭括号半全角任意组合,
|
|
212
|
+
* 括号内允许 `thin ` 前缀与名字后附注文本;变更名本体模式(日期前缀 + 白名单字符)不放宽。
|
|
209
213
|
*/
|
|
210
214
|
export function parseChangeNamesFromSubject(subject) {
|
|
211
215
|
const out = []
|
|
212
|
-
const re =
|
|
216
|
+
const re = /[((]\s*(?:thin\s+)?(\d{4}-\d{2}-\d{2}-[A-Za-z0-9._-]+)[^()()]*[))]/g
|
|
213
217
|
let m
|
|
214
218
|
while ((m = re.exec(String(subject || '')))) out.push(m[1])
|
|
215
219
|
return out
|
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)) {
|