sillyspec 3.28.0 → 3.28.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.
Files changed (60) hide show
  1. package/.claude/skills/sillyspec-auto/SKILL.md +54 -93
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -114
  3. package/.claude/skills/sillyspec-continue/SKILL.md +17 -47
  4. package/.claude/skills/sillyspec-execute/SKILL.md +222 -214
  5. package/.claude/skills/sillyspec-knowledge/SKILL.md +40 -270
  6. package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
  7. package/.claude/skills/sillyspec-propose/SKILL.md +16 -87
  8. package/.claude/skills/sillyspec-resume/SKILL.md +21 -70
  9. package/.claude/skills/sillyspec-state/SKILL.md +25 -64
  10. package/.claude/skills/sillyspec-workspace/SKILL.md +33 -175
  11. package/package.json +1 -1
  12. package/src/archive-delta.js +462 -0
  13. package/src/constants.js +6 -0
  14. package/src/design-facts.js +418 -0
  15. package/src/dispatch/backends/sillyhub-mcp.js +17 -5
  16. package/src/docs-check.js +197 -34
  17. package/src/docs-migrate.js +124 -0
  18. package/src/doctor-diagnostics.js +11 -2
  19. package/src/endpoint-baseline.js +109 -0
  20. package/src/git-helper.js +31 -0
  21. package/src/index.js +381 -22
  22. package/src/modules.js +41 -12
  23. package/src/progress/change-registry.js +39 -0
  24. package/src/progress/consistency-doctor.js +124 -5
  25. package/src/progress/stage-machine.js +13 -2
  26. package/src/progress.js +5 -0
  27. package/src/quicklog.js +51 -0
  28. package/src/run/command.js +199 -5
  29. package/src/run/complete-handlers.js +60 -4
  30. package/src/run/complete.js +105 -6
  31. package/src/run/gates.js +353 -2
  32. package/src/run/progress-confirm.js +66 -0
  33. package/src/run/prompt.js +193 -15
  34. package/src/run/quick-audit.js +12 -5
  35. package/src/run/scan-profile.js +117 -7
  36. package/src/run/shared.js +22 -6
  37. package/src/run/stage.js +14 -1
  38. package/src/scan-diff.js +54 -1
  39. package/src/scan-facts.js +173 -0
  40. package/src/scan-postcheck.js +114 -3
  41. package/src/spec-sync.js +19 -2
  42. package/src/stage-review.js +62 -3
  43. package/src/stages/archive.js +5 -5
  44. package/src/stages/brainstorm-auto.js +2 -1
  45. package/src/stages/brainstorm.js +12 -20
  46. package/src/stages/execute.js +19 -21
  47. package/src/stages/plan-postcheck.js +194 -1
  48. package/src/stages/plan.js +8 -3
  49. package/src/stages/quick.js +4 -2
  50. package/src/stages/scan.js +44 -102
  51. package/src/stages/verify.js +18 -18
  52. package/src/task-review.js +200 -0
  53. package/src/taskcard.js +8 -1
  54. package/src/verify-postcheck.js +771 -29
  55. package/src/verify-probes.js +89 -10
  56. package/src/workflow.js +30 -0
  57. package/templates/agents-instruction.md +1 -1
  58. package/templates/prompts/taskcard-rules.md +5 -0
  59. package/templates/workflows/scan-docs.yaml +6 -0
  60. package/.claude/skills/verify-per-user/SKILL.md +0 -6
@@ -13,6 +13,9 @@
13
13
  * verify-result.md 骨架:七章节固定结构 + 探针结果机械预填 + 其余章节 <!--TODO--> 占位。
14
14
  * 结论章节留「待填」——extractVerifyConclusion 找不到 PASS/FAIL 关键词即判不过,骨架不能
15
15
  * 直接过门(与 symbol-impact 骨架同款防偷懒语义)。
16
+ * P3b 增量:①章节标题行末尾 claims 层标注(可复跑探针/确定性检查/人工判断,纯后缀不新增行);
17
+ * ②--init 同步落盘 verify-facts.json 机器底稿(探针命令行 + 首跑关键指标 + 时间戳,CLI 全权写,
18
+ * 供事后独立复跑审计;重复 --init 覆盖为最近一次 init 快照)。
16
19
  */
17
20
  import { existsSync, readFileSync, readdirSync, writeFileSync } from 'fs'
18
21
  import { join, dirname, basename } from 'path'
@@ -289,9 +292,85 @@ export function renderVerifyProbesReport(result) {
289
292
  return L.join('\n')
290
293
  }
291
294
 
295
+ /**
296
+ * 从 runVerifyProbes 结果构造 verify-facts.json 机器底稿对象(P3b 可复跑审计底稿)。
297
+ * 命令行统一 `sillyspec verify-probes --change <name>`;指标 fail-soft——字段不可得时少列
298
+ * 该键而非报错(probe5 形态随 verifyApiParity 演进,宁可少列不可失真)。
299
+ * @param {{ probe1?: object, probe3?: object, probe5?: object, probe6?: object }} result
300
+ * @param {{ changeName?: string, now?: string }} [opts] now 缺省取当前时刻(ISO)
301
+ * @returns {{ schemaVersion: number, change: string, generatedAt: string, probes: object }}
302
+ */
303
+ export function buildVerifyFacts(result, { changeName, now } = {}) {
304
+ const command = `sillyspec verify-probes --change ${changeName}`
305
+ // undefined 值键不进 metrics(「取不到的字段宁可少列」)
306
+ const defined = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined))
307
+ const len = (a) => (Array.isArray(a) ? a.length : undefined)
308
+ const num = (v) => (typeof v === 'number' ? v : undefined)
309
+ const p1 = (result && result.probe1) || {}
310
+ const p3 = (result && result.probe3) || {}
311
+ const p5 = (result && result.probe5) || {}
312
+ const p6 = (result && result.probe6) || {}
313
+ return {
314
+ schemaVersion: 1,
315
+ change: changeName,
316
+ generatedAt: now || new Date().toISOString(),
317
+ probes: {
318
+ probe1: {
319
+ command,
320
+ metrics: defined({
321
+ matches: len(p1.matches),
322
+ skippedFiles: len(p1.skippedFiles),
323
+ worktreeHits: num(p1.worktreeHits),
324
+ globEntries: len(p1.globEntries),
325
+ }),
326
+ },
327
+ probe3: {
328
+ command,
329
+ metrics: defined({
330
+ tasks: len(p3.tasks),
331
+ hasTest: Array.isArray(p3.tasks) ? p3.tasks.filter(t => t && t.hasTest).length : undefined,
332
+ }),
333
+ },
334
+ probe5: {
335
+ command,
336
+ metrics: defined({
337
+ backendEndpoints: num(p5.backendCount),
338
+ frontendCalls: num(p5.frontendCount),
339
+ }),
340
+ },
341
+ probe6: {
342
+ command,
343
+ metrics: defined({
344
+ deletions: len(p6.deletions),
345
+ unavailable: typeof p6.unavailable === 'boolean' ? p6.unavailable : undefined,
346
+ }),
347
+ },
348
+ },
349
+ }
350
+ }
351
+
352
+ /**
353
+ * verify-probes --init 落盘 verify-facts.json(CLI 全权写)。重复 --init 无条件覆盖——语义是
354
+ * 「最近一次机械预填的审计底稿快照」(骨架已存在跳过时 facts 照样刷新),agent 勿手改:
355
+ * 事后复跑审计的对照快照,防篡改一致性检查的对比基准是 verify-result.md 正文(非本文件)。
356
+ * @param {string} changeDir 变更目录(spec 根下 changes/<name>)
357
+ * @param {object} result runVerifyProbes 返回值
358
+ * @param {string} changeName 变更名(统一命令行呈现)
359
+ * @returns {{ facts: object, path: string }}
360
+ */
361
+ export function writeVerifyFacts(changeDir, result, changeName) {
362
+ const facts = buildVerifyFacts(result, { changeName })
363
+ const factsPath = join(changeDir, 'verify-facts.json')
364
+ writeFileSync(factsPath, JSON.stringify(facts, null, 2) + '\n')
365
+ console.log(`📝 已刷新 verify-facts.json 机器底稿: ${factsPath}(CLI 全权写,勿手改)`)
366
+ return { facts, path: factsPath }
367
+ }
368
+
292
369
  /**
293
370
  * 生成 verify-result.md 骨架(七章节;探针结果机械预填,语义章节 <!--TODO--> 占位)。
294
371
  * 结论章节留「待填」——extractVerifyConclusion 无 PASS/FAIL 关键词即判不过,骨架不能直接过门。
372
+ * 章节标题行末尾带 claims 层标注(P3b):探针结果=可复跑探针 / 测试结果=确定性检查 / 其余=
373
+ * 人工判断——纯渲染层后缀,不新增行,verify gate 结论提取按关键词窗口制不受影响。
295
374
  * @returns {string|null} 骨架全文;无 design.md/tasks.md(非完整流程变更)→ null
296
375
  */
297
376
  export function generateVerifyResultSkeleton(result) {
@@ -301,33 +380,33 @@ export function generateVerifyResultSkeleton(result) {
301
380
  '> 探针结果已机械预填;其余章节把 `<!--TODO-->` 替换为真实内容。**结论必须写明 PASS / FAIL**——',
302
381
  '> 留「待填」会被 gate 判不过(fail-closed)。',
303
382
  '',
304
- '## 结论:<待填:PASS 或 FAIL(+一句话理由)>',
383
+ '## 结论:<待填:PASS 或 FAIL(+一句话理由)> [层:人工判断]',
305
384
  '',
306
- '## 任务完成度',
385
+ '## 任务完成度 [层:人工判断]',
307
386
  '<!--TODO: 逐 task 对照 tasks.md 勾选与验收标准,完成/未完成/存疑三态-->',
308
387
  '',
309
- '## 设计一致性',
388
+ '## 设计一致性 [层:人工判断]',
310
389
  '<!--TODO: 实现与 design.md 的偏差(无偏差也显式写「一致」)-->',
311
390
  '',
312
- '## 探针结果(CLI 机械预填)',
391
+ '## 探针结果(CLI 机械预填) [层:可复跑探针——gate 抽查防篡改]',
313
392
  renderVerifyProbesReport(result),
314
393
  '',
315
- '## 测试结果',
394
+ '## 测试结果 [层:确定性检查——CLI 实测对账]',
316
395
  '<!--TODO: 测试命令 + 结果(通过数/失败数;known_failures 豁免逐条注明)-->',
317
396
  '',
318
- '## 决策追踪矩阵(如存在 decisions.md;无则删本节)',
397
+ '## 决策追踪矩阵(如存在 decisions.md;无则删本节) [层:人工判断]',
319
398
  '<!--TODO: | 决策 ID | FR | Task | Evidence | 状态 |(D-xxx@vN → FR-xxx → task → 证据回指闭环)-->',
320
399
  '',
321
- '## 技术债务',
400
+ '## 技术债务 [层:人工判断]',
322
401
  '<!--TODO: TODO/FIXME/HACK 统计(探针 1 的命中已预填在上方探针结果)-->',
323
402
  '',
324
- '## 变更风险等级',
403
+ '## 变更风险等级 [层:人工判断]',
325
404
  '<!--TODO: doc-only / unit-sufficient / contract-required / integration-critical / deployment-critical;若 design.md frontmatter 有 risk_level 显式声明,写明「显式声明 = <等级>」+ 理由;若有命中被同句否定语境抑制(如「不新增 daemon 协议」),写明被抑制关键词与理由(抑制可审计,不许用来静默降级)-->',
326
405
  '',
327
- '## Runtime Evidence',
406
+ '## Runtime Evidence [层:人工判断]',
328
407
  '<!--TODO: 关键命令输出/时间戳/commit hash 证据链;integration/deployment-critical 必填,按实际触碰的运行时组件写(启动命令/端点/请求响应/日志片段/生命周期终态断言/失败模式排除),未涉及的行写「不涉及」-->',
329
408
  '',
330
- '## 代码审查',
409
+ '## 代码审查 [层:人工判断]',
331
410
  '<!--TODO: 问题列表 + 总体评价-->',
332
411
  '',
333
412
  ]
package/src/workflow.js CHANGED
@@ -14,6 +14,7 @@ import { join, resolve, basename } from 'path'
14
14
  import jsYaml from 'js-yaml'
15
15
  import { WORKFLOW_STATUS } from './constants.js'
16
16
  import { contentNonEmpty, lineCount, missingSections, placeholderLineMatches } from './check-primitives.js'
17
+ import { collectInvalidDocRefs } from './docs-check.js'
17
18
 
18
19
  // ─── Workflow 加载 ───
19
20
 
@@ -214,6 +215,35 @@ function checkOutput(outputDef, projectName, cwd, specBase) {
214
215
  }
215
216
  break
216
217
  }
218
+ case 'ref_exists': {
219
+ // 引用事实核验(archify 借鉴,2026-09-04):层1+层2 联合核验,语义与
220
+ // scan-postcheck 的 scan_doc_ref_invalid 单一源(collectInvalidDocRefs)。
221
+ // min = 文档至少须含的 file:line 引用条数(缺省 1;显式 0 = 只核验有效性不求数量)。
222
+ if (existsSync(fullPath)) {
223
+ const min = check.min ?? 1
224
+ let verified
225
+ try {
226
+ verified = collectInvalidDocRefs(cwd, [{ name: rawPath, absPath: fullPath }])
227
+ } catch {
228
+ // 核验器故障 fail-open(与 scan-postcheck 的 checkScanDocRefs 同口径),不伪装成「引用数不足」
229
+ results.push({ passed: true, check: `ref_exists(${min})`, detail: '引用核验器异常,跳过(fail-open)' })
230
+ break
231
+ }
232
+ const enough = verified.totalRefs >= min
233
+ const passed = verified.invalid.length === 0 && enough
234
+ let detail = ''
235
+ if (!enough) {
236
+ const crossNote = verified.skippedCrossRepo > 0 ? `(另有 ${verified.skippedCrossRepo} 条 repo:// 跨仓引用未计入)` : ''
237
+ detail = `file:line 引用仅 ${verified.totalRefs} 条,要求至少 ${min} 条${crossNote}: ${rawPath}`
238
+ } else if (verified.invalid.length > 0) {
239
+ detail = `${verified.invalid.length} 条引用未通过核验: ${verified.invalid.slice(0, 3).map(i => `${i.ref}(${i.reason})`).join('; ')} — ${rawPath}`
240
+ }
241
+ results.push({ passed, check: `ref_exists(${min})`, detail })
242
+ } else {
243
+ results.push({ passed: false, check: `ref_exists(${check.min ?? 1})`, detail: `文件不存在: ${rawPath}` })
244
+ }
245
+ break
246
+ }
217
247
  default:
218
248
  results.push({ passed: true, check: check.type, detail: `未知检查类型,跳过: ${check.type}` })
219
249
  }
@@ -11,7 +11,7 @@
11
11
  5. **执行顺序**:文档 → 读代码 → 写测试 → 写实现 → 跑测试 → 验收 → 更新文档。
12
12
  6. **判规模选档**:≤3 文件、范围明确走 `quick`;多阶段 / 架构级走完整流程。
13
13
  7. **代码先行不补流程(倒推 B 模式)**:代码若已先写好,**不回头补 brainstorm/plan 装样子**——用 `quick --done` 收尾 + 补 quicklog 条目,把已落盘改动如实登记进进度库。
14
- 8. **实证核验再 `--done`**:`quick --done` 前,触及 `src`/`test` 的改动先 `npm test` + `npm run lint`;纯 doc/配置改动可跳过(lint 不扫 docs/,空转无信息),以落盘文件与测试结果为准,不信口头"已完成"。
14
+ 8. **实证核验再 `--done`**:触及 `src`/`test` 的改动,CLI 会在 `quick --done` 时**亲自实测** `.sillyspec/local.yaml` 的 `commands.test` / `commands.lint`(门禁:实测失败阻断 --done 回 pending;纯 doc/配置与未配置命令自动跳过;倒推 B 模式按 --files 声明边界兜底判定)。agent 侧预跑**可选**——想省「--done 被拦→修复→重跑」一轮时才预跑;纯 doc/配置改动无需跑。以落盘文件与测试结果为准,不信口头"已完成"。
15
15
  9. **中途停下不靠额外命令存进度**——进度已由上一次 `--done` 自动落盘;恢复时用 `sillyspec progress show` 查看进度,再用 `sillyspec run <stage>` 续跑,不直接 commit 半成品。
16
16
  10. **实现完成后对照文档验收**(design.md / 模块文档),并检查是否影响已有测试。
17
17
  11. **非测试逻辑本身有误时,禁止改测试来"通过"**——修逻辑,不修测试。
@@ -10,6 +10,11 @@ TaskCard 格式规则(必须严格遵守):
10
10
  - 不需要:修改文件章节、覆盖来源章节、接口定义章节、TDD 步骤章节、参考章节
11
11
  - frontmatter 必须以三减号结尾闭合(开头和结尾各一行 ---)。缺结尾会让 postcheck 提取不到 frontmatter,误报「字段缺失 / allowed_paths 为空」
12
12
  - allowed_paths 用块式写法(键名换行后每项一行「 - 路径」),不要用流式方括号 [path]——块式与所有校验器一致最稳
13
+ - target_files 是可选字段:对账用精确文件级意图声明——列出本 task 计划改动的文件,供事后「计划改哪些 vs 实际改了哪些」对账;无明确文件级意图时保留骨架带出的 `target_files: []` 空占位不动,不猜清单(空 `[]` 是唯一允许的流式写法,填入实际路径后必须改块式)
14
+ - 格式:块式列表(`target_files:` 换行后每项一行「 - 路径」),精确文件路径、仓根相对、正斜杠;当前不存在、将由本 task 新建的文件加 `NEW:` 前缀
15
+ - 正例:`target_files:` + ` - src/taskcard.js`(已存在文件,精确路径)+ ` - NEW:src/foo.js`(本 task 将新建的文件)
16
+ - 反例:` - src/**`(禁 glob)、` - src/dir/`(禁目录前缀)、` - C:\repo\src\a.js` 或 ` - /repo/src/a.js`(禁绝对路径)——对账按精确路径逐字匹配,任何非精确形态永不命中
17
+ - 与 allowed_paths 的语义区别(两者并存、不可互替):allowed_paths 是 worktree 写入守卫白名单(子代理只被授权改这些路径),目录前缀与 glob 合法(覆盖性授权);target_files 是对账用精确文件级意图声明,禁 glob、禁目录前缀、禁绝对路径——且 target_files 列出的每个文件都应落在 allowed_paths 的授权范围内
13
18
  - implementation / acceptance / constraints / verify 的每条列表项是 YAML plain scalar,避免以下特殊字符触发 ScannerError:
14
19
  - 冒号+空格(如代码签名 provider_id:str、queryUsage(id:string))会被当映射键报 mapping values are not allowed here——去掉冒号后空格或改成中文描述
15
20
  - 花括号且内部含冒号或引号(如 JS 对象、JS 模板字面量)会报 expected block end——改用不含这些字符的中文描述
@@ -21,9 +21,12 @@ roles:
21
21
  min: 20
22
22
  - type: contains_sections
23
23
  sections: ["技术栈", "架构概览"]
24
+ - type: ref_exists
25
+ min: 3
24
26
  constraints:
25
27
  - "禁止读源码全文,只用 grep/rg 搜索"
26
28
  - "Schema 只记表名+说明+字段数"
29
+ - "关键架构事实(模块职责/技术栈组件/数据存储)带反引号 path:line 引用,禁止猜行号——postcheck 逐条核验"
27
30
 
28
31
  - id: conventions
29
32
  name: "代码约定"
@@ -65,9 +68,12 @@ roles:
65
68
  - type: file_exists
66
69
  - type: min_lines
67
70
  min: 15
71
+ - type: ref_exists
72
+ min: 1
68
73
  constraints:
69
74
  - "STRUCTURE: 目录树+模块说明"
70
75
  - "INTEGRATIONS: 按类型分组外部依赖"
76
+ - "外部集成条目带反引号 path:line 引用(真实可核验),禁止猜行号"
71
77
 
72
78
  - id: quality
73
79
  name: "测试+债务+概览"
@@ -1,6 +0,0 @@
1
- ---
2
- name: verify-per-user
3
- description: verify
4
- ---
5
-
6
- # test