sillyspec 3.26.3 → 3.26.5

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 (68) hide show
  1. package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -0
  2. package/.claude/skills/sillyspec-execute/SKILL.md +1 -0
  3. package/.claude/skills/sillyspec-plan/SKILL.md +1 -0
  4. package/.claude/skills/sillyspec-quick/SKILL.md +18 -4
  5. package/package.json +1 -1
  6. package/src/db.js +11 -0
  7. package/src/dispatch/backends/local-agent.js +3 -0
  8. package/src/dispatch/backends/sillyhub-mcp.js +3 -0
  9. package/src/git-helper.js +21 -8
  10. package/src/index.js +2 -0
  11. package/src/quicklog.js +2 -0
  12. package/src/run/command.js +59 -7
  13. package/src/run/complete-handlers.js +95 -1
  14. package/src/run/complete.js +23 -5
  15. package/src/run/gates.js +1 -1
  16. package/src/run/prompt.js +34 -20
  17. package/src/run/quick-audit.js +5 -6
  18. package/src/run/shared.js +45 -11
  19. package/src/run/stage.js +10 -0
  20. package/src/run.js +5 -5
  21. package/src/stage-contract-spec.js +11 -0
  22. package/src/stage-contract.js +11 -1
  23. package/src/stage-review.js +9 -1
  24. package/src/stages/archive.js +12 -26
  25. package/src/stages/brainstorm.js +22 -20
  26. package/src/stages/execute.js +48 -0
  27. package/src/stages/plan.js +17 -1
  28. package/src/stages/quick.js +5 -1
  29. package/src/stages/scan.js +2 -2
  30. package/src/stages/verify.js +2 -1
  31. package/src/sync.js +1 -1
  32. package/src/worktree-apply.js +3 -3
  33. package/src/worktree.js +83 -8
  34. package/templates/prompts/testcase-design.md +12 -0
  35. package/templates/prompts/verify-probes.md +1 -0
  36. package/templates/skills/sillyspec-onboard/SKILL.md +92 -0
  37. package/packages/dashboard/index.html +0 -15
  38. package/packages/dashboard/package-lock.json +0 -2384
  39. package/packages/dashboard/public/favicon.jpg +0 -0
  40. package/packages/dashboard/public/logo.jpg +0 -0
  41. package/packages/dashboard/public/prototype-dashboard.html +0 -836
  42. package/packages/dashboard/public/prototype-overview.html +0 -256
  43. package/packages/dashboard/src/App.vue +0 -558
  44. package/packages/dashboard/src/components/ActionBar.vue +0 -93
  45. package/packages/dashboard/src/components/CommandPalette.vue +0 -96
  46. package/packages/dashboard/src/components/DetailPanel.vue +0 -137
  47. package/packages/dashboard/src/components/DocPreview.vue +0 -257
  48. package/packages/dashboard/src/components/DocTree.vue +0 -114
  49. package/packages/dashboard/src/components/HResizeHandle.vue +0 -48
  50. package/packages/dashboard/src/components/LogStream.vue +0 -65
  51. package/packages/dashboard/src/components/PipelineStage.vue +0 -95
  52. package/packages/dashboard/src/components/PipelineView.vue +0 -156
  53. package/packages/dashboard/src/components/ProjectCard.vue +0 -187
  54. package/packages/dashboard/src/components/ProjectList.vue +0 -210
  55. package/packages/dashboard/src/components/ProjectOverview.vue +0 -152
  56. package/packages/dashboard/src/components/StageBadge.vue +0 -67
  57. package/packages/dashboard/src/components/StepCard.vue +0 -94
  58. package/packages/dashboard/src/components/VResizeHandle.vue +0 -61
  59. package/packages/dashboard/src/components/detail/DocsDetail.vue +0 -48
  60. package/packages/dashboard/src/components/detail/GitDetail.vue +0 -61
  61. package/packages/dashboard/src/components/detail/TechDetail.vue +0 -43
  62. package/packages/dashboard/src/composables/useDashboard.js +0 -213
  63. package/packages/dashboard/src/composables/useKeyboard.js +0 -119
  64. package/packages/dashboard/src/composables/useLayout.js +0 -131
  65. package/packages/dashboard/src/composables/useWebSocket.js +0 -129
  66. package/packages/dashboard/src/main.js +0 -8
  67. package/packages/dashboard/src/style.css +0 -132
  68. package/packages/dashboard/vite.config.js +0 -18
@@ -343,9 +343,25 @@ tier: {REVIEW_TIER}({REVIEW_TIER_REASON})
343
343
  {REVIEW_JSON_CONTRACT}
344
344
  4. verdict=fail 时在 reviewerNotes 写明阻断项
345
345
 
346
+ ### 生成 module-impact.md 首版(scale≠small 时)
347
+ 计划审查通过后,顺带生成 module-impact.md 首版(本次变更的模块影响分析,供 execute/verify 阶段更新、archive 阶段终审)。scale=small 不生成(small 走 quick,module-impact 对 quick 无用)。
348
+
349
+ 输入(此时 TaskCard/allowed_paths 尚未生成——在下一步 generate_blueprints,故用以下两项作输入,粒度与 archive 现状一致):
350
+ - design.md 的「文件变更清单」(本次计划改哪些源码文件)
351
+ - plan.md 的任务列表(每个 task 改的范围)
352
+
353
+ 步骤(复用 archive extract-module-impact 的核心指引,同源——改 archive step2 prompt 时保持一致):
354
+ 1. 读 {SPEC_ROOT}/docs/<project>/modules/_module-map.yaml(模块→文件路径映射)。**不存在 → 降级**:生成只含 unmapped 部分的 module-impact.md + 提示「建议运行 scan 生成模块映射」,不阻断
355
+ 2. 对照 design 文件变更清单 + plan 任务列表,逐文件匹配所属模块(命中的归 mapped,未命中的归 unmapped)
356
+ 3. 生成模块影响矩阵(模块 × 影响类型[新增/修改/删除/依赖变更] × 说明),落盘 {SPEC_ROOT}/changes/<change>/module-impact.md
357
+ 4. 首行标题必须用中文:# 模块影响分析(Module Impact)— <变更简述>
358
+
359
+ execute/verify 阶段会按实际代码变更更新此文档;archive 阶段会最终确认它。
360
+
346
361
  ### 输出
347
362
  - tier=self:审查清单结果(每条状态 + 偏差说明)
348
- - tier=independent:子代理产出的 review.json 路径 + verdict 摘要`,
363
+ - tier=independent:子代理产出的 review.json 路径 + verdict 摘要
364
+ - scale≠small:附 module-impact.md 路径 + 影响摘要`,
349
365
  outputHint: 'plan 审查结果(self=清单 / independent=review.json 路径+verdict)',
350
366
  optional: false
351
367
  }
@@ -50,9 +50,11 @@ export const definition = {
50
50
  ### 操作
51
51
  1. 先读后写:调用已有方法前 \`cat\` 源文件确认签名,\`grep\` 确认方法存在
52
52
  2. 写代码完成任务
53
- 3. 如涉及逻辑变更,建议写单元测试验证(不强制,纯配置/文档/小改动可跳过)
53
+ 3. 如涉及逻辑变更,建议写单元测试验证(不强制,纯配置/文档/小改动可跳过);写测试时按下方「测试用例设计」检查
54
54
  4. **不要做无谓的全量编译/构建。** 但——跑针对性的单元测试始终允许且鼓励(见 step3);若本任务就是修测试/构建/基础设施本身,跑测试/构建即验收,应当跑,不算"编译"
55
55
 
56
+ {{include: testcase-design}}
57
+
56
58
  ### 输出
57
59
  实现摘要 + 修改文件列表
58
60
 
@@ -91,6 +93,8 @@ sillyspec run quick --done --change <id> --output "需求:… 根因:… 方
91
93
 
92
94
  格式:\`path::括注\` 一条,\`||\` 分隔多条(括注可省略只留 \`path\`)。不传则「文件:」行回填审计到的实际改动文件单行(仍可事后手改)。
93
95
 
96
+ > ⚠️ --file-notes 只随 step3 --done 同一命令传才生效。CLI 是短进程,run 与 done 是独立进程——step1/step2 的 sillyspec run quick(仅拿提示词)即使带了 --file-notes,也只写进那个短进程的内存、进程结束即丢,不会带到 step3 --done。请在 step3 收尾时连同 --output 一起传。
97
+
94
98
  ### 收尾推荐顺序(模块文档在 --done 前,QUICKLOG 在 --done 后,别记混)
95
99
  1. 【--done 前】命中模块 → 改模块文档 → \`git add -- <模块文档>\`(见下方「模块文档同步」)
96
100
  2. 【--done】\`sillyspec run quick --done --change <id> --output "四字段" [--file-notes "..."]\`(CLI 自动翻完成 + 勾 task + 落盘 QUICKLOG 标题/文件/正文)
@@ -281,7 +281,7 @@ local.yaml 生成结果(已存在/已生成 + project.type + agent 补充的
281
281
 
282
282
  ### YAML 格式
283
283
  \`\`\`yaml
284
- schema_version: 1
284
+ schema_version: 2
285
285
  project: <project-name>
286
286
  source_commit: <git-head-short>
287
287
  generated_at: <now-datetime>
@@ -313,7 +313,7 @@ modules:
313
313
 
314
314
  ### 示例
315
315
  \`\`\`yaml
316
- schema_version: 1
316
+ schema_version: 2
317
317
  project: multi-agent-platform
318
318
  source_commit: abc1234
319
319
  generated_at: 2026-06-02 22:00:00
@@ -197,6 +197,7 @@ export const definition = {
197
197
 
198
198
  3. 生成 verify-result.md 文件,保存到 \`.sillyspec/changes/<change-name>/verify-result.md\`
199
199
  4. 给出结论:PASS / PASS WITH NOTES / FAIL(受风险门控约束)
200
+ 5. **核对 module-impact.md**(若 changes/<change>/module-impact.md 存在):对照本次实际代码变更(git diff)与 module-impact.md 的模块影响矩阵,发现不一致(漏标受影响模块 / 影响类型错误 / 实际未触碰的模块被误标)则在 verify-result.md 标注。module-impact 由 plan 首版生成、execute 各 Wave 更新,verify 是最后一次核对机会(archive 仅终审不再生成)。这是 advisory 核对(不阻断 verify 完成),但 module-impact 与实际严重背离应记为风险。
200
201
 
201
202
  ### verify-result.md 格式
202
203
  \`\`\`markdown
@@ -214,7 +215,7 @@ PASS / PASS WITH NOTES / FAIL
214
215
  ## 探针结果
215
216
  - 未实现标记扫描:...
216
217
  - 关键词覆盖:...
217
- - 测试覆盖:...
218
+ - 测试覆盖(含断言有效性抽查):...
218
219
  - 决策追踪覆盖:...
219
220
  - API 契约对账:...
220
221
  - 代码删除对账:声明修改/新增却被整文件删除 = 高风险;清单未列出的删除 = 未声明(CLI 会用 git diff --name-status HEAD advisory 复核,仅 warn 不阻断——是否 FAIL blocker 由你诚实判定)
package/src/sync.js CHANGED
@@ -517,7 +517,7 @@ export class SyncManager {
517
517
 
518
518
  if (!result) {
519
519
  console.warn(`[sync] 检查审批状态失败: ${changeName}`);
520
- return { status: 'pending', reason: '请求失败' };
520
+ return { status: 'unknown', reason: '请求失败(404/断网/超时),无法核实审批状态' };
521
521
  }
522
522
 
523
523
  // 更新本地 approvals 表
@@ -414,7 +414,7 @@ export function applyWorktree(changeName, { cwd, checkOnly = false, merge = fals
414
414
  if (changedFiles.length === 0) {
415
415
  // 没有变更
416
416
  if (!checkOnly) {
417
- wm.cleanup(changeName);
417
+ wm.cleanup(changeName, { force: true });
418
418
  }
419
419
  result.ok = true;
420
420
  return result;
@@ -646,7 +646,7 @@ export function applyWorktree(changeName, { cwd, checkOnly = false, merge = fals
646
646
 
647
647
  // --- 8. 成功后自动 cleanup(失败不影响整体结果) ---
648
648
  try {
649
- wm.cleanup(changeName);
649
+ wm.cleanup(changeName, { force: true });
650
650
  } catch (cleanupErr) {
651
651
  result.warnings = result.warnings || [];
652
652
  result.warnings.push(`cleanup 失败(不影响应用结果): ${cleanupErr.message}`);
@@ -756,7 +756,7 @@ function applyByMerge(result, changeName, projectRoot, wm) {
756
756
  result.ok = true;
757
757
  try { result.mergeSummary = git(projectRoot, ['log', '--oneline', '-1']); } catch {}
758
758
  try {
759
- wm.cleanup(changeName);
759
+ wm.cleanup(changeName, { force: true });
760
760
  } catch (cleanupErr) {
761
761
  result.warnings = result.warnings || [];
762
762
  result.warnings.push(`cleanup 失败(不影响 merge 结果): ${cleanupErr.message}`);
package/src/worktree.js CHANGED
@@ -236,6 +236,53 @@ export function copyUntrackedEntry(src, dst) {
236
236
  }
237
237
  }
238
238
 
239
+ /**
240
+ * execute 阶段级核验(防空跑谎报,D-002@v1 / FR-04/05/06):聚合 review.changedFiles 的交付文件,
241
+ * 逐个核验存在于 worktree 分支 tree 或 worktree 工作区。两处皆无 = 声称实现但从未落盘。
242
+ *
243
+ * - checked:false:worktreePath 不存在或 branch 不存在(git rev-parse --verify <branch> 失败)
244
+ * → 无法核验(worktree 已丢 / 分支已删),调用方保守提示人工确认。
245
+ * - 逐文件:`git rev-parse --verify --quiet <branch>:<file>` 成功(在分支 tree)或
246
+ * existsSync(worktreePath/<file>)(在工作区)→ verified;两处皆无 → missing。
247
+ * tree 探测用 rev-parse --verify --quiet 而非 `git cat-file -e`:语义等价(按 branch tree 解析
248
+ * blob),但 cat-file 对不存在路径会往 stderr 打 `fatal: path ... does not exist` 噪音
249
+ * (git-helper gitQuiet 只吞退出码不吞 stderr),--quiet 完全静默。
250
+ *
251
+ * git 调用走项目统一入口 gitQuiet(git-helper.js,数组形式 execFileSync 不经 shell,Windows 安全)。
252
+ * 宽松非阻断:本函数只返回结果不 warn/exit,由调用方决定(design 为 warn 不 exit)。
253
+ *
254
+ * @param {object} p
255
+ * @param {string} p.worktreePath - worktree 根目录(绝对路径,meta.worktreePath)
256
+ * @param {string} p.branch - sillyspec/<change> 分支名(核验 tree)
257
+ * @param {string[]} p.changedFiles - review 声称的交付文件(调用方已过滤主仓 repo、非 .sillyspec)
258
+ * @returns {{ missing: string[], verified: string[], checked: boolean }}
259
+ * checked=false 表示无法核验(worktree/分支不存在),调用方保守提示人工确认。
260
+ */
261
+ export function findMissingDeliverables({ worktreePath, branch, changedFiles }) {
262
+ const files = Array.isArray(changedFiles)
263
+ ? changedFiles.filter(f => typeof f === 'string' && f.trim() !== '')
264
+ : []
265
+ // worktree 目录不存在 → 无法核验(目录已丢,如 apply 后 cleanup / 并行 session 清过)
266
+ if (!worktreePath || !existsSync(worktreePath)) {
267
+ return { missing: [], verified: [], checked: false }
268
+ }
269
+ // branch 不存在(rev-parse --verify 失败)→ 无法核验分支 tree
270
+ if (!branch || !gitQuiet(worktreePath, ['rev-parse', '--verify', '--quiet', branch])) {
271
+ return { missing: [], verified: [], checked: false }
272
+ }
273
+ const missing = []
274
+ const verified = []
275
+ for (const raw of files) {
276
+ // 统一正斜杠:review.json 可能落 Windows 反斜杠路径,rev-parse tree 路径一律 /
277
+ const f = raw.replace(/\\/g, '/')
278
+ const inBranch = gitQuiet(worktreePath, ['rev-parse', '--verify', '--quiet', `${branch}:${f}`]) !== null
279
+ const inWorktree = existsSync(join(worktreePath, f))
280
+ if (inBranch || inWorktree) verified.push(raw)
281
+ else missing.push(raw)
282
+ }
283
+ return { missing, verified, checked: true }
284
+ }
285
+
239
286
  export class WorktreeManager {
240
287
  constructor({ cwd, worktreeDir } = {}) {
241
288
  this.cwd = cwd || process.cwd();
@@ -704,8 +751,9 @@ export class WorktreeManager {
704
751
  * 三重清理:git worktree 注册 + worktree 目录 + meta 目录。
705
752
  * @param {string} changeName
706
753
  * @param {{ force?: boolean, maxRetries?: number }} opts
707
- * @returns {{ result: 'cleaned'|'force-cleaned'|'skipped'|'kept'|'partial', mode: string|null, details: string[], residual: string[] }}
708
- * result 取值:cleaned=git remove 成功;force-cleaned=git remove 失败但 fallback 清理完成;
754
+ * @returns {{ result: 'blocked'|'cleaned'|'force-cleaned'|'skipped'|'kept'|'partial', mode: string|null, details: string[], residual: string[] }}
755
+ * result 取值:blocked=有未落主仓交付变更,fail-closed 拒绝清理(需 force 绕过,D-001@v1);
756
+ * cleaned=git remove 成功;force-cleaned=git remove 失败但 fallback 清理完成;
709
757
  * partial=有残留(目录/meta/git 注册未清);skipped=无需清理;kept=native-worktree 保留。
710
758
  * residual:未清干净的路径/引用列表(空数组表示干净)。
711
759
  */
@@ -731,6 +779,21 @@ export class WorktreeManager {
731
779
  return { result: 'kept', mode, details: ['native-worktree: 外部隔离环境,跳过清理'], residual: [] };
732
780
  }
733
781
 
782
+ // fail-closed 保护(D-001@v1):junction 解链与 git worktree remove --force 之前,检查未落主仓交付变更。
783
+ // 清理即蒸发:worktree 有未 commit / 未 apply 到主仓 HEAD 的交付文件,拒绝清理,需显式 --force 绕过。
784
+ // hasUnappliedChanges 判定 main HEAD(byte-identical),git apply --3way 不 commit → apply 后仍判 true,
785
+ // 故 apply 后自动 cleanup 与 execute reset 显式传 force:true 绕过(D-006)。in-place / native-worktree
786
+ // 由 hasUnappliedChanges 内部返回 hasChanges:false,自然跳过保护,零回归。
787
+ if (!force) {
788
+ const check = this.hasUnappliedChanges(name);
789
+ if (check.hasChanges) {
790
+ console.error(`🚫 worktree cleanup 拒绝:${check.changedFiles.length} 个交付变更未落地主工作区 HEAD,清理会丢失代码。`);
791
+ for (const f of check.changedFiles) console.error(` ${f}`);
792
+ console.error(' 请先落地(sillyspec worktree apply <name>)或 commit 到分支,或显式 --force 强制清理。');
793
+ return { result: 'blocked', mode, details: [...details, 'blocked: uncommitted deliverable changes'], residual: [] };
794
+ }
795
+ }
796
+
734
797
  const branch = (meta && meta.branch) || BRANCH_PREFIX + name;
735
798
 
736
799
  // Windows 保护:先解链接 worktree/node_modules(junction 指向主 checkout),
@@ -1045,6 +1108,9 @@ export class WorktreeManager {
1045
1108
  const result = this.cleanup(name);
1046
1109
  if (result.result === 'cleaned' || result.result === 'force-cleaned') {
1047
1110
  fixed.push(`cleaned stale: ${name}`);
1111
+ } else if (result.result === 'blocked') {
1112
+ console.error(`🚫 拒绝清理过期 worktree ${name}: 有未落主仓交付变更,请先 sillyspec worktree apply ${name} 或 commit 到分支,再重试 doctor --fix`);
1113
+ unfixable.push(`cleanup blocked: ${name}(有未落主仓交付变更)`);
1048
1114
  } else {
1049
1115
  unfixable.push(`cleanup skipped: ${name}`);
1050
1116
  }
@@ -1093,7 +1159,10 @@ export class WorktreeManager {
1093
1159
 
1094
1160
  const worktreePath = meta.worktreePath;
1095
1161
  if (!worktreePath || !existsSync(worktreePath)) {
1096
- return { hasChanges: false, changedFiles: [], reason: 'worktree dir not found' };
1162
+ // 目录不存在:无法判定是否有未落代码——分支可能有已 commit 但未 apply 的交付(execute 批量完成
1163
+ // cleanup 删分支 ref 盲区,memory execute-batch-cleanup-deletes-branch-recovery)。保守保留
1164
+ // (hasChanges:true),防 cleanup 误删分支让 commit 变 dangling。目录真丢时用户 --force 强清。
1165
+ return { hasChanges: true, changedFiles: [], reason: 'worktree dir not found (conservative keep)' };
1097
1166
  }
1098
1167
 
1099
1168
  // in-place 模式没有隔离目录,不算有未 apply 的变更
@@ -1107,16 +1176,22 @@ export class WorktreeManager {
1107
1176
 
1108
1177
  const diffBase = meta.baselineCommit || meta.baseHash;
1109
1178
  if (!diffBase) {
1110
- return { hasChanges: false, changedFiles: [], reason: 'no diff base' };
1179
+ // 有 meta 但缺 diff 起点:无法判定是否有未落代码,保守保留(防 cleanup 删分支)
1180
+ return { hasChanges: true, changedFiles: [], reason: 'no diff base (conservative keep)' };
1111
1181
  }
1112
1182
 
1113
1183
  const isDeliverable = f => f && !f.startsWith('.sillyspec/') && f !== 'meta.json';
1114
1184
  try {
1115
1185
  // 1) 候选交付变更(worktree 工作区相对 diffBase)。--no-renames:rename 退化成 D+A,两侧文件都进集
1116
- const tracked = (gitQuiet(worktreePath, ['diff', '--no-renames', '--name-only', diffBase], { timeout: 30000 }) || '')
1117
- .split('\n').filter(Boolean).filter(isDeliverable);
1118
- const untracked = (gitQuiet(worktreePath, ['ls-files', '--others', '--exclude-standard'], { timeout: 30000 }) || '')
1119
- .split('\n').filter(Boolean).filter(isDeliverable);
1186
+ const trackedRaw = gitQuiet(worktreePath, ['diff', '--no-renames', '--name-only', diffBase], { timeout: 30000 });
1187
+ const untrackedRaw = gitQuiet(worktreePath, ['ls-files', '--others', '--exclude-standard'], { timeout: 30000 });
1188
+ // git 检测失败(返回 null,如 worktree .git 半坏/目录部分删除)→ 不能当"无改动",保守保留。
1189
+ // 只有 git 成功返回空才判"真无改动"(可安全清理)。
1190
+ if (trackedRaw === null || untrackedRaw === null) {
1191
+ return { hasChanges: true, changedFiles: [], reason: `git detection failed (conservative keep): tracked=${trackedRaw === null ? 'null' : 'ok'} untracked=${untrackedRaw === null ? 'null' : 'ok'}` };
1192
+ }
1193
+ const tracked = trackedRaw.split('\n').filter(Boolean).filter(isDeliverable);
1194
+ const untracked = untrackedRaw.split('\n').filter(Boolean).filter(isDeliverable);
1120
1195
 
1121
1196
  if (tracked.length === 0 && untracked.length === 0) {
1122
1197
  return { hasChanges: false, changedFiles: [], reason: 'no changes in worktree' };
@@ -0,0 +1,12 @@
1
+ ### 测试用例设计(写测试时按此检查)
2
+
3
+ 写测试是为了拦住回归,不是"有测试文件"。
4
+ **基础习惯照做、不展开**:FIRST(快/独立/可重复/自校验/及时)、金字塔分层(单元为主/集成少量/E2E 只冒烟)、AAA 结构、测试名描述场景、测试数据轻量构造(Object Mother/Builder)、覆盖率不追数字。
5
+
6
+ 逐条对照检查:
7
+ 1. **覆盖**:正常路径 + 边界 + 异常/非法输入,不只写业务正例;单测全绿 ≠ 集成正确。
8
+ 2. **断言**:断言真实输出/副作用,禁止只"不抛错"的空断言;不 mock 被测方法自身。
9
+ 3. **行为 vs 实现**:走公开 API 测行为;重构后功能不变,测试不应失败。
10
+ 4. **契约与回归**:覆盖本次改动会破坏的既有行为与相邻模块接口;改公共签名/文案/常量时同步修复受影响既有测试并跑全量,禁止删断言凑绿。
11
+ 5. **时间敏感分支**:超时/重试/并发分支别真等——超时参数做成可注入,测试传毫秒级极小值或注入可控时钟推进,再断言超时后的行为(走哪个分支/返回什么/资源是否回收)。
12
+ 6. **隔离确定性**:独立 fixture、可重复跑,不依赖共享状态/执行顺序。
@@ -21,6 +21,7 @@ grep -rl "<关键词>" <源码目录>/ --include="*.java" --include="*.js" --inc
21
21
  2. 对每个 task,**递归**检查对应模块目录及其**所有子目录**下是否存在测试文件(*test*、*spec*、*Test*、*Spec*)——包括模块内 co-located 的 tests/ 目录(如 `backend/app/modules/*/tests/`),**不要只看顶层 `tests/`**,否则会把测试就在模块内部的项目误判为“无测试”
22
22
  3. 没有测试文件的 task 标记为 ⚠️ 缺少测试
23
23
  4. **集成盲区提示**:测试文件存在 ≠ 集成正确——路由/layout 守卫重定向、跨模块装配这类集成 bug 组件单测覆盖不到,只有集成/冒烟/E2E 才暴露。对路由/layout/跨进程装配敏感的 task,额外检查是否有集成冒烟覆盖;无则标 ⚠️ 集成层未验证(需人工或部署确认)
24
+ 5. **断言有效性抽查(与 execute「测试用例设计」闭环)**:测试文件存在 ≠ 测试有效。对最能代表本次变更行为的 2-3 个核心测试做抽查:① 断言验证真实输出/副作用,不是只"不抛错"的空断言、只测 getter/setter;② 覆盖边界/异常分支,不只业务正例;③ 走公开 API 测行为,不测实现细节(重构后功能不变测试不应失败)。不达标标 ⚠️ 断言薄弱或无效(advisory:CLI 不阻断,是否 FAIL 由你诚实判定;execute 已按「测试用例设计」写的,这里只抽查核验,不重审全量)
24
25
 
25
26
  **探针 4:决策追踪覆盖探针(如存在 decisions.md)**
26
27
  1. 从 decisions.md 提取所有当前版本 D-xxx@vN
@@ -0,0 +1,92 @@
1
+ ---
2
+ name: sillyspec-onboard
3
+ description: 为未安装 SillySpec 的全新项目接入规范驱动开发工作流——引导安装 CLI、初始化接入、建立项目库,并把「用 SillySpec 走流程」确立为项目默认工作方式。适合用户说"给这个项目接上 sillyspec"或新项目开工前确认流程。
4
+ ---
5
+
6
+ ## 交互规范
7
+
8
+ **当需要用户从多个选项中做出选择时,必须使用 Claude Code 内置的 AskUserQuestion 工具,将选项以参数传入。** 涉及是否全局安装 CLI / 是否建立项目库等决策,让用户亲手拍板,不替用户做决定。
9
+
10
+ ## 何时使用
11
+
12
+ - 目标项目**没有安装/未接入** SillySpec(判断:项目根无 `.sillyspec/` 目录;或 `sillyspec --version` 命令不可用)
13
+ - 用户想让 SillySpec 成为该项目的开发流程控制器
14
+
15
+ ## 铁律
16
+
17
+ - **必须用 exec 工具(shell)执行 CLI,不编造命令**;命中的命令固定为下方公开 CLI,不自行发明子命令
18
+ - 本 skill 只做**接入动作**(安装/初始化/验证),不写业务代码、不改项目源码
19
+ - 全程在主工作区根执行 CLI,不进入 worktree
20
+ - 每个决策点用 AskUserQuestion 让用户拍板,不静默代选
21
+
22
+ ## 核心流程
23
+
24
+ ### Step 0: 判定接入状态
25
+
26
+ ```bash
27
+ # 项目根是否已有 .sillyspec/(已接入)
28
+ ls -d .sillyspec 2>/dev/null
29
+ # CLI 是否可用
30
+ sillyspec --version 2>/dev/null
31
+ ```
32
+
33
+ - 已接入(`.sillyspec/` 存在)→ 不重复接入,提示用户项目已用 SillySpec 管理,退出。
34
+ - 未接入但 CLI 已装 → 跳到 Step 2。
35
+ - 都无 → 继续 Step 1。
36
+
37
+ ### Step 1: 引导安装 CLI(全局)
38
+
39
+ ```bash
40
+ npm install -g sillyspec
41
+ ```
42
+
43
+ - 若当前环境沙箱/权限导致 `npm -g` 失败,提示用户用 `! npm install -g sillyspec` 手动执行(输出直达会话)。
44
+ - 装完验证:`sillyspec --version` 可输出版本号。
45
+
46
+ ### Step 2: 初始化接入项目
47
+
48
+ ```bash
49
+ sillyspec init
50
+ ```
51
+
52
+ `init` 会自动:创建 `.sillyspec/`(项目注册、文档中心、知识库、运行时进度库)、检测项目中的 AI 工具(Claude Code 等)、给 `CLAUDE.md` 注入 SillySpec 受管段(若项目已有文件)或生成完整指引模板(若没有)、安装对应命令模板/MCP 工具。
53
+
54
+ - 新项目为空目录时建议走 `sillyspec run scan`(见 Step 3)补齐项目库;已有代码但未接入的存量项目,先确认项目状态再决定是否 scan。
55
+ - 验证产物:
56
+ ```bash
57
+ ls .sillyspec/projects/ # 项目注册表
58
+ grep -c "SillySpec" CLAUDE.md # 受管段/指引已注入(>0)
59
+ ```
60
+
61
+ ### Step 3: 建立项目库(知识基石)
62
+
63
+ ```bash
64
+ sillyspec run scan
65
+ ```
66
+
67
+ 把项目的代码风格(CONVENTIONS.md)、架构(ARCHITECTURE.md)沉淀为后续 agent 的检索知识。这是「让后续 agent 不瞎猜」的关键一步,新项目建议立即做。
68
+
69
+ ### Step 4: 价值认知锚 —— 让后续 agent 愿意用
70
+
71
+ 接入完成后,**让"用 SillySpec 走流程"成为项目默认工作方式**靠的是已落盘的载体,不是本 skill(skill 是一次性的):
72
+
73
+ 1. 确认价值锚已就位:
74
+ - `CLAUDE.md` 含 SillySpec 段(Step 2 已验证)——agent 每次会话读取项目说明书时都会看到「禁止绕过流程 / 新功能走 brainstorm→plan→execute→verify→archive / 小修复走 quick」。
75
+ - `.claude/skills/` 含 SillySpec 持续 skill(init 已复制 plan/quick/brainstorm 等)——agent 在开发任务中 `/plan`、`/quick` 即走流程。
76
+ - `.sillyspec/` 进度库已建——进度自动落盘,多 agent 并发不互相覆盖。
77
+
78
+ 2. 向用户/后续 agent 说明价值(一句话口径,复用 init 注入的陈述,不另造文案):
79
+ > SillySpec 是给 Agent 调用的 CLI 流程控制器:你通过 CLI 告诉它"我在哪",它告诉你"下一步做什么";你执行步骤,它校验产出、推进状态,人类只在关键决策点介入审批。多 agent 同时操作代码时进度统一落盘、不丢不回滚。
80
+
81
+ 3. 给后续 agent 一个「开工首查」示例:
82
+ ```bash
83
+ sillyspec progress show # 看项目当前阶段/进行中变更
84
+ sillyspec run <stage> # 续跑当前阶段(brainstorm/plan/execute/verify/archive)
85
+ ```
86
+
87
+ ## 分发与安装(本 skill 是引导型)
88
+
89
+ 本 skill 是「接入引导」,只在新项目接入时用一次,**不应随项目复制进 `.claude/skills/` 长期留存**。使用方式二选一:
90
+
91
+ - **项目级一次性**:复制本 `SKILL.md` 到目标项目 `.claude/skills/sillyspec-onboard/SKILL.md`,接入完成后删除。
92
+ - **全局可用**:复制到 `~/.claude/skills/sillyspec-onboard/SKILL.md`(或 `~/.config/claude/skills/`,随 Claude Code 版本),任何新项目都能呼起 `/sillyspec-onboard`。
@@ -1,15 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>SillySpec Dashboard</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com">
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
- <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
10
- </head>
11
- <body style="background-color: #0A0A0B; margin: 0;">
12
- <div id="app"></div>
13
- <script type="module" src="/src/main.js"></script>
14
- </body>
15
- </html>