sillyspec 3.32.1 → 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.
@@ -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.1",
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 dimensions = [multiDb, pointerHealth, changesSplit, changeDb, executeMismatch, docBloat, repoNativeChain, lifecycleDoc, worktreeHealth, buildEnv, mcpEndpoints, applyManifestDrift, selfMaintenanceTax, archiveIntegrity, ceremonyShadow, gateSnapshotLeak];
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
+ }
@@ -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
- const km = await matchKnowledgeHybrid(knowledgeRoot, `${change}\n${input || ''}`, { cwd: dirname(specBase) })
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
- for (const m of _commitTexts.matchAll(/task-(\d{1,2})/gi)) _evidencedTasks.add(String(Number(m[1])).padStart(2, '0'))
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
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)) {