sillyspec 3.27.2 → 3.27.6

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 (398) hide show
  1. package/.claude/skills/sillyspec-execute/SKILL.md +8 -0
  2. package/.claude/skills/sillyspec-quick/SKILL.md +2 -1
  3. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  4. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-auto/SKILL.md +93 -0
  5. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  6. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  7. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-continue/SKILL.md +47 -0
  8. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  9. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-execute/SKILL.md +214 -0
  10. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-explore/SKILL.md +102 -0
  11. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-export/SKILL.md +57 -0
  12. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-init/SKILL.md +177 -0
  13. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  14. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  15. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  16. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-resume/SKILL.md +70 -0
  17. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  18. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-state/SKILL.md +64 -0
  19. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-status/SKILL.md +54 -0
  20. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  21. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
  22. package/.claude/worktrees/sillyspec-data-model-research/.gitattributes +2 -0
  23. package/.claude/worktrees/sillyspec-data-model-research/.husky/pre-push +20 -0
  24. package/.claude/worktrees/sillyspec-data-model-research/README.md +203 -0
  25. package/.claude/worktrees/sillyspec-data-model-research/SKILL.md +91 -0
  26. package/.claude/worktrees/sillyspec-data-model-research/bin/sillyspec.js +2 -0
  27. package/.claude/worktrees/sillyspec-data-model-research/logo.jpg +0 -0
  28. package/.claude/worktrees/sillyspec-data-model-research/package-lock.json +977 -0
  29. package/.claude/worktrees/sillyspec-data-model-research/package.json +39 -0
  30. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
  31. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  32. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/favicon.jpg +0 -0
  33. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/index.html +16 -0
  34. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/logo.jpg +0 -0
  35. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-dashboard.html +836 -0
  36. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-overview.html +256 -0
  37. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/package.json +25 -0
  38. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/executor.js +95 -0
  39. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/index.js +679 -0
  40. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/parser.js +526 -0
  41. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/watcher.js +357 -0
  42. package/.claude/worktrees/sillyspec-data-model-research/src/agent-session-log.js +803 -0
  43. package/.claude/worktrees/sillyspec-data-model-research/src/change-list.js +290 -0
  44. package/.claude/worktrees/sillyspec-data-model-research/src/change-risk-profile.js +270 -0
  45. package/.claude/worktrees/sillyspec-data-model-research/src/check-primitives.js +65 -0
  46. package/.claude/worktrees/sillyspec-data-model-research/src/classify-change.js +109 -0
  47. package/.claude/worktrees/sillyspec-data-model-research/src/commit-suggest.js +202 -0
  48. package/.claude/worktrees/sillyspec-data-model-research/src/config-cat.js +88 -0
  49. package/.claude/worktrees/sillyspec-data-model-research/src/config-schema.js +328 -0
  50. package/.claude/worktrees/sillyspec-data-model-research/src/constants.js +97 -0
  51. package/.claude/worktrees/sillyspec-data-model-research/src/contract-matrix.js +480 -0
  52. package/.claude/worktrees/sillyspec-data-model-research/src/datetime.js +20 -0
  53. package/.claude/worktrees/sillyspec-data-model-research/src/db-engine.js +55 -0
  54. package/.claude/worktrees/sillyspec-data-model-research/src/db.js +381 -0
  55. package/.claude/worktrees/sillyspec-data-model-research/src/decision-distill.js +517 -0
  56. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/local-agent.js +155 -0
  57. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/sillyhub-mcp.js +245 -0
  58. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/probe.js +246 -0
  59. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/strategy.js +121 -0
  60. package/.claude/worktrees/sillyspec-data-model-research/src/docs-check.js +825 -0
  61. package/.claude/worktrees/sillyspec-data-model-research/src/docs-debt.js +393 -0
  62. package/.claude/worktrees/sillyspec-data-model-research/src/docs-gate.js +107 -0
  63. package/.claude/worktrees/sillyspec-data-model-research/src/doctor-diagnostics.js +1099 -0
  64. package/.claude/worktrees/sillyspec-data-model-research/src/endpoint-extractor.js +420 -0
  65. package/.claude/worktrees/sillyspec-data-model-research/src/foreign-declared.js +78 -0
  66. package/.claude/worktrees/sillyspec-data-model-research/src/fs-atomic.js +73 -0
  67. package/.claude/worktrees/sillyspec-data-model-research/src/git-helper.js +150 -0
  68. package/.claude/worktrees/sillyspec-data-model-research/src/hooks/claude-pre-tool-use.cjs +130 -0
  69. package/.claude/worktrees/sillyspec-data-model-research/src/hooks/worktree-guard.js +892 -0
  70. package/.claude/worktrees/sillyspec-data-model-research/src/index.js +2798 -0
  71. package/.claude/worktrees/sillyspec-data-model-research/src/init.js +626 -0
  72. package/.claude/worktrees/sillyspec-data-model-research/src/knowledge-match.js +231 -0
  73. package/.claude/worktrees/sillyspec-data-model-research/src/local-detect.js +128 -0
  74. package/.claude/worktrees/sillyspec-data-model-research/src/local-register.js +104 -0
  75. package/.claude/worktrees/sillyspec-data-model-research/src/machine-interface.js +543 -0
  76. package/.claude/worktrees/sillyspec-data-model-research/src/migrate.js +127 -0
  77. package/.claude/worktrees/sillyspec-data-model-research/src/module-impact.js +166 -0
  78. package/.claude/worktrees/sillyspec-data-model-research/src/module-resolve.js +222 -0
  79. package/.claude/worktrees/sillyspec-data-model-research/src/modules.js +580 -0
  80. package/.claude/worktrees/sillyspec-data-model-research/src/progress/change-registry.js +336 -0
  81. package/.claude/worktrees/sillyspec-data-model-research/src/progress/consistency-doctor.js +355 -0
  82. package/.claude/worktrees/sillyspec-data-model-research/src/progress/shared.js +36 -0
  83. package/.claude/worktrees/sillyspec-data-model-research/src/progress/stage-machine.js +594 -0
  84. package/.claude/worktrees/sillyspec-data-model-research/src/progress/step-store.js +265 -0
  85. package/.claude/worktrees/sillyspec-data-model-research/src/progress.js +1304 -0
  86. package/.claude/worktrees/sillyspec-data-model-research/src/quick-recommend.js +115 -0
  87. package/.claude/worktrees/sillyspec-data-model-research/src/quicklog.js +852 -0
  88. package/.claude/worktrees/sillyspec-data-model-research/src/review-tier.js +77 -0
  89. package/.claude/worktrees/sillyspec-data-model-research/src/run/command.js +1595 -0
  90. package/.claude/worktrees/sillyspec-data-model-research/src/run/complete-handlers.js +1552 -0
  91. package/.claude/worktrees/sillyspec-data-model-research/src/run/complete.js +1259 -0
  92. package/.claude/worktrees/sillyspec-data-model-research/src/run/concurrent-detect.js +156 -0
  93. package/.claude/worktrees/sillyspec-data-model-research/src/run/gates.js +1084 -0
  94. package/.claude/worktrees/sillyspec-data-model-research/src/run/multi-repo-context.js +208 -0
  95. package/.claude/worktrees/sillyspec-data-model-research/src/run/next.js +128 -0
  96. package/.claude/worktrees/sillyspec-data-model-research/src/run/prompt.js +843 -0
  97. package/.claude/worktrees/sillyspec-data-model-research/src/run/quick-audit.js +186 -0
  98. package/.claude/worktrees/sillyspec-data-model-research/src/run/scan-profile.js +269 -0
  99. package/.claude/worktrees/sillyspec-data-model-research/src/run/shared.js +1562 -0
  100. package/.claude/worktrees/sillyspec-data-model-research/src/run/stage.js +584 -0
  101. package/.claude/worktrees/sillyspec-data-model-research/src/run.js +23 -0
  102. package/.claude/worktrees/sillyspec-data-model-research/src/scan-diff.js +388 -0
  103. package/.claude/worktrees/sillyspec-data-model-research/src/scan-postcheck.js +512 -0
  104. package/.claude/worktrees/sillyspec-data-model-research/src/scan-staleness.js +95 -0
  105. package/.claude/worktrees/sillyspec-data-model-research/src/setup.js +414 -0
  106. package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/client.js +597 -0
  107. package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/config.js +62 -0
  108. package/.claude/worktrees/sillyspec-data-model-research/src/spec-dir-typo.js +50 -0
  109. package/.claude/worktrees/sillyspec-data-model-research/src/spec-sync.js +358 -0
  110. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-engine.js +171 -0
  111. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-spec.js +550 -0
  112. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract.js +889 -0
  113. package/.claude/worktrees/sillyspec-data-model-research/src/stage-review.js +597 -0
  114. package/.claude/worktrees/sillyspec-data-model-research/src/stages/archive.js +199 -0
  115. package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm-auto.js +236 -0
  116. package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm.js +617 -0
  117. package/.claude/worktrees/sillyspec-data-model-research/src/stages/cmd-existence.js +145 -0
  118. package/.claude/worktrees/sillyspec-data-model-research/src/stages/doctor.js +540 -0
  119. package/.claude/worktrees/sillyspec-data-model-research/src/stages/execute.js +1275 -0
  120. package/.claude/worktrees/sillyspec-data-model-research/src/stages/explore.js +43 -0
  121. package/.claude/worktrees/sillyspec-data-model-research/src/stages/index.js +29 -0
  122. package/.claude/worktrees/sillyspec-data-model-research/src/stages/knowledge.js +514 -0
  123. package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan-postcheck.js +1344 -0
  124. package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan.js +650 -0
  125. package/.claude/worktrees/sillyspec-data-model-research/src/stages/quick.js +167 -0
  126. package/.claude/worktrees/sillyspec-data-model-research/src/stages/scan.js +608 -0
  127. package/.claude/worktrees/sillyspec-data-model-research/src/stages/status.js +64 -0
  128. package/.claude/worktrees/sillyspec-data-model-research/src/stages/verify.js +246 -0
  129. package/.claude/worktrees/sillyspec-data-model-research/src/sync.js +1470 -0
  130. package/.claude/worktrees/sillyspec-data-model-research/src/task-review.js +1498 -0
  131. package/.claude/worktrees/sillyspec-data-model-research/src/taskcard.js +210 -0
  132. package/.claude/worktrees/sillyspec-data-model-research/src/verify-postcheck.js +1718 -0
  133. package/.claude/worktrees/sillyspec-data-model-research/src/verify-probes.js +277 -0
  134. package/.claude/worktrees/sillyspec-data-model-research/src/version.js +19 -0
  135. package/.claude/worktrees/sillyspec-data-model-research/src/workflow.js +600 -0
  136. package/.claude/worktrees/sillyspec-data-model-research/src/workspace.js +125 -0
  137. package/.claude/worktrees/sillyspec-data-model-research/src/worktree-apply.js +1446 -0
  138. package/.claude/worktrees/sillyspec-data-model-research/src/worktree-deps.js +576 -0
  139. package/.claude/worktrees/sillyspec-data-model-research/src/worktree.js +1715 -0
  140. package/.claude/worktrees/sillyspec-data-model-research/templates/claude-instruction.md +23 -0
  141. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/taskcard-rules.md +28 -0
  142. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/testcase-design.md +12 -0
  143. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/verify-probes.md +32 -0
  144. package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/archive-impact.yaml +79 -0
  145. package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/scan-docs.yaml +132 -0
  146. package/.worktrees/8d2d0392/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  147. package/.worktrees/8d2d0392/.claude/skills/sillyspec-auto/SKILL.md +93 -0
  148. package/.worktrees/8d2d0392/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  149. package/.worktrees/8d2d0392/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  150. package/.worktrees/8d2d0392/.claude/skills/sillyspec-continue/SKILL.md +47 -0
  151. package/.worktrees/8d2d0392/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  152. package/.worktrees/8d2d0392/.claude/skills/sillyspec-execute/SKILL.md +214 -0
  153. package/.worktrees/8d2d0392/.claude/skills/sillyspec-explore/SKILL.md +102 -0
  154. package/.worktrees/8d2d0392/.claude/skills/sillyspec-export/SKILL.md +57 -0
  155. package/.worktrees/8d2d0392/.claude/skills/sillyspec-init/SKILL.md +177 -0
  156. package/.worktrees/8d2d0392/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  157. package/.worktrees/8d2d0392/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  158. package/.worktrees/8d2d0392/.claude/skills/sillyspec-propose/SKILL.md +87 -0
  159. package/.worktrees/8d2d0392/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  160. package/.worktrees/8d2d0392/.claude/skills/sillyspec-resume/SKILL.md +70 -0
  161. package/.worktrees/8d2d0392/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  162. package/.worktrees/8d2d0392/.claude/skills/sillyspec-state/SKILL.md +64 -0
  163. package/.worktrees/8d2d0392/.claude/skills/sillyspec-status/SKILL.md +54 -0
  164. package/.worktrees/8d2d0392/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  165. package/.worktrees/8d2d0392/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
  166. package/.worktrees/8d2d0392/.claude/skills/verify-per-user/SKILL.md +6 -0
  167. package/.worktrees/8d2d0392/.gitattributes +2 -0
  168. package/.worktrees/8d2d0392/.husky/pre-push +20 -0
  169. package/.worktrees/8d2d0392/README.md +203 -0
  170. package/.worktrees/8d2d0392/SKILL.md +91 -0
  171. package/.worktrees/8d2d0392/architecture-analysis.md +448 -0
  172. package/.worktrees/8d2d0392/bin/sillyspec.js +2 -0
  173. package/.worktrees/8d2d0392/eval/README.md +86 -0
  174. package/.worktrees/8d2d0392/eval/eval.config.json +28 -0
  175. package/.worktrees/8d2d0392/eval/lib/adapters/cli.mjs +63 -0
  176. package/.worktrees/8d2d0392/eval/lib/adapters/dryrun.mjs +27 -0
  177. package/.worktrees/8d2d0392/eval/lib/config.mjs +82 -0
  178. package/.worktrees/8d2d0392/eval/lib/db.mjs +76 -0
  179. package/.worktrees/8d2d0392/eval/lib/report.mjs +120 -0
  180. package/.worktrees/8d2d0392/eval/lib/runner.mjs +157 -0
  181. package/.worktrees/8d2d0392/eval/lib/tasks.mjs +73 -0
  182. package/.worktrees/8d2d0392/eval/lib/util.mjs +107 -0
  183. package/.worktrees/8d2d0392/eval/lib/workdir.mjs +30 -0
  184. package/.worktrees/8d2d0392/eval/run.mjs +83 -0
  185. package/.worktrees/8d2d0392/eval/tasks/001-slugify/files/package.json +6 -0
  186. package/.worktrees/8d2d0392/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
  187. package/.worktrees/8d2d0392/eval/tasks/001-slugify/task.json +6 -0
  188. package/.worktrees/8d2d0392/eval/tasks/001-slugify/verify.mjs +28 -0
  189. package/.worktrees/8d2d0392/eval/tasks/002-each-day/files/package.json +6 -0
  190. package/.worktrees/8d2d0392/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
  191. package/.worktrees/8d2d0392/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
  192. package/.worktrees/8d2d0392/eval/tasks/002-each-day/task.json +6 -0
  193. package/.worktrees/8d2d0392/eval/tasks/002-each-day/verify.mjs +31 -0
  194. package/.worktrees/8d2d0392/eval/tasks/003-word-report/files/data/notes.txt +5 -0
  195. package/.worktrees/8d2d0392/eval/tasks/003-word-report/files/package.json +6 -0
  196. package/.worktrees/8d2d0392/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
  197. package/.worktrees/8d2d0392/eval/tasks/003-word-report/task.json +6 -0
  198. package/.worktrees/8d2d0392/eval/tasks/003-word-report/verify.mjs +38 -0
  199. package/.worktrees/8d2d0392/logo.jpg +0 -0
  200. package/.worktrees/8d2d0392/package-lock.json +977 -0
  201. package/.worktrees/8d2d0392/package.json +41 -0
  202. package/.worktrees/8d2d0392/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
  203. package/.worktrees/8d2d0392/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  204. package/.worktrees/8d2d0392/packages/dashboard/dist/favicon.jpg +0 -0
  205. package/.worktrees/8d2d0392/packages/dashboard/dist/index.html +16 -0
  206. package/.worktrees/8d2d0392/packages/dashboard/dist/logo.jpg +0 -0
  207. package/.worktrees/8d2d0392/packages/dashboard/dist/prototype-dashboard.html +836 -0
  208. package/.worktrees/8d2d0392/packages/dashboard/dist/prototype-overview.html +256 -0
  209. package/.worktrees/8d2d0392/packages/dashboard/package.json +25 -0
  210. package/.worktrees/8d2d0392/packages/dashboard/server/executor.js +95 -0
  211. package/.worktrees/8d2d0392/packages/dashboard/server/index.js +679 -0
  212. package/.worktrees/8d2d0392/packages/dashboard/server/parser.js +526 -0
  213. package/.worktrees/8d2d0392/packages/dashboard/server/watcher.js +357 -0
  214. package/.worktrees/8d2d0392/src/agent-session-log.js +803 -0
  215. package/.worktrees/8d2d0392/src/change-list.js +290 -0
  216. package/.worktrees/8d2d0392/src/change-risk-profile.js +339 -0
  217. package/.worktrees/8d2d0392/src/check-primitives.js +65 -0
  218. package/.worktrees/8d2d0392/src/classify-change.js +109 -0
  219. package/.worktrees/8d2d0392/src/commit-suggest.js +202 -0
  220. package/.worktrees/8d2d0392/src/config-cat.js +88 -0
  221. package/.worktrees/8d2d0392/src/config-schema.js +328 -0
  222. package/.worktrees/8d2d0392/src/constants.js +97 -0
  223. package/.worktrees/8d2d0392/src/contract-matrix.js +533 -0
  224. package/.worktrees/8d2d0392/src/datetime.js +20 -0
  225. package/.worktrees/8d2d0392/src/db-engine.js +55 -0
  226. package/.worktrees/8d2d0392/src/db.js +381 -0
  227. package/.worktrees/8d2d0392/src/decision-distill.js +517 -0
  228. package/.worktrees/8d2d0392/src/dispatch/backends/local-agent.js +155 -0
  229. package/.worktrees/8d2d0392/src/dispatch/backends/sillyhub-mcp.js +245 -0
  230. package/.worktrees/8d2d0392/src/dispatch/probe.js +246 -0
  231. package/.worktrees/8d2d0392/src/dispatch/strategy.js +121 -0
  232. package/.worktrees/8d2d0392/src/docs-check.js +825 -0
  233. package/.worktrees/8d2d0392/src/docs-debt.js +393 -0
  234. package/.worktrees/8d2d0392/src/docs-gate.js +107 -0
  235. package/.worktrees/8d2d0392/src/doctor-diagnostics.js +1099 -0
  236. package/.worktrees/8d2d0392/src/endpoint-extractor.js +409 -0
  237. package/.worktrees/8d2d0392/src/foreign-declared.js +78 -0
  238. package/.worktrees/8d2d0392/src/fs-atomic.js +73 -0
  239. package/.worktrees/8d2d0392/src/git-helper.js +150 -0
  240. package/.worktrees/8d2d0392/src/hooks/claude-pre-tool-use.cjs +130 -0
  241. package/.worktrees/8d2d0392/src/hooks/worktree-guard.js +892 -0
  242. package/.worktrees/8d2d0392/src/index.js +2854 -0
  243. package/.worktrees/8d2d0392/src/init.js +626 -0
  244. package/.worktrees/8d2d0392/src/knowledge-match.js +231 -0
  245. package/.worktrees/8d2d0392/src/local-detect.js +128 -0
  246. package/.worktrees/8d2d0392/src/local-register.js +104 -0
  247. package/.worktrees/8d2d0392/src/machine-interface.js +543 -0
  248. package/.worktrees/8d2d0392/src/migrate.js +127 -0
  249. package/.worktrees/8d2d0392/src/module-impact.js +166 -0
  250. package/.worktrees/8d2d0392/src/module-resolve.js +222 -0
  251. package/.worktrees/8d2d0392/src/modules.js +580 -0
  252. package/.worktrees/8d2d0392/src/plan-adopt-waves.js +126 -0
  253. package/.worktrees/8d2d0392/src/progress/change-registry.js +336 -0
  254. package/.worktrees/8d2d0392/src/progress/consistency-doctor.js +355 -0
  255. package/.worktrees/8d2d0392/src/progress/shared.js +36 -0
  256. package/.worktrees/8d2d0392/src/progress/stage-machine.js +594 -0
  257. package/.worktrees/8d2d0392/src/progress/step-store.js +265 -0
  258. package/.worktrees/8d2d0392/src/progress.js +1304 -0
  259. package/.worktrees/8d2d0392/src/quick-recommend.js +115 -0
  260. package/.worktrees/8d2d0392/src/quicklog.js +883 -0
  261. package/.worktrees/8d2d0392/src/review-tier.js +77 -0
  262. package/.worktrees/8d2d0392/src/run/command.js +1641 -0
  263. package/.worktrees/8d2d0392/src/run/complete-handlers.js +1552 -0
  264. package/.worktrees/8d2d0392/src/run/complete.js +1259 -0
  265. package/.worktrees/8d2d0392/src/run/concurrent-detect.js +156 -0
  266. package/.worktrees/8d2d0392/src/run/gates.js +1094 -0
  267. package/.worktrees/8d2d0392/src/run/multi-repo-context.js +208 -0
  268. package/.worktrees/8d2d0392/src/run/next.js +128 -0
  269. package/.worktrees/8d2d0392/src/run/prompt.js +843 -0
  270. package/.worktrees/8d2d0392/src/run/quick-audit.js +186 -0
  271. package/.worktrees/8d2d0392/src/run/scan-profile.js +269 -0
  272. package/.worktrees/8d2d0392/src/run/shared.js +1562 -0
  273. package/.worktrees/8d2d0392/src/run/stage.js +587 -0
  274. package/.worktrees/8d2d0392/src/run.js +23 -0
  275. package/.worktrees/8d2d0392/src/scan-diff.js +388 -0
  276. package/.worktrees/8d2d0392/src/scan-postcheck.js +524 -0
  277. package/.worktrees/8d2d0392/src/scan-staleness.js +95 -0
  278. package/.worktrees/8d2d0392/src/setup.js +414 -0
  279. package/.worktrees/8d2d0392/src/sillyhub-mcp/client.js +597 -0
  280. package/.worktrees/8d2d0392/src/sillyhub-mcp/config.js +62 -0
  281. package/.worktrees/8d2d0392/src/spec-dir-typo.js +50 -0
  282. package/.worktrees/8d2d0392/src/spec-sync.js +358 -0
  283. package/.worktrees/8d2d0392/src/stage-contract-engine.js +171 -0
  284. package/.worktrees/8d2d0392/src/stage-contract-spec.js +551 -0
  285. package/.worktrees/8d2d0392/src/stage-contract.js +948 -0
  286. package/.worktrees/8d2d0392/src/stage-review.js +597 -0
  287. package/.worktrees/8d2d0392/src/stages/archive.js +199 -0
  288. package/.worktrees/8d2d0392/src/stages/brainstorm-auto.js +285 -0
  289. package/.worktrees/8d2d0392/src/stages/brainstorm.js +624 -0
  290. package/.worktrees/8d2d0392/src/stages/cmd-existence.js +145 -0
  291. package/.worktrees/8d2d0392/src/stages/doctor.js +540 -0
  292. package/.worktrees/8d2d0392/src/stages/execute.js +1277 -0
  293. package/.worktrees/8d2d0392/src/stages/explore.js +43 -0
  294. package/.worktrees/8d2d0392/src/stages/index.js +29 -0
  295. package/.worktrees/8d2d0392/src/stages/knowledge.js +514 -0
  296. package/.worktrees/8d2d0392/src/stages/plan-postcheck.js +1398 -0
  297. package/.worktrees/8d2d0392/src/stages/plan.js +651 -0
  298. package/.worktrees/8d2d0392/src/stages/quick.js +167 -0
  299. package/.worktrees/8d2d0392/src/stages/scan.js +608 -0
  300. package/.worktrees/8d2d0392/src/stages/status.js +64 -0
  301. package/.worktrees/8d2d0392/src/stages/verify.js +246 -0
  302. package/.worktrees/8d2d0392/src/sync.js +1470 -0
  303. package/.worktrees/8d2d0392/src/task-review.js +1498 -0
  304. package/.worktrees/8d2d0392/src/taskcard-placeholders.js +21 -0
  305. package/.worktrees/8d2d0392/src/taskcard.js +217 -0
  306. package/.worktrees/8d2d0392/src/verify-postcheck.js +1718 -0
  307. package/.worktrees/8d2d0392/src/verify-probes.js +327 -0
  308. package/.worktrees/8d2d0392/src/version.js +19 -0
  309. package/.worktrees/8d2d0392/src/workflow.js +600 -0
  310. package/.worktrees/8d2d0392/src/workspace.js +125 -0
  311. package/.worktrees/8d2d0392/src/worktree-apply.js +1611 -0
  312. package/.worktrees/8d2d0392/src/worktree-deps.js +576 -0
  313. package/.worktrees/8d2d0392/src/worktree.js +1838 -0
  314. package/.worktrees/8d2d0392/templates/claude-instruction.md +23 -0
  315. package/.worktrees/8d2d0392/templates/prompts/taskcard-rules.md +29 -0
  316. package/.worktrees/8d2d0392/templates/prompts/testcase-design.md +12 -0
  317. package/.worktrees/8d2d0392/templates/prompts/verify-probes.md +32 -0
  318. package/.worktrees/8d2d0392/templates/workflows/archive-impact.yaml +79 -0
  319. package/.worktrees/8d2d0392/templates/workflows/scan-docs.yaml +132 -0
  320. package/architecture-analysis.md +448 -0
  321. package/eval/README.md +86 -0
  322. package/eval/eval.config.json +28 -0
  323. package/eval/lib/adapters/cli.mjs +63 -0
  324. package/eval/lib/adapters/dryrun.mjs +27 -0
  325. package/eval/lib/config.mjs +82 -0
  326. package/eval/lib/db.mjs +76 -0
  327. package/eval/lib/report.mjs +120 -0
  328. package/eval/lib/runner.mjs +157 -0
  329. package/eval/lib/tasks.mjs +73 -0
  330. package/eval/lib/util.mjs +107 -0
  331. package/eval/lib/workdir.mjs +30 -0
  332. package/eval/run.mjs +83 -0
  333. package/eval/tasks/001-slugify/files/package.json +6 -0
  334. package/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
  335. package/eval/tasks/001-slugify/task.json +6 -0
  336. package/eval/tasks/001-slugify/verify.mjs +28 -0
  337. package/eval/tasks/002-each-day/files/package.json +6 -0
  338. package/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
  339. package/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
  340. package/eval/tasks/002-each-day/task.json +6 -0
  341. package/eval/tasks/002-each-day/verify.mjs +31 -0
  342. package/eval/tasks/003-word-report/files/data/notes.txt +5 -0
  343. package/eval/tasks/003-word-report/files/package.json +6 -0
  344. package/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
  345. package/eval/tasks/003-word-report/task.json +6 -0
  346. package/eval/tasks/003-word-report/verify.mjs +38 -0
  347. package/package.json +4 -2
  348. package/src/agent-session-log.js +45 -5
  349. package/src/change-risk-profile.js +83 -14
  350. package/src/config-schema.js +18 -3
  351. package/src/contract-matrix.js +90 -29
  352. package/src/datetime.js +20 -0
  353. package/src/decision-distill.js +517 -0
  354. package/src/dispatch/backends/local-agent.js +4 -4
  355. package/src/docs-check.js +227 -0
  356. package/src/docs-debt.js +187 -24
  357. package/src/doctor-diagnostics.js +102 -2
  358. package/src/endpoint-extractor.js +41 -52
  359. package/src/foreign-declared.js +155 -0
  360. package/src/index.js +95 -13
  361. package/src/init.js +9 -7
  362. package/src/knowledge-match.js +107 -6
  363. package/src/local-register.js +7 -1
  364. package/src/module-resolve.js +12 -1
  365. package/src/modules.js +2 -1
  366. package/src/plan-adopt-waves.js +126 -0
  367. package/src/quicklog.js +50 -4
  368. package/src/run/command.js +124 -44
  369. package/src/run/complete-handlers.js +48 -19
  370. package/src/run/complete.js +5 -2
  371. package/src/run/gates.js +104 -22
  372. package/src/run/prompt.js +84 -42
  373. package/src/run/quick-audit.js +11 -0
  374. package/src/run/shared.js +152 -10
  375. package/src/run/stage.js +8 -5
  376. package/src/run.js +2 -2
  377. package/src/scan-postcheck.js +30 -17
  378. package/src/stage-contract-spec.js +3 -2
  379. package/src/stage-contract.js +68 -4
  380. package/src/stages/archive.js +53 -12
  381. package/src/stages/brainstorm-auto.js +50 -1
  382. package/src/stages/brainstorm.js +13 -0
  383. package/src/stages/doctor.js +157 -1
  384. package/src/stages/execute.js +32 -6
  385. package/src/stages/plan-postcheck.js +76 -17
  386. package/src/stages/plan.js +9 -4
  387. package/src/stages/quick.js +6 -1
  388. package/src/stages/verify.js +32 -15
  389. package/src/sync.js +41 -3
  390. package/src/task-review.js +2 -2
  391. package/src/taskcard-placeholders.js +21 -0
  392. package/src/taskcard.js +43 -1
  393. package/src/verify-postcheck.js +315 -13
  394. package/src/verify-probes.js +73 -10
  395. package/src/worktree-apply.js +569 -227
  396. package/src/worktree-deps.js +150 -1
  397. package/src/worktree.js +285 -77
  398. package/templates/prompts/taskcard-rules.md +2 -1
@@ -0,0 +1,600 @@
1
+ /**
2
+ * SillySpec Workflow Engine
3
+ *
4
+ * 定义、检查和执行结构化工作流。
5
+ * 职责:
6
+ * - 加载 .sillyspec/workflows/*.yaml
7
+ * - 运行 post_check 验证产物
8
+ * - 按角色定位失败 + 生成重试 prompt
9
+ * - 根据 role 定义生成 role prompts(Level 2)
10
+ */
11
+
12
+ import { readFileSync, existsSync, readdirSync, writeFileSync, mkdirSync } from 'fs'
13
+ import { join, resolve, basename } from 'path'
14
+ import jsYaml from 'js-yaml'
15
+ import { WORKFLOW_STATUS } from './constants.js'
16
+ import { contentNonEmpty, lineCount, missingSections, placeholderLineMatches } from './check-primitives.js'
17
+
18
+ // ─── Workflow 加载 ───
19
+
20
+ /**
21
+ * 查找并加载指定名称的 workflow YAML
22
+ * @param {string} cwd - 项目根目录
23
+ * @param {string} name - workflow 名称(如 'scan-docs')
24
+ * @returns {object|null} workflow 定义,或 null
25
+ */
26
+ export function loadWorkflow(cwd, name, validate = true) {
27
+ const wfDir = join(cwd, '.sillyspec', 'workflows')
28
+ if (!existsSync(wfDir)) return null
29
+
30
+ // 优先找 <name>.yaml,其次找 <name>.yml
31
+ for (const ext of ['.yaml', '.yml']) {
32
+ const f = join(wfDir, `${name}${ext}`)
33
+ if (existsSync(f)) {
34
+ const raw = readFileSync(f, 'utf8')
35
+ const wf = jsYaml.load(raw)
36
+ if (validate) {
37
+ const errors = validateWorkflow(wf)
38
+ if (errors.length > 0) return { _validationErrors: errors, ...wf }
39
+ }
40
+ return wf
41
+ }
42
+ }
43
+ return null
44
+ }
45
+
46
+ /**
47
+ * 校验 workflow YAML 结构
48
+ * @param {object} wf - workflow 定义
49
+ * @returns {string[]} 错误列表,空数组表示通过
50
+ */
51
+ function validateWorkflow(wf) {
52
+ const errors = []
53
+ const roles = wf.roles || []
54
+ const roleIds = new Set(roles.map(r => r.id))
55
+
56
+ for (const role of roles) {
57
+ if (!role.id) {
58
+ errors.push(`role 缺少 id 字段`)
59
+ continue
60
+ }
61
+ // depends_on 校验
62
+ const deps = role.depends_on || []
63
+ for (const depId of deps) {
64
+ if (!roleIds.has(depId)) {
65
+ errors.push(`role "${role.id}" 的 depends_on 引用了不存在的 role "${depId}"`)
66
+ }
67
+ }
68
+ // depends_on 循环检测(简单两层:A→B→A)
69
+ for (const depId of deps) {
70
+ const depRole = roles.find(r => r.id === depId)
71
+ if (depRole && (depRole.depends_on || []).includes(role.id)) {
72
+ errors.push(`循环依赖:"${role.id}" ↔ "${depId}"`)
73
+ }
74
+ }
75
+ // from_role 校验
76
+ const inputs = role.inputs || {}
77
+ if (!Array.isArray(inputs)) {
78
+ // inputs is a mapping
79
+ if (inputs.from_role) {
80
+ if (!roleIds.has(inputs.from_role)) {
81
+ errors.push(`role "${role.id}" 的 inputs.from_role 引用了不存在的 role "${inputs.from_role}"`)
82
+ }
83
+ if (inputs.output) {
84
+ const sourceRole = roles.find(r => r.id === inputs.from_role)
85
+ if (sourceRole) {
86
+ const outputExists = (sourceRole.outputs || []).some(o => {
87
+ const outputName = o.name || o.path?.split('/').pop()?.replace(/\.md$/, '') || ''
88
+ return outputName === inputs.output
89
+ })
90
+ if (!outputExists) {
91
+ errors.push(`role "${role.id}" 的 inputs.from_role "${inputs.from_role}" 没有名为 "${inputs.output}" 的 output`)
92
+ }
93
+ }
94
+ }
95
+ if (!deps.includes(inputs.from_role)) {
96
+ errors.push(`role "${role.id}" 的 inputs.from_role "${inputs.from_role}" 未在 depends_on 中声明`)
97
+ }
98
+ }
99
+ } else {
100
+ // inputs is an array (legacy format)
101
+ for (const input of inputs) {
102
+ if (input.from_role) {
103
+ if (!roleIds.has(input.from_role)) {
104
+ errors.push(`role "${role.id}" 的 inputs.from_role 引用了不存在的 role "${input.from_role}"`)
105
+ }
106
+ if (!deps.includes(input.from_role)) {
107
+ errors.push(`role "${role.id}" 的 inputs.from_role "${input.from_role}" 未在 depends_on 中声明`)
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ return errors
114
+ }
115
+
116
+ /**
117
+ * 列出所有可用 workflow
118
+ */
119
+ export function listWorkflows(cwd) {
120
+ const wfDir = join(cwd, '.sillyspec', 'workflows')
121
+ if (!existsSync(wfDir)) return []
122
+ const files = readdirSync(wfDir).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'))
123
+ return files.map(f => f.replace(/\.(yaml|yml)$/, ''))
124
+ }
125
+
126
+ // ─── 占位符替换 ───
127
+
128
+ /**
129
+ * 替换 workflow YAML 中的 <project> 占位符
130
+ * @param {object} wf - workflow 定义(会被修改)
131
+ * @param {string} projectName - 项目名
132
+ */
133
+ function replaceProjectPlaceholder(wf, projectName) {
134
+ const json = JSON.stringify(wf)
135
+ const replaced = json.replace(/<project>/g, projectName)
136
+ return JSON.parse(replaced)
137
+ }
138
+
139
+ // ─── Post Check ───
140
+
141
+ /**
142
+ * 检查结果项
143
+ * @typedef {{ role: string, output: string, path: string, check: string, passed: boolean, detail?: string }} CheckResult
144
+ */
145
+
146
+ /**
147
+ * 对单个 output 运行检查
148
+ * @param {object} outputDef - output 定义
149
+ * @param {string} basePath - 被检查的文件所在目录
150
+ * @param {string} cwd - 项目根目录
151
+ * @returns {CheckResult}
152
+ */
153
+ function checkOutput(outputDef, projectName, cwd, specBase) {
154
+ // specBase 优先(平台模式 platformOpts.specRoot,已含或不含 .sillyspec 语义);
155
+ // 未传时回退 join(cwd, '.sillyspec'),等价于旧行为 resolve(cwd, '.sillyspec/...')
156
+ const effectiveBase = specBase || join(cwd, '.sillyspec')
157
+ // 将 <project> 替换为实际项目名
158
+ let rawPath = (outputDef.path || '').replace(/<project>/g, projectName)
159
+ // 旧版兼容:如果 path 以 .sillyspec/ 开头(相对路径),strip 前缀避免双拼接
160
+ // 新版 yaml 用 {SPEC_ROOT} 已在 runPostCheck 中替换为绝对路径,不会走这里
161
+ if (rawPath.startsWith('.sillyspec/')) {
162
+ rawPath = rawPath.slice('.sillyspec/'.length)
163
+ }
164
+ const fullPath = resolve(effectiveBase, rawPath)
165
+ const checks = outputDef.checks || []
166
+ const results = []
167
+
168
+ for (const check of checks) {
169
+ switch (check.type) {
170
+ case 'file_exists': {
171
+ const exists = existsSync(fullPath)
172
+ results.push({ passed: exists, check: 'file_exists', detail: exists ? '' : `文件不存在: ${rawPath}` })
173
+ break
174
+ }
175
+ case 'no_empty_files': {
176
+ if (existsSync(fullPath)) {
177
+ const content = readFileSync(fullPath, 'utf8')
178
+ const empty = !contentNonEmpty(content)
179
+ results.push({ passed: !empty, check: 'no_empty_files', detail: empty ? `文件为空: ${rawPath}` : '' })
180
+ } else {
181
+ results.push({ passed: false, check: 'no_empty_files', detail: `文件不存在: ${rawPath}` })
182
+ }
183
+ break
184
+ }
185
+ case 'min_lines': {
186
+ if (existsSync(fullPath)) {
187
+ const content = readFileSync(fullPath, 'utf8')
188
+ const lines = lineCount(content)
189
+ const min = check.min || 1
190
+ results.push({ passed: lines >= min, check: `min_lines(${min})`, detail: lines >= min ? '' : `文件只有 ${lines} 行,要求至少 ${min} 行: ${rawPath}` })
191
+ } else {
192
+ results.push({ passed: false, check: `min_lines(${check.min || 1})`, detail: `文件不存在: ${rawPath}` })
193
+ }
194
+ break
195
+ }
196
+ case 'contains_sections': {
197
+ if (existsSync(fullPath)) {
198
+ const content = readFileSync(fullPath, 'utf8')
199
+ const sections = check.sections || []
200
+ const missing = missingSections(content, sections)
201
+ results.push({ passed: missing.length === 0, check: 'contains_sections', detail: missing.length > 0 ? `缺少章节: ${missing.join(', ')} — ${rawPath}` : '' })
202
+ } else {
203
+ results.push({ passed: false, check: 'contains_sections', detail: `文件不存在: ${rawPath}` })
204
+ }
205
+ break
206
+ }
207
+ case 'no_placeholder': {
208
+ if (existsSync(fullPath)) {
209
+ const content = readFileSync(fullPath, 'utf8')
210
+ const lineMatches = placeholderLineMatches(content, check.patterns)
211
+ results.push({ passed: lineMatches.length === 0, check: 'no_placeholder', detail: lineMatches.length > 0 ? `包含占位文本: ${lineMatches.map(m => `"${m}"`).join(', ')} — ${rawPath}` : '' })
212
+ } else {
213
+ results.push({ passed: false, check: 'no_placeholder', detail: `文件不存在: ${rawPath}` })
214
+ }
215
+ break
216
+ }
217
+ default:
218
+ results.push({ passed: true, check: check.type, detail: `未知检查类型,跳过: ${check.type}` })
219
+ }
220
+ }
221
+
222
+ return results
223
+ }
224
+
225
+ /**
226
+ * 运行 workflow 的 post_check
227
+ * @param {object} wf - workflow 定义
228
+ * @param {string} cwd - 项目根目录
229
+ * @param {string} projectName - 项目名
230
+ * @returns {{ passed: boolean, roleResults: Array<{ roleId: string, roleName: string, passed: boolean, failures: string[] }>, workflowFailures: string[] }}
231
+ */
232
+ /**
233
+ * 统一的 Workflow Check 结果协议
234
+ * CLI 和 run.js 共用同一份结构化结果
235
+ *
236
+ * 返回结构:
237
+ * {
238
+ * workflow: string, // workflow 名称
239
+ * project: string, // 项目名
240
+ * status: 'pass'|'fail', // 总体状态
241
+ * spec_version: number, // spec 版本
242
+ * roles: [{ id, name, status, outputs: [{ path, status, checks: [{ type, status, detail }] }] }],
243
+ * workflow_checks: [{ type, status, detail }],
244
+ * failures: [{ level: 'role'|'workflow', role_id?, output?, check, message }],
245
+ * retry_prompts: [{ role_id, role_name, prompt }]
246
+ * }
247
+ */
248
+ export function runPostCheck(wf, cwd, projectName, placeholders = {}, specBase) {
249
+ let resolved = replaceProjectPlaceholder(wf, projectName)
250
+
251
+ // 自动注入 {SPEC_ROOT} 占位符(yaml 模板用 {SPEC_ROOT} 表示规范根目录)
252
+ // effectiveBase 与 _checkWorkflow 内部一致:specBase || join(cwd, '.sillyspec')
253
+ const effectiveBase = specBase || join(cwd, '.sillyspec')
254
+ const allPlaceholders = { SPEC_ROOT: effectiveBase, ...placeholders }
255
+
256
+ let json = JSON.stringify(resolved)
257
+ for (const [key, value] of Object.entries(allPlaceholders)) {
258
+ // 支持 {key} 和 <key> 两种占位符语法
259
+ // 字符串值先做 JSON 转义再嵌入:Windows 路径等含反斜杠/引号的值裸替换进 JSON 字面量会
260
+ // 破坏 parse(实测报「Bad escaped character」→ scan 深度扫描质量门静默 fail-open)。
261
+ // JSON.stringify(v).slice(1,-1) 产出合法的 JSON 字符串内容(反斜杠→\\、引号→\");
262
+ // 非字符串值保持 String(v) 原样(数字占位符不被引号包裹,语义不变)。
263
+ const embedValue = (v) => (typeof v === 'string' ? JSON.stringify(v).slice(1, -1) : String(v))
264
+ json = json.replace(new RegExp(`\{${key}\}`, 'g'), embedValue(value))
265
+ json = json.replace(new RegExp(`<${key}>`, 'g'), embedValue(value))
266
+ }
267
+ resolved = JSON.parse(json)
268
+
269
+ return _checkWorkflow(resolved, cwd, projectName, specBase)
270
+ }
271
+
272
+ function _checkWorkflow(wf, cwd, projectName, specBase) {
273
+ const effectiveBase = specBase || join(cwd, '.sillyspec')
274
+ const workflowName = wf.name || 'unknown'
275
+ const specVersion = wf.spec_version || wf.version || 0
276
+ const workflowChecks = wf.checks?.workflow_level || []
277
+ const roleLevelChecks = wf.checks?.role_level || []
278
+ const roles = []
279
+ const failures = []
280
+ const workflowCheckResults = []
281
+
282
+ // 1. 角色级别检查
283
+ for (const role of wf.roles || []) {
284
+ const roleId = role.id
285
+ const roleName = role.name || roleId
286
+ const outputDefs = role.outputs || []
287
+ const outputs = []
288
+
289
+ for (const outputDef of outputDefs) {
290
+ const rawPath = (outputDef.path || '').replace(/<project>/g, projectName)
291
+ // role_level checks: 全局施加到每个 output(与 output 自身的 checks 合并)
292
+ const mergedOutputDef = roleLevelChecks.length > 0
293
+ ? { ...outputDef, checks: [...(outputDef.checks || []), ...roleLevelChecks] }
294
+ : outputDef
295
+ const checkResults = checkOutput(mergedOutputDef, projectName, cwd, effectiveBase)
296
+ const outputPassed = checkResults.every(c => c.passed)
297
+
298
+ outputs.push({
299
+ path: rawPath,
300
+ status: outputPassed ? 'pass' : 'fail',
301
+ checks: checkResults.map(c => ({
302
+ type: c.check,
303
+ status: c.passed ? 'pass' : 'fail',
304
+ detail: c.detail
305
+ }))
306
+ })
307
+
308
+ for (const cr of checkResults) {
309
+ if (!cr.passed) {
310
+ failures.push({
311
+ level: 'role',
312
+ role_id: roleId,
313
+ output: rawPath,
314
+ check: cr.check,
315
+ message: cr.detail
316
+ })
317
+ }
318
+ }
319
+ }
320
+
321
+ const rolePassed = outputs.every(o => o.status === 'pass')
322
+ roles.push({
323
+ id: roleId,
324
+ name: roleName,
325
+ status: rolePassed ? 'pass' : 'fail',
326
+ outputs
327
+ })
328
+ }
329
+
330
+ // 2. 工作流级别检查
331
+ for (const check of workflowChecks) {
332
+ switch (check.type) {
333
+ case 'file_count': {
334
+ const scanDir = join(effectiveBase, 'docs', projectName, check.path || 'scan/')
335
+ if (existsSync(scanDir)) {
336
+ const files = readdirSync(scanDir).filter(f => f.endsWith('.md'))
337
+ const min = check.min || 0
338
+ if (files.length < min) {
339
+ const detail = `文件数不足: ${scanDir} 有 ${files.length} 个 .md 文件,要求至少 ${min} 个`
340
+ workflowCheckResults.push({ type: 'file_count', status: 'fail', detail })
341
+ failures.push({ level: 'workflow', check: 'file_count', message: detail })
342
+ } else {
343
+ workflowCheckResults.push({ type: 'file_count', status: 'pass', detail: '' })
344
+ }
345
+ } else {
346
+ const detail = `目录不存在: ${scanDir}`
347
+ workflowCheckResults.push({ type: 'file_count', status: 'fail', detail })
348
+ failures.push({ level: 'workflow', check: 'file_count', message: detail })
349
+ }
350
+ break
351
+ }
352
+ case 'no_empty_files': {
353
+ const scanDir = join(effectiveBase, 'docs', projectName, check.path || 'scan/')
354
+ if (existsSync(scanDir)) {
355
+ const files = readdirSync(scanDir).filter(f => f.endsWith('.md'))
356
+ let anyEmpty = false
357
+ for (const f of files) {
358
+ const content = readFileSync(join(scanDir, f), 'utf8')
359
+ if (!contentNonEmpty(content)) {
360
+ const detail = `空文件: ${join(scanDir, f)}`
361
+ workflowCheckResults.push({ type: 'no_empty_files', status: 'fail', detail })
362
+ failures.push({ level: 'workflow', check: 'no_empty_files', message: detail })
363
+ anyEmpty = true
364
+ }
365
+ }
366
+ if (!anyEmpty) {
367
+ workflowCheckResults.push({ type: 'no_empty_files', status: 'pass', detail: '' })
368
+ }
369
+ } else {
370
+ const detail = `目录不存在: ${scanDir}`
371
+ workflowCheckResults.push({ type: 'no_empty_files', status: 'fail', detail })
372
+ failures.push({ level: 'workflow', check: 'no_empty_files', message: detail })
373
+ }
374
+ break
375
+ }
376
+ case 'file_exists': {
377
+ // workflow_level file_exists: check.path 可以是目录或文件,相对 effectiveBase
378
+ let checkPath = check.path || ''
379
+ // 兼容 .sillyspec/ 前缀
380
+ if (checkPath.startsWith('.sillyspec/')) checkPath = checkPath.slice('.sillyspec/'.length)
381
+ // 替换 <change-name> 占位符(archive-impact 用)
382
+ checkPath = checkPath.replace(/<change-name>/g, wf._changeName || '')
383
+ const fullPath = join(effectiveBase, checkPath)
384
+ if (!existsSync(fullPath)) {
385
+ const detail = `文件不存在: ${checkPath}`
386
+ workflowCheckResults.push({ type: 'file_exists', status: 'fail', detail })
387
+ failures.push({ level: 'workflow', check: 'file_exists', message: detail })
388
+ } else {
389
+ workflowCheckResults.push({ type: 'file_exists', status: 'pass', detail: '' })
390
+ }
391
+ break
392
+ }
393
+ case 'min_lines': {
394
+ let checkPath = check.path || ''
395
+ if (checkPath.startsWith('.sillyspec/')) checkPath = checkPath.slice('.sillyspec/'.length)
396
+ checkPath = checkPath.replace(/<change-name>/g, wf._changeName || '')
397
+ const fullPath = join(effectiveBase, checkPath)
398
+ if (existsSync(fullPath)) {
399
+ const content = readFileSync(fullPath, 'utf8')
400
+ const lines = lineCount(content)
401
+ const min = check.min || 1
402
+ if (lines < min) {
403
+ const detail = `文件只有 ${lines} 行,要求至少 ${min} 行: ${checkPath}`
404
+ workflowCheckResults.push({ type: 'min_lines', status: 'fail', detail })
405
+ failures.push({ level: 'workflow', check: 'min_lines', message: detail })
406
+ } else {
407
+ workflowCheckResults.push({ type: 'min_lines', status: 'pass', detail: '' })
408
+ }
409
+ } else {
410
+ const detail = `文件不存在: ${checkPath}`
411
+ workflowCheckResults.push({ type: 'min_lines', status: 'fail', detail })
412
+ failures.push({ level: 'workflow', check: 'min_lines', message: detail })
413
+ }
414
+ break
415
+ }
416
+ case 'contains_sections': {
417
+ let checkPath = check.path || ''
418
+ if (checkPath.startsWith('.sillyspec/')) checkPath = checkPath.slice('.sillyspec/'.length)
419
+ checkPath = checkPath.replace(/<change-name>/g, wf._changeName || '')
420
+ const fullPath = join(effectiveBase, checkPath)
421
+ if (existsSync(fullPath)) {
422
+ const content = readFileSync(fullPath, 'utf8')
423
+ const sections = check.sections || []
424
+ const missing = missingSections(content, sections)
425
+ if (missing.length > 0) {
426
+ const detail = `缺少章节: ${missing.join(', ')} — ${checkPath}`
427
+ workflowCheckResults.push({ type: 'contains_sections', status: 'fail', detail })
428
+ failures.push({ level: 'workflow', check: 'contains_sections', message: detail })
429
+ } else {
430
+ workflowCheckResults.push({ type: 'contains_sections', status: 'pass', detail: '' })
431
+ }
432
+ } else {
433
+ const detail = `文件不存在: ${checkPath}`
434
+ workflowCheckResults.push({ type: 'contains_sections', status: 'fail', detail })
435
+ failures.push({ level: 'workflow', check: 'contains_sections', message: detail })
436
+ }
437
+ break
438
+ }
439
+ case 'no_placeholder': {
440
+ let checkPath = check.path || ''
441
+ if (checkPath.startsWith('.sillyspec/')) checkPath = checkPath.slice('.sillyspec/'.length)
442
+ checkPath = checkPath.replace(/<change-name>/g, wf._changeName || '')
443
+ const fullPath = join(effectiveBase, checkPath)
444
+ if (existsSync(fullPath)) {
445
+ const content = readFileSync(fullPath, 'utf8')
446
+ const lineMatches = placeholderLineMatches(content, check.patterns)
447
+ if (lineMatches.length > 0) {
448
+ const detail = `包含占位文本: ${lineMatches.map(m => `"${m}"`).join(', ')} — ${checkPath}`
449
+ workflowCheckResults.push({ type: 'no_placeholder', status: 'fail', detail })
450
+ failures.push({ level: 'workflow', check: 'no_placeholder', message: detail })
451
+ } else {
452
+ workflowCheckResults.push({ type: 'no_placeholder', status: 'pass', detail: '' })
453
+ }
454
+ } else {
455
+ const detail = `文件不存在: ${checkPath}`
456
+ workflowCheckResults.push({ type: 'no_placeholder', status: 'fail', detail })
457
+ failures.push({ level: 'workflow', check: 'no_placeholder', message: detail })
458
+ }
459
+ break
460
+ }
461
+ default:
462
+ workflowCheckResults.push({ type: check.type, status: 'pass', detail: `未知检查类型,跳过: ${check.type}` })
463
+ }
464
+ }
465
+
466
+ const allPassed = roles.every(r => r.status === 'pass') && workflowCheckResults.every(c => c.status === 'pass')
467
+
468
+ // 生成 retry prompts
469
+ const retryPrompts = []
470
+ if (!allPassed) {
471
+ for (const role of roles.filter(r => r.status === 'fail')) {
472
+ const roleFailures = failures.filter(f => f.role_id === role.id)
473
+ const targetFiles = [...new Set(roleFailures.map(f => f.output).filter(Boolean))]
474
+ const roleDef = (wf.roles || []).find(r => r.id === role.id)
475
+ const constraints = roleDef?.constraints || []
476
+ let prompt = `上一次 workflow 执行存在失败项,请重试。\n\n`
477
+ prompt += `### 失败角色:${role.name} (${role.id})\n失败原因:\n`
478
+ for (const f of roleFailures) {
479
+ prompt += `- ${f.message}\n`
480
+ }
481
+ prompt += `\n`
482
+ for (const fp of targetFiles) {
483
+ prompt += `目标文件:\`${fp}\`\n`
484
+ }
485
+ if (constraints.length > 0) {
486
+ prompt += `约束:\n`
487
+ for (const c of constraints) {
488
+ prompt += `- ${c}\n`
489
+ }
490
+ }
491
+ prompt += `\n⚠️ 你必须确保文件写入指定路径。不要只报告完成,请用 write 工具实际写入。`
492
+ retryPrompts.push({ role_id: role.id, role_name: role.name, prompt })
493
+ }
494
+ }
495
+
496
+ return {
497
+ workflow: workflowName,
498
+ project: projectName,
499
+ status: allPassed ? 'pass' : 'fail',
500
+ spec_version: specVersion,
501
+ roles,
502
+ workflow_checks: workflowCheckResults,
503
+ failures,
504
+ retry_prompts: retryPrompts
505
+ }
506
+ }
507
+
508
+ /**
509
+ * 格式化检查结果为人类可读报告(兼容旧接口)
510
+ */
511
+ export function formatCheckReport(result) {
512
+ const lines = []
513
+ lines.push('\n📋 Workflow Post-Check 报告\n')
514
+
515
+ for (const r of (result.roles || [])) {
516
+ const icon = r.status === 'pass' ? '✅' : '❌'
517
+ lines.push(`${icon} ${r.name} (${r.id})`)
518
+ // 兼容新旧格式
519
+ const outputFailures = (r.outputs || []).flatMap(o =>
520
+ (o.checks || []).filter(c => c.status === 'fail').map(c => c.detail)
521
+ )
522
+ for (const f of outputFailures) {
523
+ lines.push(` └─ ${f}`)
524
+ }
525
+ }
526
+
527
+ if ((result.workflow_checks || []).some(c => c.status === 'fail')) {
528
+ lines.push('')
529
+ for (const c of result.workflow_checks) {
530
+ if (c.status === 'fail') {
531
+ lines.push(`❌ 全局检查失败: ${c.detail}`)
532
+ }
533
+ }
534
+ }
535
+
536
+ if (result.status === 'pass') {
537
+ lines.push('\n✅ 全部检查通过')
538
+ } else {
539
+ lines.push('\n❌ 存在失败项,请根据以下重试提示修复:')
540
+ }
541
+
542
+ return lines.join('\n')
543
+ }
544
+
545
+ // ─── Workflow Run 归档 ───
546
+
547
+
548
+ /**
549
+ * 将 workflow check 结果归档到 .sillyspec/.runtime/workflow-runs/
550
+ * @param {object} result - runPostCheck 返回的结构化结果
551
+ * @param {object} options
552
+ * @param {string} options.cwd - 项目根目录
553
+ * @param {string} [options.source] - 调用来源('run.js' / 'cli')
554
+ * @param {string} [options.stage] - 阶段名(scan/archive)
555
+ * @param {string} [options.step] - 步骤名
556
+ * @returns {string|null} 保存路径,失败返回 null
557
+ */
558
+ export function saveWorkflowRun(result, options = {}) {
559
+ const { cwd = '.', source = 'unknown', stage, step, runtimeRoot, scanRunId } = options
560
+ // 平台模式:写入 runtime-root/scan-runs/<scan-run-id>/workflow-runs/
561
+ // 本地模式:写入 cwd/.sillyspec/.runtime/workflow-runs/
562
+ const runDir = runtimeRoot
563
+ ? join(runtimeRoot, 'scan-runs', scanRunId || 'unknown', 'workflow-runs')
564
+ : join(cwd, '.sillyspec', '.runtime', 'workflow-runs')
565
+ try {
566
+ mkdirSync(runDir, { recursive: true })
567
+ } catch (e) {
568
+ console.warn('⚠️ 无法创建 workflow-runs 目录:', e.message)
569
+ return null
570
+ }
571
+
572
+ const now = new Date()
573
+ const ts = now.toISOString().replace(/[-:T]/g, '').slice(0, 14)
574
+ const filename = `${ts}-${result.workflow || 'unknown'}-${result.project || 'default'}-${result.status}.json`
575
+ const filepath = join(runDir, filename)
576
+
577
+ const record = {
578
+ run_id: filename.replace('.json', ''),
579
+ created_at: now.toISOString(),
580
+ source,
581
+ ...(stage ? { stage } : {}),
582
+ ...(step ? { step } : {}),
583
+ workflow: result.workflow,
584
+ project: result.project,
585
+ status: result.status,
586
+ spec_version: result.spec_version,
587
+ roles: result.roles,
588
+ workflow_checks: result.workflow_checks,
589
+ failures: result.failures,
590
+ retry_prompts: result.retry_prompts
591
+ }
592
+
593
+ try {
594
+ writeFileSync(filepath, JSON.stringify(record, null, 2), 'utf8')
595
+ return filepath
596
+ } catch (e) {
597
+ console.warn('⚠️ 保存 workflow run 失败:', e.message)
598
+ return null
599
+ }
600
+ }