sillyspec 3.32.9 → 3.32.10

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.
Files changed (45) hide show
  1. package/.claude/skills/sillyspec-brainstorm/SKILL.md +2 -2
  2. package/.claude/skills/sillyspec-execute/SKILL.md +2 -2
  3. package/.claude/skills/sillyspec-flow/SKILL.md +1 -1
  4. package/.claude/skills/sillyspec-plan/SKILL.md +2 -2
  5. package/AGENTS.md +1 -1
  6. package/assets/command-cards/flow.md +1 -1
  7. package/package.json +1 -1
  8. package/src/commit-suggest.js +45 -4
  9. package/src/config-schema.js +14 -0
  10. package/src/decision-distill.js +77 -21
  11. package/src/decision-renumber.js +157 -0
  12. package/src/docs-check.js +30 -15
  13. package/src/docs-debt.js +3 -2
  14. package/src/doctor-diagnostics.js +3 -1
  15. package/src/flow-parity.js +18 -2
  16. package/src/flow.js +75 -12
  17. package/src/fr-index.js +15 -1
  18. package/src/hooks/worktree-guard.js +3 -3
  19. package/src/index.js +7 -5
  20. package/src/knowledge-digest.js +23 -6
  21. package/src/knowledge-graph.js +92 -14
  22. package/src/knowledge-match.js +34 -9
  23. package/src/knowledge-stats.js +12 -13
  24. package/src/knowledge-vector.js +98 -7
  25. package/src/machine-interface.js +26 -0
  26. package/src/quicklog.js +6 -1
  27. package/src/run/archive-distill.js +3 -0
  28. package/src/run/command.js +3 -3
  29. package/src/run/complete-handlers.js +8 -3
  30. package/src/run/complete.js +113 -44
  31. package/src/run/gates.js +93 -89
  32. package/src/run/prompt.js +3 -3
  33. package/src/run/stage.js +1 -1
  34. package/src/sentinel-assertions.js +43 -1
  35. package/src/stage-contract.js +26 -3
  36. package/src/stage-review.js +44 -11
  37. package/src/stage-templates.js +2 -0
  38. package/src/stages/brainstorm.js +5 -1
  39. package/src/stages/knowledge.js +56 -5
  40. package/src/sync.js +28 -2
  41. package/src/task-tick.js +12 -0
  42. package/src/timeline.js +60 -19
  43. package/src/verify-postcheck.js +49 -1
  44. package/src/verify-probes.js +61 -18
  45. package/templates/agents-instruction.md +1 -1
@@ -1952,7 +1952,7 @@ function prefillMatrixCells(item, testFiles, hint) {
1952
1952
  * @param {{ applicable: boolean, tasks: Array<{task: string, acceptance: string[], testFiles: string[], hints: Record<number, {terms: string[], files: string[], anchors?: Array<{term: string, file: string, line: number}>}>}> }} p7
1953
1953
  * @returns {string[]} 行数组(含段标题;调用方自理前后空行)
1954
1954
  */
1955
- function renderProbe7Lines(p7) {
1955
+ export function renderProbe7Lines(p7) {
1956
1956
  const L = [PROBE7_HEADING]
1957
1957
  if (!p7 || !p7.applicable) {
1958
1958
  L.push('- 不适用(无 TaskCard)')
@@ -1986,8 +1986,12 @@ function renderProbe7Lines(p7) {
1986
1986
  L.push('| acceptance 条目 | 归属测试文件 | 关键词命中(提示,命中≠判定) | 判定 | 证据 |')
1987
1987
  L.push('|---|---|---|---|---|')
1988
1988
  t.acceptance.forEach((item, i) => {
1989
+ // 展示帽(坑 probe-noise-and-commit-suggest-quality 坑A,2026-10-15 消费仓实证:宽目录
1990
+ // allowed_paths 递归把 82 个测试文件全量塞进单格(单行数千字符 ×3 行),信号全淹——照
1991
+ // 探针 3 既有 slice 截断先例收敛为前 5 + 「共 N 个」折叠;路径经 mdEscapeCell 防竖线破表)
1989
1992
  const attribCell = (t.testFiles && t.testFiles.length > 0)
1990
- ? t.testFiles.map(f => `\`${f}\``).join('<br>')
1993
+ ? t.testFiles.slice(0, 5).map(f => `\`${mdEscapeCell(f, 120)}\``).join('<br>')
1994
+ + (t.testFiles.length > 5 ? `<br>…共 ${t.testFiles.length} 个(目录级候选全量在 facts/trace,归属按关键词命中与证据锚点收窄)` : '')
1991
1995
  : '无归属测试——判定大概率 uncovered'
1992
1996
  const h = (t.hints && t.hints[i]) || null
1993
1997
  const hintCell = h
@@ -2251,7 +2255,9 @@ export function runVerifyProbes({ cwd, changeName, specDir = null }) {
2251
2255
  if (t && isProbe7TestPath(t) && !fromDependents.includes(t)) fromDependents.push(t)
2252
2256
  }
2253
2257
  }
2254
- const testFiles = [...new Set([...fromAllowed, ...fromReview, ...fromDependents])]
2258
+ // 收集帽(坑 probe-noise-and-commit-suggest-quality 坑A):目录级 allowed_paths 递归可卷
2259
+ // 入整仓测试树(实证 82 个)——去重后截前 20(展示层另有 5 个硬帽;hints/trace 消费同集)
2260
+ const testFiles = [...new Set([...fromAllowed, ...fromReview, ...fromDependents])].slice(0, 20)
2255
2261
  // 无归属卡注入 FR 关联回归既有用例候选(task-02;有归属卡 existingTests 恒空——零噪音)
2256
2262
  const existingTests = []
2257
2263
  if (frLinkedExisting && testFiles.length === 0) {
@@ -2259,7 +2265,7 @@ export function runVerifyProbes({ cwd, changeName, specDir = null }) {
2259
2265
  if (isProbe7TestPath(f) && !existingTests.includes(f)) existingTests.push(f)
2260
2266
  }
2261
2267
  }
2262
- const finalTestFiles = existingTests.length > 0 ? [...new Set([...testFiles, ...existingTests])] : testFiles
2268
+ const finalTestFiles = existingTests.length > 0 ? [...new Set([...testFiles, ...existingTests])].slice(0, 20) : testFiles
2263
2269
  probe7.tasks.push({
2264
2270
  task: card.task,
2265
2271
  acceptance: card.acceptance,
@@ -2663,6 +2669,33 @@ const API_MATRIX_HEADING = '## 接口验证覆盖矩阵'
2663
2669
  // 权限矩阵段有对应行或显式豁免(「无权限约束」)——warning 计算归 task-05,此处只定集合。
2664
2670
  const API_MATRIX_WRITE_METHODS = new Set(['POST', 'PUT', 'DELETE', 'PATCH'])
2665
2671
 
2672
+ /**
2673
+ * design.md 接口段实况解析面(gate/derive 预检注入用,2026-10-14-gate-verify-apiface-preflight):
2674
+ * 与 backfillFactsFromMdAndTests 落盘的 facts.apiFace 同形同源(同一 parseDesignApiTable、同一
2675
+ * writeEndpoints 过滤——单一产物源,禁第二套解析文法),零落盘。背景:facts.apiFace 唯一生产者
2676
+ * 是 run verify --done 收尾前置 backfill(run/gates.js),gate 路径不回填且 --init 刻意不落键 →
2677
+ * 预检 parseSet 空/旧 → design接口表# 锚点恒空指(平台 6 轮 gate 实证)。gate 侧经本 helper
2678
+ * 实况解析后经 context.apiFaceLive 注入 validator(stage-contract 零 import 纪律:注入不做
2679
+ * 函数调用),「预检过=必过」对锚点核对成立;design.md 缺失/不可读返回 null(调用方不注入,
2680
+ * 退回 facts 口径——2026-10-14 评审 P1:空面注入会覆写有效 facts.apiFace 反致误阻断)、解析
2681
+ * 异常由调用方 fail-soft 承接(不注入退回 facts)。
2682
+ * @param {string} designPath changeDir/design.md 路径(缺失/不可读 → null=不注入信号)
2683
+ * @returns {{ endpoints: Array<{method: string, path: string, rowIdx: number}>, declared: number|null, writeEndpoints: Array }|null}
2684
+ */
2685
+ export function computeApiFaceFromDesign(designPath) {
2686
+ let designText = null
2687
+ if (existsSync(designPath)) {
2688
+ try { designText = readFileSync(designPath, 'utf8') } catch { designText = null }
2689
+ }
2690
+ if (designText === null) return null
2691
+ const face = parseDesignApiTable(designText)
2692
+ return {
2693
+ endpoints: face.endpoints,
2694
+ declared: face.declared,
2695
+ writeEndpoints: face.endpoints.filter(e => e && API_MATRIX_WRITE_METHODS.has(e.method)),
2696
+ }
2697
+ }
2698
+
2666
2699
  /**
2667
2700
  * 渲染「## 接口验证覆盖矩阵」段(task-04)。
2668
2701
  * 三态:解析有端点 → 逐端点预填行(判定列 `<待填:五选一>` 占位,同 probe7 预填口径供 agent
@@ -2731,7 +2764,12 @@ export function buildVerifyFacts(result, { changeName, now } = {}) {
2731
2764
  // v2(2026-09-08-ir-verify-facts):probes 机器段原样;conclusion/tests/requiredEvidence/
2732
2765
  // runtimeEvidence/factsConsistency 五段是 slot-backfill/实测回填段(D-001@v2),--init 快照
2733
2766
  // 不落键(writeVerifyFacts 分段合并时保留既有固化段),由 backfillFactsFromMdAndTests 填。
2734
- return {
2767
+ // apiFace 例外(坑 verify-close-gate-staggered-exposure 建议③连带,2026-10-15 消费仓实证):
2768
+ // --init 即落键——runVerifyProbes 的 apiFace 与 --done backfill 同源同形(computeApiFaceFromDesign),
2769
+ // 原「刻意不落」使「重跑 --init 刷新 facts」救不了空指预检(报错文案也没提这一点),且空指
2770
+ // 诊断方向被误导去改 design 文档。键只在值在场时出现(undefined 值键会在 writeVerifyFacts
2771
+ // 合并 spread 时覆盖 prev 固化段)。
2772
+ const factsSkeleton = {
2735
2773
  schemaVersion: 2,
2736
2774
  change: changeName,
2737
2775
  generatedAt: now || new Date().toISOString(),
@@ -2796,6 +2834,8 @@ export function buildVerifyFacts(result, { changeName, now } = {}) {
2796
2834
  },
2797
2835
  },
2798
2836
  }
2837
+ if (result && result.apiFace && typeof result.apiFace === 'object') factsSkeleton.apiFace = result.apiFace
2838
+ return factsSkeleton
2799
2839
  }
2800
2840
 
2801
2841
  /**
@@ -2832,7 +2872,16 @@ export function writeVerifyFacts(changeDir, result, changeName, opts = {}) {
2832
2872
  try {
2833
2873
  const prev = JSON.parse(readFileSync(factsPath, 'utf8'))
2834
2874
  if (prev && prev.schemaVersion === FACTS_SCHEMA_VERSION) {
2875
+ // backfill 固化段保留(坑 verify-close-gate-staggered-exposure,2026-10-15):旧白名单只保
2876
+ // 五段,re-init 会把 --done 回填的 apiFace/consumerHints/handover/dbScriptDeclarations 等
2877
+ // 整个抹掉(--init 后预检又空指)。此处先铺 prev 固化段再被 fresh 覆盖:apiFace 由 fresh
2878
+ // 侧重算(同源 computeApiFaceFromDesign,值应等价),fresh 未带的键原样保留。
2879
+ const preservedBackfill = {}
2880
+ for (const k of ['apiFace', 'consumerHints', 'handover', 'dbScriptDeclarations', 'matrixPartialRows', 'smokeRan']) {
2881
+ if (prev[k] !== undefined) preservedBackfill[k] = prev[k]
2882
+ }
2835
2883
  facts = {
2884
+ ...preservedBackfill,
2836
2885
  ...fresh,
2837
2886
  conclusion: prev.conclusion,
2838
2887
  tests: prev.tests,
@@ -3140,20 +3189,14 @@ export function backfillFactsFromMdAndTests(factsPath, { verifyMd, testCheckResu
3140
3189
  // validator 判 N=0 走零行为注记);与 runVerifyProbes 同一解析器(单一产物源)。
3141
3190
  {
3142
3191
  const changeDir = dirname(factsPath)
3143
- const designPath = join(changeDir, 'design.md')
3144
- let designText = null
3145
- if (existsSync(designPath)) {
3146
- try { designText = readFileSync(designPath, 'utf8') } catch { designText = null }
3147
- }
3148
- const face = parseDesignApiTable(designText || '')
3149
- facts.apiFace = {
3150
- endpoints: face.endpoints,
3151
- declared: face.declared,
3152
- writeEndpoints: face.endpoints.filter(e => e && API_MATRIX_WRITE_METHODS.has(e.method)),
3153
- }
3192
+ // backfill 契约=恒落对象面(缺失/不可读 → 空面,基线口径不变):helper 的 null 是 gate
3193
+ // 注入通道的「不注入」信号,非 backfill 落盘形态(2026-10-14 评审 P3——null 落盘会让
3194
+ // 「design 蒸发×矩阵段缺失×严格档」窄角从基线 error 降 warning,重构不得夹带行为变化)。
3195
+ facts.apiFace = computeApiFaceFromDesign(join(changeDir, 'design.md'))
3196
+ || { endpoints: [], declared: null, writeEndpoints: [] }
3154
3197
  let designPaths = []
3155
- if (designText !== null) {
3156
- try { designPaths = parseFileChangeListDetailed(designPath).map(e => String(e.path)) } catch { designPaths = [] }
3198
+ if (existsSync(join(changeDir, 'design.md'))) {
3199
+ try { designPaths = parseFileChangeListDetailed(join(changeDir, 'design.md')).map(e => String(e.path)) } catch { designPaths = [] }
3157
3200
  }
3158
3201
  facts.consumerHints = classifyConsumerHints(designPaths)
3159
3202
  }
@@ -15,7 +15,7 @@
15
15
  `--input` 过门格式(轻量道首次调用即过,勿靠报错学;引号内换行合法,下例可照抄):
16
16
 
17
17
  ```bash
18
- sillyspec flow start --change <YYYY-MM-DD-名> --input "<动机与背景>
18
+ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(首行即变更标题);随后动机与背景
19
19
 
20
20
  成功标准:
21
21
  - <可验证标准>"