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,512 @@
1
+ /**
2
+ * scan-postcheck.js — CLI 层 scan 完成后强制校验
3
+ *
4
+ * 不依赖 AI agent 的自检报告,由 CLI 代码直接检查文件系统。
5
+ * 平台模式下必须通过所有 check 才能 success,否则降级。
6
+ */
7
+
8
+ import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, renameSync, rmdirSync } from 'fs'
9
+ import { join, basename } from 'path'
10
+ import { SCAN_STATUS, CHECK_SEVERITY, SCAN_REQUIRED_DOCS, SCAN_REQUIRED_DOCS_QUICK } from './constants.js'
11
+ import { validateScriptCommands } from './stages/cmd-existence.js'
12
+ import { git } from './git-helper.js'
13
+ import { nowWallClock } from './datetime.js'
14
+
15
+ const REQUIRED_SCAN_DOCS = SCAN_REQUIRED_DOCS
16
+
17
+ /**
18
+ * @param {object} opts
19
+ * @param {string} opts.cwd - 源码项目根目录 (source_root)
20
+ * @param {string} opts.specDir - 规范目录 (spec-root),null 时为非平台模式
21
+ * @param {string} [opts.outputText] - 最后一步(自检)的 AI 输出文本
22
+ * @param {object} [opts.scanMeta] - scan 元数据(由 runCommand 传入)
23
+ * @param {boolean} [opts.scanMeta.projectListParsed] - Step 2 项目列表是否成功解析
24
+ * @param {boolean} [opts.scanMeta.manifestWritten] - manifest.json 是否写入成功
25
+ * @param {number} [opts.scanMeta.projectCount] - 实际展开的项目数量
26
+ * @returns {{ status: 'success'|'completed_with_warnings'|'failed_post_check', checks: Array<{name, severity, detail}> }}
27
+ */
28
+ export function runScanPostCheck({ cwd, specDir, outputText = '', scanMeta = {}, scanProfile = null } ) {
29
+ const isPlatform = !!specDir
30
+ // profile 感知:quick 档只要求 4 份核心文档,否则要求完整 7 份。
31
+ // 否则 quick 扫描(--quick 显式 或 小项目自动判定)在平台模式会因缺 INTEGRATIONS/TESTING/CONCERNS 直接判 failed。
32
+ const mode = scanProfile?.mode || null
33
+ const requiredDocs = mode === 'quick' ? SCAN_REQUIRED_DOCS_QUICK : SCAN_REQUIRED_DOCS
34
+ const checks = []
35
+
36
+ if (!isPlatform) {
37
+ // 非平台模式:只做轻量检查
38
+ const localSpec = join(cwd, '.sillyspec')
39
+ const scanDir = join(localSpec, 'docs', basename(cwd), 'scan')
40
+
41
+ // 检查必需文档是否存在(quick profile 按 4 份清单,否则 7 份)
42
+ const missing = requiredDocs.filter(f => !existsSync(join(scanDir, f)))
43
+ if (missing.length > 0) {
44
+ checks.push({ name: 'missing_docs', severity: CHECK_SEVERITY.WARNING, detail: `缺少 ${missing.length} 份 scan 文档(${mode || 'full'} profile): ${missing.join(', ')}` })
45
+ }
46
+ if (mode === 'quick') {
47
+ checks.push({ name: 'quick_profile_notice', severity: CHECK_SEVERITY.WARNING, detail: 'quick scan:仅生成 4 份核心文档(PROJECT/ARCHITECTURE/CONVENTIONS/STRUCTURE),深度扫描待补齐' })
48
+ }
49
+
50
+ const hasWarning = checks.some(c => c.severity === 'warning')
51
+ return { status: hasWarning ? 'completed_with_warnings' : 'success', checks }
52
+ }
53
+
54
+ // ── 平台模式:严格检查 ──
55
+
56
+ const projectName = basename(cwd)
57
+
58
+ // 1. source_root 污染检查(docs/projects/workflows/knowledge/manifest/local)
59
+ const pollutePaths = ['docs', 'projects', 'workflows', 'knowledge']
60
+ const polluteFiles = ['manifest.json', 'local.yaml']
61
+ for (const sub of pollutePaths) {
62
+ const localSub = join(cwd, '.sillyspec', sub)
63
+ if (existsSync(localSub)) {
64
+ try {
65
+ const leaked = readdirSync(localSub, { recursive: true }).filter(e => String(e).endsWith('.md') || String(e).endsWith('.yaml') || String(e).endsWith('.json'))
66
+ if (leaked.length > 0) {
67
+ checks.push({
68
+ name: sub === 'docs' ? 'source_root_docs_leak' : 'source_root_leak',
69
+ severity: CHECK_SEVERITY.FAILED,
70
+ detail: `source_root/.sillyspec/${sub}/ 下存在 ${leaked.length} 个文件(${localSub}/),agent 写入到了错误路径`
71
+ })
72
+ }
73
+ } catch {}
74
+ }
75
+ }
76
+ for (const file of polluteFiles) {
77
+ const filePath = join(cwd, '.sillyspec', file)
78
+ if (existsSync(filePath)) {
79
+ checks.push({
80
+ name: 'source_root_leak',
81
+ severity: CHECK_SEVERITY.FAILED,
82
+ detail: `source_root/.sillyspec/${file} 存在,agent 写入到了错误路径(${filePath})`
83
+ })
84
+ }
85
+ }
86
+
87
+ // 2. spec_root 检查必需文档(quick profile 按 4 份清单,否则 7 份)
88
+ const specScanDir = join(specDir, 'docs', projectName, 'scan')
89
+ const missingDocs = requiredDocs.filter(f => !existsSync(join(specScanDir, f)))
90
+ if (missingDocs.length > 0) {
91
+ checks.push({
92
+ name: missingDocs.length === requiredDocs.length ? 'all_docs_missing' : 'partial_docs_missing',
93
+ severity: CHECK_SEVERITY.FAILED,
94
+ detail: missingDocs.length === requiredDocs.length
95
+ ? `spec_root 下无任何 scan 文档(${specScanDir}/),扫描可能未执行`
96
+ : `spec_root 缺少必需文档: ${missingDocs.join(', ')}(${mode === 'quick' ? 'quick profile 要求 4 份核心文档' : '7 份 scan 文档均为 required'})`
97
+ })
98
+ }
99
+ if (mode === 'quick') {
100
+ checks.push({ name: 'quick_profile_notice', severity: CHECK_SEVERITY.WARNING, detail: 'quick scan:仅生成 4 份核心文档(PROJECT/ARCHITECTURE/CONVENTIONS/STRUCTURE),深度扫描待补齐' })
101
+ }
102
+
103
+ // 3. 检查文档 header(author / created_at)— 只看文件头部,避免正文出现同名词被误判
104
+ const existingDocs = requiredDocs.filter(f => existsSync(join(specScanDir, f)))
105
+ const docsMissingHeader = []
106
+ for (const doc of existingDocs) {
107
+ const content = readFileSync(join(specScanDir, doc), 'utf8')
108
+ const headerSlice = content.slice(0, 512)
109
+ if (!/author\s*:/.test(headerSlice) || !/created_at\s*:/.test(headerSlice)) {
110
+ docsMissingHeader.push(doc)
111
+ }
112
+ }
113
+ if (docsMissingHeader.length > 0) {
114
+ checks.push({
115
+ name: 'docs_missing_header',
116
+ severity: CHECK_SEVERITY.WARNING,
117
+ detail: `${docsMissingHeader.length} 份文档缺少 author/created_at: ${docsMissingHeader.join(', ')}(sillyspec scan-fix-headers 一键补齐)`
118
+ })
119
+ }
120
+
121
+ // 4. local.yaml 校验
122
+ const localYamlPath = join(specDir, 'local.yaml')
123
+ if (existsSync(localYamlPath)) {
124
+ const yamlContent = readFileSync(localYamlPath, 'utf8')
125
+ // 改调共享 validateScriptCommands(H2,去重 plan/scan 两处命令校验)。
126
+ // 严重度维持 WARNING(design D-04:scan 阶段保守,local.yaml 命令误报不阻断 init);
127
+ // 不传 modules → 仅查根 package.json,与历史行为一致(monorepo 子包感知由 plan-postcheck 启用)。
128
+ // 仅校验 `npm|pnpm|yarn run <script>` 这类可静态对账的命令;install/typecheck/npx 等直接
129
+ // 包管理器调用不在范围(沿用 cmd-existence.js:126-129 注释立场,正则天然不匹配它们)。
130
+ const { invalid } = validateScriptCommands(yamlContent, { projectRoot: cwd })
131
+ if (invalid.length > 0) {
132
+ checks.push({
133
+ name: 'local_config_invalid',
134
+ severity: CHECK_SEVERITY.WARNING,
135
+ detail: `local.yaml 引用不存在的命令: ${invalid.map(i => `${i.cmd} (${i.reason})`).join('; ')}`
136
+ })
137
+ }
138
+ }
139
+
140
+ // 5. 检查 AI 输出中的真实错误标记
141
+ // 注意:不对 agent 描述性文本做全文正则匹配,防止误报。
142
+ // 只检测明显是 agent 运行时失败的信号(未被捕获的错误块),
143
+ // 而非 agent 正常描述中提到这些词。
144
+ if (outputText) {
145
+ // 5a: 检测未被恢复的 API 错误(连续多次、非描述性提及)
146
+ const apiErrorCount = (outputText.match(/API Error\b.*?\b529\b/gi) || []).length
147
+ if (apiErrorCount >= 2) {
148
+ checks.push({ name: 'api_error_529', severity: CHECK_SEVERITY.WARNING, detail: 'AI 输出中包含多次 API Error 529' })
149
+ }
150
+ const rateLimitCount = (outputText.match(/rate.?limit.*?exhausted/gi) || []).length
151
+ if (rateLimitCount >= 2) {
152
+ checks.push({ name: 'rate_limit_exhausted', severity: CHECK_SEVERITY.WARNING, detail: 'AI 输出中包含多次 rate_limit exhausted' })
153
+ }
154
+ // tool_use_error 和 fallback 已移除:agent 描述性文本中正常提及这些词
155
+ // 不应触发 warning。真正的问题会通过文档缺失、manifest 失败等其他检查捕获。
156
+ }
157
+
158
+ // 6. manifest 写入状态检查
159
+ if (scanMeta.manifestWritten === false) {
160
+ checks.push({
161
+ name: 'manifest_write_failed',
162
+ severity: CHECK_SEVERITY.FAILED,
163
+ detail: 'manifest.json 写入失败,平台无法消费 scan 结果'
164
+ })
165
+ }
166
+
167
+ // 7. 项目列表解析状态检查
168
+ if (scanMeta.projectListParsed === false) {
169
+ checks.push({
170
+ name: 'project_list_parse_failed',
171
+ severity: CHECK_SEVERITY.WARNING,
172
+ detail: 'Step 2 项目列表解析失败,回退到注册项目列表,可能遗漏子项目'
173
+ })
174
+ }
175
+
176
+ // 7.5 knowledge 产物校验
177
+ const knowledgeDir = join(specDir, 'knowledge')
178
+ if (existsSync(knowledgeDir)) {
179
+ const indexPath = join(knowledgeDir, 'INDEX.md')
180
+ if (!existsSync(indexPath)) {
181
+ checks.push({
182
+ name: 'knowledge_index_missing',
183
+ severity: CHECK_SEVERITY.WARNING,
184
+ detail: `knowledge/INDEX.md 不存在`
185
+ })
186
+ } else {
187
+ // 检查 INDEX.md 引用的文件是否真实存在
188
+ const indexContent = readFileSync(indexPath, 'utf8')
189
+ const referencedFiles = [...indexContent.matchAll(/\(([^)]+\.md)/g)].map(m => m[1])
190
+ const missingRefs = referencedFiles.filter(f => !existsSync(join(knowledgeDir, f)))
191
+ if (missingRefs.length > 0) {
192
+ checks.push({
193
+ name: 'knowledge_broken_refs',
194
+ severity: CHECK_SEVERITY.WARNING,
195
+ detail: `INDEX.md 引用了不存在的文件: ${missingRefs.join(', ')}`
196
+ })
197
+ }
198
+ }
199
+ } else {
200
+ checks.push({
201
+ name: 'knowledge_dir_missing',
202
+ severity: CHECK_SEVERITY.WARNING,
203
+ detail: `knowledge/ 目录不存在`
204
+ })
205
+ }
206
+
207
+ // 8. 计算 finalStatus
208
+ const hasFailed = checks.some(c => c.severity === CHECK_SEVERITY.FAILED)
209
+ const hasWarning = checks.some(c => c.severity === CHECK_SEVERITY.WARNING)
210
+
211
+ let status
212
+ if (hasFailed) {
213
+ status = SCAN_STATUS.FAILED_POST_CHECK
214
+ } else if (hasWarning) {
215
+ status = SCAN_STATUS.COMPLETED_WITH_WARNINGS
216
+ } else {
217
+ status = SCAN_STATUS.SUCCESS
218
+ }
219
+
220
+ return { status, checks }
221
+ }
222
+
223
+ /**
224
+ * 将 postcheck 结果转换为结构化 JSON(SillyHub 可消费格式)
225
+ *
226
+ * failure_category 标准化:
227
+ * - warning : 非致命问题,不阻塞流程
228
+ * - error : 文档缺失/内容不完整,需要修复
229
+ * - critical : 安全问题(source_root 泄漏/路径污染)
230
+ *
231
+ * 结构化字段:
232
+ * - violations : 明确违反约束的条目(source_root 泄漏等)
233
+ * - missing_outputs : 预期文件不存在
234
+ * - path_pollution : 产物写入了错误路径
235
+ * - bad_references : 引用了不存在的命令/资源
236
+ * - quality_warnings: AI 输出中包含错误标记等质量信号
237
+ *
238
+ * @param {object} result - runScanPostCheck 返回值
239
+ * @param {object} [meta] - 附带元数据(workspace_id, scan_run_id, timestamp 等)
240
+ * @returns {object} 结构化 JSON
241
+ */
242
+ export function formatStructuredResult(result, meta = {}) {
243
+ const structured = {
244
+ schema_version: 1,
245
+ generated_at: new Date().toISOString(),
246
+ overall_status: result.status,
247
+ // 路径溯源(供平台消费)
248
+ ...(meta.workspace_id ? { workspace_id: meta.workspace_id } : {}),
249
+ ...(meta.scan_run_id ? { scan_run_id: meta.scan_run_id } : {}),
250
+ ...(meta.source_root ? { source_root: meta.source_root } : {}),
251
+ ...(meta.spec_root ? { spec_root: meta.spec_root } : {}),
252
+ ...(meta.runtime_root ? { runtime_root: meta.runtime_root } : {}),
253
+ summary: {
254
+ total_checks: result.checks.length,
255
+ critical: 0,
256
+ error: 0,
257
+ warning: 0,
258
+ },
259
+ failure_categories: {
260
+ violations: [],
261
+ missing_outputs: [],
262
+ path_pollution: [],
263
+ bad_references: [],
264
+ quality_warnings: [],
265
+ },
266
+ checks: result.checks.map(c => ({
267
+ name: c.name,
268
+ severity: c.severity === 'failed' ? 'critical' : c.severity,
269
+ detail: c.detail,
270
+ })),
271
+ }
272
+
273
+ // 分类到 failure_categories
274
+ for (const check of result.checks) {
275
+ const severity = check.severity === 'failed' ? 'critical' : check.severity
276
+ const entry = { name: check.name, detail: check.detail, severity }
277
+
278
+ // 路径污染类
279
+ if (check.name === 'source_root_leak' || check.name === 'source_root_docs_leak') {
280
+ structured.failure_categories.path_pollution.push(entry)
281
+ structured.failure_categories.violations.push(entry)
282
+ }
283
+ // 文档缺失类
284
+ else if (check.name === 'all_docs_missing' || check.name === 'partial_docs_missing' || check.name === 'missing_docs') {
285
+ structured.failure_categories.missing_outputs.push(entry)
286
+ }
287
+ // 引用无效类
288
+ else if (check.name === 'local_config_invalid') {
289
+ structured.failure_categories.bad_references.push(entry)
290
+ }
291
+ // AI 输出质量类
292
+ else if (['api_error_529', 'rate_limit_exhausted'].includes(check.name)) {
293
+ structured.failure_categories.quality_warnings.push(entry)
294
+ }
295
+ // manifest/project 列表问题
296
+ else if (check.name === 'manifest_write_failed' || check.name === 'project_list_parse_failed') {
297
+ structured.failure_categories.violations.push(entry)
298
+ }
299
+ // 文档缺少 header
300
+ else if (check.name === 'docs_missing_header') {
301
+ structured.failure_categories.quality_warnings.push(entry)
302
+ }
303
+ // quick profile 说明(informational warning,非缺陷)
304
+ else if (check.name === 'quick_profile_notice') {
305
+ structured.failure_categories.quality_warnings.push(entry)
306
+ }
307
+ // 兜底:归入 violations
308
+ else {
309
+ structured.failure_categories.violations.push(entry)
310
+ }
311
+ }
312
+
313
+ // 汇总计数
314
+ for (const check of result.checks) {
315
+ if (check.severity === 'failed') structured.summary.critical++
316
+ else structured.summary.warning++
317
+ }
318
+
319
+ return structured
320
+ }
321
+
322
+ /**
323
+ * 将结构化结果写入 JSON 文件(平台模式供 SillyHub 消费)
324
+ *
325
+ * 本地模式:写入 specDir/.runtime/postcheck-result.json
326
+ * 平台模式:写入 runtimeRoot/scan-runs/{scan_run_id}/postcheck-result.json
327
+ *
328
+ * @param {object} structured - formatStructuredResult 返回值
329
+ * @param {string} specDir - 规范目录(本地模式使用)
330
+ * @param {object} [opts] - 平台模式选项
331
+ * @param {string} [opts.runtimeRoot] - 平台模式运行时根目录
332
+ * @param {string} [opts.scanRunId] - scan run ID
333
+ * @returns {string|null} 写入的文件路径,失败时返回 null
334
+ */
335
+ export function writeStructuredResult(structured, specDir, opts = {}) {
336
+ if (!specDir && !opts.runtimeRoot) return null
337
+ try {
338
+ let outPath
339
+ if (opts.runtimeRoot && opts.scanRunId) {
340
+ const scanRunDir = join(opts.runtimeRoot, 'scan-runs', opts.scanRunId)
341
+ mkdirSync(scanRunDir, { recursive: true })
342
+ outPath = join(scanRunDir, 'postcheck-result.json')
343
+ } else if (specDir) {
344
+ const runtimeDir = join(specDir, '.runtime')
345
+ mkdirSync(runtimeDir, { recursive: true })
346
+ outPath = join(runtimeDir, 'postcheck-result.json')
347
+ } else {
348
+ return null
349
+ }
350
+
351
+ writeFileSync(outPath, JSON.stringify(structured, null, 2) + '\n')
352
+ return outPath
353
+ } catch (e) {
354
+ console.warn(` ⚠️ postcheck-result.json 写入失败: ${e.message}`)
355
+ return null
356
+ }
357
+ }
358
+
359
+ /**
360
+ * 打印 post-check 结果到 stdout
361
+ */
362
+ export function printScanPostCheckResult(result) {
363
+ if (result.checks.length === 0) {
364
+ console.log(' ✅ CLI post-check: 全部通过')
365
+ return
366
+ }
367
+
368
+ for (const check of result.checks) {
369
+ const icon = check.severity === 'failed' ? '❌' : '⚠️'
370
+ console.log(` ${icon} CLI post-check [${check.name}]: ${check.detail}`)
371
+ }
372
+ console.log(` 📋 最终状态: ${result.status}`)
373
+ }
374
+
375
+ /**
376
+ * scan 文档 header 幂等补填(2026-08-21 agent-手工产出审计第三批 E3)。
377
+ *
378
+ * runScanPostCheck 对缺 author/created_at 只 warning,agent 手改常忘——本函数补齐:
379
+ * author = git user.name(回退 unknown),created_at = 当前时间。已有两键的文件不动;
380
+ * 有 frontmatter(--- 开头)就地插缺失键,无则补一段 frontmatter。
381
+ * `sillyspec scan-fix-headers [--project <名>]` 入口;与 runScanPostCheck 的
382
+ * 「头部 512 字符含 author:/created_at:」判定口径一致。
383
+ *
384
+ * @param {{ cwd: string, specDir?: string|null, project?: string|null }} opts
385
+ * @returns {{ fixed: string[], skipped: string[] }}
386
+ */
387
+ export function fixScanDocHeaders({ cwd, specDir = null, project = null }) {
388
+ const specBase = specDir ? specDir : join(cwd, '.sillyspec')
389
+ const docsRoot = join(specBase, 'docs')
390
+ const projects = project
391
+ ? [project]
392
+ : (existsSync(docsRoot) ? readdirSync(docsRoot, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name) : [])
393
+
394
+ let author = 'unknown'
395
+ try {
396
+ author = git(cwd, ['config', 'user.name']) || 'unknown'
397
+ } catch { /* git 不可用 → unknown */ }
398
+ const now = nowWallClock()
399
+
400
+ const fixed = []
401
+ const skipped = []
402
+ for (const p of projects) {
403
+ const scanDir = join(docsRoot, p, 'scan')
404
+ if (!existsSync(scanDir)) continue
405
+ for (const f of readdirSync(scanDir)) {
406
+ if (!f.endsWith('.md')) continue
407
+ const filePath = join(scanDir, f)
408
+ let content
409
+ try {
410
+ content = readFileSync(filePath, 'utf8')
411
+ } catch {
412
+ continue
413
+ }
414
+ const header = content.slice(0, 512)
415
+ const hasAuthor = /author\s*:/.test(header)
416
+ const hasCreated = /created_at\s*:/.test(header)
417
+ if (hasAuthor && hasCreated) {
418
+ skipped.push(filePath)
419
+ continue
420
+ }
421
+ const inserts = []
422
+ if (!hasAuthor) inserts.push(`author: ${author}`)
423
+ if (!hasCreated) inserts.push(`created_at: ${now}`)
424
+ let newContent
425
+ if (content.startsWith('---\n') || content.startsWith('---\r\n')) {
426
+ // 有 frontmatter:插到开标签后(CRLF 归一写回,与其它 CLI 写入口径一致)
427
+ const normalized = content.replace(/\r\n?/g, '\n')
428
+ newContent = normalized.replace(/^---\n/, `---\n${inserts.join('\n')}\n`)
429
+ } else {
430
+ newContent = `---\n${inserts.join('\n')}\n---\n\n` + content
431
+ }
432
+ try {
433
+ writeFileSync(filePath, newContent)
434
+ fixed.push(filePath)
435
+ } catch { /* 只读文件等写失败 → 跳过不抛 */ }
436
+ }
437
+ }
438
+ return { fixed, skipped }
439
+ }
440
+
441
+ /**
442
+ * source_root 污染一键修复(2026-08-21 agent-手工产出审计第四批 C-3)。
443
+ *
444
+ * runScanPostCheck 对「agent 把产物写到 <source_root>/.sillyspec/ 而非平台 specRoot」只报
445
+ * FAILED 不修——agent 手工搬目录常搬错。本函数把检查枚举的同款清单(docs/projects/workflows/
446
+ * knowledge 目录 + manifest.json/local.yaml)搬到 specRoot 对应位置(已存在的不覆盖,报告
447
+ * skipped),搬完源目录若空则删除。与检查的路径口径逐字一致(pollutePaths/polluteFiles)。
448
+ *
449
+ * @param {{ cwd: string, specDir: string }} opts cwd=source_root,specDir=平台 spec_root 绝对路径
450
+ * @returns {{ moved: string[], skipped: string[], removedDirs: string[] }}
451
+ */
452
+ export function fixSourceRootLeak({ cwd, specDir }) {
453
+ if (!cwd || !specDir) return { moved: [], skipped: [], removedDirs: [] }
454
+ const moved = []
455
+ const skipped = []
456
+ const removedDirs = []
457
+ const pollutePaths = ['docs', 'projects', 'workflows', 'knowledge']
458
+ const polluteFiles = ['manifest.json', 'local.yaml']
459
+
460
+ // 目标目录确保存在
461
+ try { mkdirSync(join(specDir, '.runtime'), { recursive: true }) } catch {}
462
+
463
+ for (const file of polluteFiles) {
464
+ const src = join(cwd, '.sillyspec', file)
465
+ const dst = join(specDir, file)
466
+ if (!existsSync(src)) continue
467
+ if (existsSync(dst)) {
468
+ skipped.push(`local.yaml 已存在于 specRoot(保留两份,人工合并凭据后删源)`.replace('local.yaml', file))
469
+ continue
470
+ }
471
+ try {
472
+ renameSync(src, dst)
473
+ moved.push(`.sillyspec/${file} → specRoot/${file}`)
474
+ } catch (e) {
475
+ skipped.push(`.sillyspec/${file} 搬移失败: ${e.message}`)
476
+ }
477
+ }
478
+
479
+ for (const sub of pollutePaths) {
480
+ const srcDir = join(cwd, '.sillyspec', sub)
481
+ if (!existsSync(srcDir)) continue
482
+ let entries = []
483
+ try { entries = readdirSync(srcDir, { withFileTypes: true }) } catch { continue }
484
+ if (entries.length === 0) {
485
+ try { rmdirSync(srcDir); removedDirs.push(`.sillyspec/${sub}/(空目录)`) } catch {}
486
+ continue
487
+ }
488
+ const dstDir = join(specDir, sub)
489
+ mkdirSync(dstDir, { recursive: true })
490
+ let allMoved = true
491
+ for (const e of entries) {
492
+ const src = join(srcDir, e.name)
493
+ const dst = join(dstDir, e.name)
494
+ if (existsSync(dst)) {
495
+ skipped.push(`.sillyspec/${sub}/${e.name}(specRoot 已有同名,不覆盖——人工比对后择一保留)`)
496
+ allMoved = false
497
+ continue
498
+ }
499
+ try {
500
+ renameSync(src, dst)
501
+ moved.push(`.sillyspec/${sub}/${e.name} → specRoot/${sub}/${e.name}`)
502
+ } catch (err) {
503
+ skipped.push(`.sillyspec/${sub}/${e.name} 搬移失败: ${err.message}`)
504
+ allMoved = false
505
+ }
506
+ }
507
+ if (allMoved) {
508
+ try { rmdirSync(srcDir); removedDirs.push(`.sillyspec/${sub}/`) } catch {}
509
+ }
510
+ }
511
+ return { moved, skipped, removedDirs }
512
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * scan-staleness — scan 文档新鲜度提示(债单 D-7 方案 A,change: 2026-08-15 ql-20260815-013;
3
+ * 2026-08-16 ql-20260816-009-fb44 修正判定语义)
4
+ *
5
+ * 原则(doc-consistency-debt 第六节用户裁决):CLI 算事实注入——git 算出 scan 文档落后程度,
6
+ * 注入给 agent 的是一行结论不是一段劝说指令。brainstorm「加载项目上下文」读 scan 文档前
7
+ * 就知道"我读的 scan 文档可能没跟上近期结构变更",按提示核对或重扫,而非盲信。
8
+ *
9
+ * 判定语义(2026-08-16 修正,与第七节「behind 计数不参与判定」对齐):source_commit vs HEAD
10
+ * 的 `rev-list --count` 落后数只是「建议核对/重扫」的提示信号(旧快照可能未收录近期新增模块),
11
+ * 不是「文档失效/失真」的判据——文档引用是否失效由 docs-check(直接信号,src/docs-check.js)
12
+ * 负责,与 docs-gate 原则一致。status 三态 fresh / needs-refresh / unknown:needs-refresh =
13
+ * 该刷新/核对,不是文档判错。
14
+ *
15
+ * 纯读操作(git 只读 + frontmatter 解析),任何失败降级 null/unknown(advisory 信号不阻断流程)。
16
+ */
17
+ import { existsSync, readFileSync, readdirSync } from 'node:fs'
18
+ import { join } from 'node:path'
19
+ import { safeGit } from './git-helper.js'
20
+
21
+ /** 刷新提示阈值(commit 数 / 天数,超过任一即 needs-refresh;advisory 提示允许保守默认) */
22
+ export const STALENESS_THRESHOLDS = { commits: 50, days: 14 }
23
+
24
+ /**
25
+ * 纯函数:从 scan 文档内容解析 frontmatter 的 source_commit。
26
+ * @param {string} content 文档全文
27
+ * @returns {string|null} commit hex(短/长均可);无字段/无 frontmatter → null
28
+ */
29
+ export function parseSourceCommit(content) {
30
+ if (!content || typeof content !== 'string') return null
31
+ // \r? 容错 CRLF($ 前的 \r 会致 LF 正则失配)
32
+ const m = content.match(/^---\r?\n[\s\S]*?^source_commit:\s*([0-9a-f]{7,40})\s*\r?$/m)
33
+ return m ? m[1] : null
34
+ }
35
+
36
+ /**
37
+ * 计算单个 project 的 scan 文档漂移事实。
38
+ * @param {{ projectRoot: string, specBase: string, projectName: string, thresholds?: { commits: number, days: number } }} opts
39
+ * @returns {{ status: 'fresh'|'needs-refresh'|'unknown', behindCommits: number|null, daysSinceScan: number|null, sourceCommit: string|null, message: string }|null}
40
+ * null = 无 scan 文档目录(绿地项目等),调用方直接跳过;unknown = 有文档但算不出(无
41
+ * source_commit 字段 / git 失败),message 说明降级原因。
42
+ */
43
+ export function computeScanStaleness(opts) {
44
+ const { projectRoot, specBase, projectName, thresholds = STALENESS_THRESHOLDS } = opts || {}
45
+ if (!projectRoot || !specBase || !projectName) return null
46
+ const scanDir = join(specBase, 'docs', projectName, 'scan')
47
+ if (!existsSync(scanDir)) return null
48
+
49
+ // 任一 scan 文档带 source_commit 即可代表整批(modules.js 同批写入同值)
50
+ let sourceCommit = null
51
+ let checked = 0
52
+ for (const f of readdirSync(scanDir)) {
53
+ if (!f.endsWith('.md')) continue
54
+ checked++
55
+ const c = parseSourceCommit(readFileSync(join(scanDir, f), 'utf8'))
56
+ if (c) { sourceCommit = c; break }
57
+ }
58
+ if (checked === 0) return null
59
+ if (!sourceCommit) {
60
+ return { status: 'unknown', behindCommits: null, daysSinceScan: null, sourceCommit: null,
61
+ message: 'scan 文档无 source_commit 字段(旧版生成),无法算漂移提示——文档引用是否失效以 docs check 为准;如架构已大改建议重跑 sillyspec run scan --standard' }
62
+ }
63
+
64
+ const head = safeGit(projectRoot, ['rev-parse', 'HEAD'])
65
+ if (head.error || !head.value) {
66
+ return { status: 'unknown', behindCommits: null, daysSinceScan: null, sourceCommit,
67
+ message: `git 读取失败(${head.error || '无 HEAD'}),跳过漂移判定` }
68
+ }
69
+ const isAncestor = safeGit(projectRoot, ['merge-base', '--is-ancestor', sourceCommit, head.value.trim()])
70
+ if (isAncestor.error) {
71
+ return { status: 'unknown', behindCommits: null, daysSinceScan: null, sourceCommit,
72
+ message: `source_commit ${sourceCommit.slice(0, 7)} 不在当前分支历史(分支切换/rebase),跳过漂移判定` }
73
+ }
74
+ const countRes = safeGit(projectRoot, ['rev-list', '--count', `${sourceCommit}..HEAD`])
75
+ const behindCommits = countRes.error ? null : parseInt(countRes.value, 10)
76
+ if (behindCommits === null || Number.isNaN(behindCommits)) {
77
+ return { status: 'unknown', behindCommits: null, daysSinceScan: null, sourceCommit,
78
+ message: 'rev-list 计数失败,跳过漂移判定' }
79
+ }
80
+ const dateRes = safeGit(projectRoot, ['log', '-1', '--format=%cI', sourceCommit])
81
+ const daysSinceScan = dateRes.error ? null
82
+ : Math.floor((Date.now() - new Date(dateRes.value.trim()).getTime()) / 86400000)
83
+
84
+ const stale = behindCommits >= thresholds.commits || (daysSinceScan !== null && daysSinceScan >= thresholds.days)
85
+ if (!stale) {
86
+ return { status: 'fresh', behindCommits, daysSinceScan, sourceCommit,
87
+ message: `scan 文档无需刷新(落后 ${behindCommits} commit / ${daysSinceScan ?? '?'} 天,低于提示阈值)` }
88
+ }
89
+ return {
90
+ status: 'needs-refresh', behindCommits, daysSinceScan, sourceCommit,
91
+ message: `⚠️ scan 文档已落后源码 ${behindCommits} commit / ${daysSinceScan ?? '?'} 天(基线 ${sourceCommit.slice(0, 7)})。` +
92
+ `落后数≠文档错误——文档引用是否失效由 docs check 判定;旧快照可能未收录近期新增模块/流程,` +
93
+ `涉及结构/模块判断建议核对源码,或跑 sillyspec run scan --standard --force-rescan 刷新基线`,
94
+ }
95
+ }