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,679 @@
1
+ import { createServer } from 'http'
2
+ import { WebSocketServer } from 'ws'
3
+ import { existsSync, readFileSync, readdirSync, realpathSync, watch } from 'fs'
4
+ import { join, dirname, basename, sep, resolve, relative } from 'path'
5
+ import { fileURLToPath } from 'url'
6
+ import { homedir } from 'os'
7
+ import open from 'open'
8
+ import { parseProjectState, parseSillyspecDocsTree, parseProjectOverview, parseGitDetail, parseTechStackDetail } from './parser.js'
9
+ import { startWatcher, stopWatcher, addCustomScanPath, removeCustomScanPath, getCustomScanPaths, customScanPaths } from './watcher.js'
10
+ import { executeCommand } from './executor.js'
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url))
13
+
14
+ // WebSocket clients and active processes
15
+ let wss = null
16
+ const activeProcesses = new Map()
17
+ const allowedStages = new Set(['brainstorm', 'plan', 'execute', 'verify', 'scan', 'quick', 'explore', 'archive', 'status', 'doctor', 'auto'])
18
+
19
+ function isAllowedCliCommand(command) {
20
+ const args = String(command || '').trim().split(/\s+/).filter(Boolean)
21
+ if (args.length === 0) return false
22
+ if (args[0] === 'run') {
23
+ if (!allowedStages.has(args[1])) return false
24
+ const allowedFlags = new Set(['--status', '--reset', '--done', '--skip', '--output', '--input', '--change'])
25
+ for (let i = 2; i < args.length; i++) {
26
+ if (args[i].startsWith('-') && !allowedFlags.has(args[i])) return false
27
+ if (['--output', '--input', '--change'].includes(args[i])) i++
28
+ }
29
+ return true
30
+ }
31
+ if (args[0] === 'progress') {
32
+ const sub = args[1]
33
+ if (!['show', 'status', 'validate', 'reset'].includes(sub)) return false
34
+ for (let i = 2; i < args.length; i++) {
35
+ if (!['--stage', '--deep'].includes(args[i])) return false
36
+ if (args[i] === '--stage') i++
37
+ }
38
+ return true
39
+ }
40
+ return false
41
+ }
42
+
43
+ // Progress file watchers: projectPath -> { watcher, timer, refCount }
44
+ const progressWatchers = new Map()
45
+
46
+ function startProgressWatch(projectPath) {
47
+ if (progressWatchers.has(projectPath)) {
48
+ progressWatchers.get(projectPath).refCount++
49
+ return
50
+ }
51
+ // Watch the SQLite database file for changes (replaces old progress.json watch)
52
+ const dbFile = join(projectPath, '.sillyspec', '.runtime', 'sillyspec.db')
53
+ if (!existsSync(dbFile)) return
54
+
55
+ let timer = null
56
+ try {
57
+ const watcher = watch(dbFile, (eventType) => {
58
+ if (timer) clearTimeout(timer)
59
+ timer = setTimeout(() => {
60
+ timer = null
61
+ try {
62
+ const state = parseProjectState(projectPath)
63
+ broadcast({ type: 'project:update', data: { path: projectPath, state } })
64
+ } catch {}
65
+ }, 200)
66
+ })
67
+ progressWatchers.set(projectPath, { watcher, timer, refCount: 1 })
68
+ } catch {}
69
+ }
70
+
71
+ function stopProgressWatch(projectPath) {
72
+ const entry = progressWatchers.get(projectPath)
73
+ if (!entry) return
74
+ entry.refCount--
75
+ if (entry.refCount <= 0) {
76
+ entry.watcher.close()
77
+ if (entry.timer) clearTimeout(entry.timer)
78
+ progressWatchers.delete(projectPath)
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Broadcast message to all connected WebSocket clients
84
+ * @param {object} data - Data to broadcast
85
+ */
86
+ function broadcast(data) {
87
+ if (!wss) return
88
+ const message = JSON.stringify(data)
89
+ wss.clients.forEach(client => {
90
+ if (client.readyState === 1) { // OPEN
91
+ client.send(message)
92
+ }
93
+ })
94
+ }
95
+
96
+ function isInside(parent, child) {
97
+ const rel = relative(resolve(parent), resolve(child))
98
+ return rel === '' || (!!rel && !rel.startsWith('..') && !rel.includes(`..${sep}`))
99
+ }
100
+
101
+ function isSillyspecPath(filePath) {
102
+ const parts = resolve(filePath).split(/[\\/]+/)
103
+ return parts.includes('.sillyspec')
104
+ }
105
+
106
+ // 凭据/敏感运行时文件:local.yaml 含 SillyHub token(connect 明文写入),.plan.md.lock 等锁文件
107
+ // 是并发原语非文档。/api/docs/content 无鉴权(仅 Origin 检查),这些文件绝不经 HTTP 暴露。
108
+ function isCredentialPath(filePath) {
109
+ const base = basename(resolve(filePath)).toLowerCase()
110
+ return base === 'local.yaml' || base === 'local.yml' || base.endsWith('.lock') || base === 'sillyspec-platform.json'
111
+ }
112
+
113
+ function isViewableDocPath(filePath) {
114
+ return /\.(md|markdown|mdx|html?|txt|log|json|ya?ml|toml|xml|csv)$/i.test(filePath)
115
+ }
116
+
117
+ // ── 项目发现缓存 + 路径校验(体检 SEC-06;TTL 缓存兼避免每请求全量扫 HOME 级目录)──
118
+ let _projectsCache = { at: 0, data: null }
119
+ async function getDiscoveredProjects() {
120
+ const TTL_MS = 10_000
121
+ if (_projectsCache.data && Date.now() - _projectsCache.at < TTL_MS) return _projectsCache.data
122
+ const data = await discoverProjects()
123
+ _projectsCache = { at: Date.now(), data }
124
+ return data
125
+ }
126
+
127
+ /**
128
+ * 目标路径是否位于某个已发现项目内。此前 /api/docs/content 只查"路径含 .sillyspec 组件",
129
+ * 磁盘上任意 .sillyspec 树(含 .runtime 运行时文件)都可读;detail API 还以任意路径为
130
+ * cwd 跑 git。收紧为"必须位于已发现项目的目录内"(与 WS cli:execute 的项目校验同范式)。
131
+ */
132
+ async function isKnownProjectPath(targetPath) {
133
+ const projects = await getDiscoveredProjects()
134
+ return projects.some(p => isInside(p.path, targetPath))
135
+ }
136
+
137
+ /**
138
+ * 跨站防护(体检 SEC-03 收紧):dashboard 前端由本服务自身托管,合法浏览器请求的
139
+ * Origin 端口必然等于服务端口。此前只校验 hostname,本机任意其他端口的 web 页面
140
+ * (被攻陷的 dev server / 恶意 npm 包起的页面)也能通过检查,驱动 WS cli:execute 等
141
+ * 完整 API。无 Origin 头(curl / 本地非浏览器进程)保持放行——与 localhost 服务
142
+ * "本地进程可信"的固有级别一致,浏览器攻击面才是本检查能挡的部分。
143
+ */
144
+ function isSameDashboardOrigin(origin, ownPort) {
145
+ if (!origin) return true
146
+ try {
147
+ const { hostname, port: originPort } = new URL(origin)
148
+ if (hostname !== 'localhost' && hostname !== '127.0.0.1' && hostname !== '::1') return false
149
+ return String(originPort) === String(ownPort)
150
+ } catch {
151
+ return false
152
+ }
153
+ }
154
+
155
+ // --- Shared scan logic (aligned with watcher.js) ---
156
+
157
+ const excludeDirs = new Set([
158
+ '.Trash', '.cache', '.npm', '.local', '.vscode', 'Library',
159
+ '.git', 'node_modules', '.Trash-*', '.DS_Store', '.config',
160
+ '.cocoapods', '.gem', '.rvm', '.nvm', '.asdf', '.brew',
161
+ 'AppData', 'Application Data', '.cargo', '.rustup',
162
+ '.nuget', '.android', '.gradle', '.m2', '.vscode-server'
163
+ ])
164
+
165
+ function shouldExclude(name, cwd) {
166
+ if (excludeDirs.has(name)) return true
167
+ for (const pattern of excludeDirs) {
168
+ if (pattern.includes('*')) {
169
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$')
170
+ if (regex.test(name)) return true
171
+ }
172
+ }
173
+ const cwdName = cwd.split(sep).pop() || cwd.split('/').pop() || ''
174
+ if (name.startsWith('.') && name !== cwdName) return true
175
+ return false
176
+ }
177
+
178
+ function scanDirectory(baseDir, seen, maxDepth = 2, currentDepth = 0) {
179
+ const cwd = process.cwd()
180
+ const projects = []
181
+ try {
182
+ const entries = readdirSync(baseDir, { withFileTypes: true })
183
+ for (const entry of entries) {
184
+ if (!entry.isDirectory()) continue
185
+ if (shouldExclude(entry.name, cwd)) continue
186
+ const dirPath = join(baseDir, entry.name)
187
+ let realPath
188
+ try { realPath = realpathSync(dirPath) } catch { realPath = dirPath }
189
+ const normalizedPath = realPath.toLowerCase()
190
+ if (seen.has(normalizedPath)) continue
191
+ seen.add(normalizedPath)
192
+ if (existsSync(join(dirPath, '.sillyspec'))) {
193
+ projects.push({ name: entry.name, path: dirPath })
194
+ }
195
+ if (currentDepth < maxDepth) {
196
+ projects.push(...scanDirectory(dirPath, seen, maxDepth, currentDepth + 1))
197
+ }
198
+ }
199
+ } catch (err) {}
200
+ return projects
201
+ }
202
+
203
+ /**
204
+ * Discover all SillySpec projects
205
+ * @returns {Promise<Array>} Array of project objects
206
+ */
207
+ async function discoverProjects() {
208
+ const home = homedir()
209
+ const cwd = process.cwd()
210
+
211
+ const scanDirs = new Set()
212
+ scanDirs.add(cwd)
213
+ scanDirs.add(dirname(cwd))
214
+ scanDirs.add(dirname(dirname(cwd)))
215
+ scanDirs.add(home)
216
+
217
+ const extraDirs = [
218
+ 'Desktop', '桌面', 'Documents', '文档', 'Downloads', '下载',
219
+ 'Projects', '项目', 'Work', '工作', 'Repos', 'Code', 'src', 'dev',
220
+ 'workspace', '工作区'
221
+ ]
222
+
223
+ for (const extra of extraDirs) {
224
+ const extraPath = join(home, extra)
225
+ if (existsSync(extraPath)) scanDirs.add(extraPath)
226
+ }
227
+
228
+ for (const customPath of customScanPaths) {
229
+ if (existsSync(customPath)) scanDirs.add(customPath)
230
+ }
231
+
232
+ const seen = new Set()
233
+ const projects = []
234
+
235
+ // Normalize cwd for dedup
236
+ let cwdReal
237
+ try { cwdReal = realpathSync(cwd) } catch { cwdReal = cwd }
238
+ seen.add(cwdReal.toLowerCase())
239
+ if (existsSync(join(cwd, '.sillyspec'))) {
240
+ projects.push({ name: basename(cwd), path: cwd })
241
+ }
242
+
243
+ for (const baseDir of scanDirs) {
244
+ projects.push(...scanDirectory(baseDir, seen, 2, 0))
245
+ }
246
+
247
+ return projects
248
+ }
249
+
250
+ /**
251
+ * Handle WebSocket message for CLI execution
252
+ * @param {object} ws - WebSocket client
253
+ * @param {object} data - Message data
254
+ */
255
+ function handleCliExecute(ws, data) {
256
+ const { projectName, command } = data
257
+
258
+ if (!projectName || !command) {
259
+ ws.send(JSON.stringify({
260
+ type: 'cli:error',
261
+ data: { message: 'Missing projectName or command' }
262
+ }))
263
+ return
264
+ }
265
+
266
+ if (!isAllowedCliCommand(command)) {
267
+ ws.send(JSON.stringify({
268
+ type: 'cli:error',
269
+ data: { message: `Command not allowed: ${command}` }
270
+ }))
271
+ return
272
+ }
273
+
274
+ discoverProjects().then(projects => {
275
+ const project = projects.find(p => p.name === projectName)
276
+ if (!project) {
277
+ ws.send(JSON.stringify({
278
+ type: 'cli:error',
279
+ data: { message: `Project not found: ${projectName}` }
280
+ }))
281
+ return
282
+ }
283
+
284
+ const existingKill = activeProcesses.get(projectName)
285
+ if (existingKill) existingKill()
286
+
287
+ const kill = executeCommand(
288
+ project.path,
289
+ command,
290
+ (output) => {
291
+ broadcast({
292
+ type: 'cli:output',
293
+ data: { projectName, output: output.data, outputType: output.type }
294
+ })
295
+ },
296
+ (result) => {
297
+ activeProcesses.delete(projectName)
298
+ broadcast({
299
+ type: 'cli:complete',
300
+ data: { projectName, exitCode: result.code, signal: result.signal }
301
+ })
302
+ }
303
+ )
304
+
305
+ activeProcesses.set(projectName, kill)
306
+ broadcast({ type: 'cli:started', data: { projectName, command } })
307
+ })
308
+ }
309
+
310
+ /**
311
+ * Start the dashboard server
312
+ * @param {object} options - Server options
313
+ * @returns {object} HTTP server instance
314
+ */
315
+ function startServer({ port = 3456, open: openBrowser = true } = {}) {
316
+ const server = createServer((req, res) => {
317
+ const origin = req.headers.origin
318
+ if (!isSameDashboardOrigin(origin, port)) {
319
+ res.writeHead(403)
320
+ res.end('Forbidden')
321
+ return
322
+ }
323
+ res.setHeader('Access-Control-Allow-Origin', origin || `http://127.0.0.1:${port}`)
324
+ res.setHeader('Vary', 'Origin')
325
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
326
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
327
+
328
+ if (req.method === 'OPTIONS') {
329
+ res.writeHead(200)
330
+ res.end()
331
+ return
332
+ }
333
+
334
+ if (req.url === '/api/projects') {
335
+ discoverProjects().then(projects => {
336
+ res.setHeader('Content-Type', 'application/json')
337
+ res.writeHead(200)
338
+ res.end(JSON.stringify(projects))
339
+ }).catch(err => {
340
+ res.writeHead(500)
341
+ res.end(JSON.stringify({ error: err.message }))
342
+ })
343
+ return
344
+ }
345
+
346
+ // Detail API(SEC-06:projectPath 须位于已发现项目内——parseGitDetail 以其为 cwd 跑 git)
347
+ const detailMatch = req.url?.match(/^\/api\/projects\/(.+)\/detail(\?|$)/)
348
+ if (detailMatch) {
349
+ const projectPath = decodeURIComponent(detailMatch[1])
350
+ const url = new URL(req.url, `http://${req.headers.host}`)
351
+ const type = url.searchParams.get('type')
352
+ isKnownProjectPath(projectPath).then(known => {
353
+ if (!known) {
354
+ res.writeHead(403)
355
+ res.end(JSON.stringify({ error: 'Unknown project path' }))
356
+ return
357
+ }
358
+ let data
359
+ try {
360
+ if (type === 'git') data = parseGitDetail(projectPath)
361
+ else if (type === 'tech') data = parseTechStackDetail(projectPath)
362
+ else if (type === 'docs') data = parseSillyspecDocsTree(projectPath).groups
363
+ else { res.writeHead(400); res.end(JSON.stringify({ error: 'Invalid type' })); return }
364
+ res.setHeader('Content-Type', 'application/json')
365
+ res.writeHead(200)
366
+ res.end(JSON.stringify(data))
367
+ } catch (err) {
368
+ res.writeHead(500)
369
+ res.end(JSON.stringify({ error: err.message }))
370
+ }
371
+ }).catch(() => {
372
+ res.writeHead(500)
373
+ res.end(JSON.stringify({ error: 'Project discovery failed' }))
374
+ })
375
+ return
376
+ }
377
+
378
+ // Overview API(SEC-06:同 detail,projectPath 须位于已发现项目内)
379
+ if (req.url?.startsWith('/api/projects/') && req.url.endsWith('/overview')) {
380
+ const parts = req.url.replace('/api/projects/', '').replace('/overview', '').split('/')
381
+ const projectPath = decodeURIComponent(parts.join('/'))
382
+ isKnownProjectPath(projectPath).then(known => {
383
+ if (!known) {
384
+ res.writeHead(403)
385
+ res.end(JSON.stringify({ error: 'Unknown project path' }))
386
+ return
387
+ }
388
+ try {
389
+ const overview = parseProjectOverview(projectPath)
390
+ res.setHeader('Content-Type', 'application/json')
391
+ res.writeHead(200)
392
+ res.end(JSON.stringify(overview))
393
+ } catch (err) {
394
+ res.writeHead(500)
395
+ res.end(JSON.stringify({ error: err.message }))
396
+ }
397
+ }).catch(() => {
398
+ res.writeHead(500)
399
+ res.end(JSON.stringify({ error: 'Project discovery failed' }))
400
+ })
401
+ return
402
+ }
403
+
404
+ if (req.url?.startsWith('/api/project/')) {
405
+ const projectName = decodeURIComponent(req.url.split('/').pop())
406
+ discoverProjects().then(projects => {
407
+ const project = projects.find(p => p.name === projectName)
408
+ if (project) {
409
+ const state = parseProjectState(project.path)
410
+ res.setHeader('Content-Type', 'application/json')
411
+ res.writeHead(200)
412
+ res.end(JSON.stringify({ ...project, state }))
413
+ } else {
414
+ res.writeHead(404)
415
+ res.end(JSON.stringify({ error: 'Project not found' }))
416
+ }
417
+ }).catch(err => {
418
+ res.writeHead(500)
419
+ res.end(JSON.stringify({ error: err.message }))
420
+ })
421
+ return
422
+ }
423
+
424
+ // Docs API
425
+ if (req.url?.startsWith('/api/docs/content')) {
426
+ const url = new URL(req.url, `http://${req.headers.host}`)
427
+ const filePath = url.searchParams.get('path')
428
+ if (!filePath) {
429
+ res.writeHead(400)
430
+ res.end(JSON.stringify({ error: 'Missing path parameter' }))
431
+ return
432
+ }
433
+ // Security: only allow reading viewable text documents under a discovered
434
+ // project's .sillyspec tree(SEC-06:不限在已发现项目内则磁盘上任意 .sillyspec 树可读).
435
+ const normalizedPath = resolve(filePath)
436
+ isKnownProjectPath(normalizedPath).then(known => {
437
+ if (!known || !isSillyspecPath(normalizedPath) || !isViewableDocPath(normalizedPath) || isCredentialPath(normalizedPath)) {
438
+ res.writeHead(403)
439
+ res.end(JSON.stringify({ error: 'Access denied' }))
440
+ return
441
+ }
442
+ if (existsSync(normalizedPath)) {
443
+ try {
444
+ const content = readFileSync(normalizedPath, 'utf-8')
445
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8')
446
+ res.writeHead(200)
447
+ res.end(content)
448
+ } catch (err) {
449
+ res.writeHead(500)
450
+ res.end(JSON.stringify({ error: err.message }))
451
+ }
452
+ } else {
453
+ res.writeHead(404)
454
+ res.end(JSON.stringify({ error: 'File not found' }))
455
+ }
456
+ }).catch(() => {
457
+ res.writeHead(500)
458
+ res.end(JSON.stringify({ error: 'Project discovery failed' }))
459
+ })
460
+ return
461
+ }
462
+
463
+ if (req.url?.startsWith('/api/docs')) {
464
+ const url = new URL(req.url, `http://${req.headers.host}`)
465
+ const projectPath = url.searchParams.get('project')
466
+ if (!projectPath) {
467
+ res.writeHead(400)
468
+ res.end(JSON.stringify({ error: 'Missing project parameter' }))
469
+ return
470
+ }
471
+ // SEC-06 对齐:detail/content API 均已限"已发现项目内",此处此前漏校验——
472
+ // 任意绝对路径可列其 .sillyspec/docs 树与根文档文件名
473
+ isKnownProjectPath(projectPath).then(known => {
474
+ if (!known) {
475
+ res.writeHead(403)
476
+ res.end(JSON.stringify({ error: 'Unknown project path' }))
477
+ return
478
+ }
479
+ const docs = parseSillyspecDocsTree(projectPath)
480
+ res.setHeader('Content-Type', 'application/json')
481
+ res.writeHead(200)
482
+ res.end(JSON.stringify(docs))
483
+ })
484
+ return
485
+ }
486
+
487
+ // Serve static files (dist/)
488
+ const distDir = join(__dirname, '../dist')
489
+ const indexPath = join(distDir, 'index.html')
490
+ if (existsSync(distDir)) {
491
+ const url = new URL(req.url || '/', `http://${req.headers.host}`)
492
+ const requestPath = decodeURIComponent(url.pathname)
493
+ const filePath = resolve(distDir, requestPath === '/' ? 'index.html' : `.${requestPath}`)
494
+ if (!isInside(distDir, filePath)) {
495
+ res.writeHead(403)
496
+ res.end('Access denied')
497
+ return
498
+ }
499
+ if (existsSync(filePath)) {
500
+ const ext = filePath.split('.').pop()
501
+ const mimeTypes = { html: 'text/html', js: 'application/javascript', css: 'text/css', svg: 'image/svg+xml', png: 'image/png', jpg: 'image/jpeg' }
502
+ res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream')
503
+ res.writeHead(200)
504
+ res.end(readFileSync(filePath))
505
+ return
506
+ }
507
+ if (existsSync(indexPath)) {
508
+ res.setHeader('Content-Type', 'text/html')
509
+ res.writeHead(200)
510
+ res.end(readFileSync(indexPath, 'utf8'))
511
+ return
512
+ }
513
+ }
514
+
515
+ res.writeHead(404)
516
+ res.end('Not found')
517
+ })
518
+
519
+ wss = new WebSocketServer({ server })
520
+
521
+ wss.on('error', (err) => {
522
+ console.error('WebSocket server error:', err)
523
+ })
524
+
525
+ wss.on('connection', (ws, req) => {
526
+ if (!isSameDashboardOrigin(req.headers.origin, port)) {
527
+ ws.close(1008, 'Forbidden origin')
528
+ return
529
+ }
530
+ console.log('WebSocket client connected')
531
+
532
+ // Send initial projects list
533
+ discoverProjects().then(projects => {
534
+ const projectsWithState = projects.map(p => ({
535
+ ...p,
536
+ state: parseProjectState(p.path),
537
+ overview: parseProjectOverview(p.path)
538
+ }))
539
+
540
+ ws.send(JSON.stringify({
541
+ type: 'projects:init',
542
+ data: projectsWithState
543
+ }))
544
+
545
+ // Start watching progress files for all discovered projects
546
+ for (const p of projectsWithState) {
547
+ startProgressWatch(p.path)
548
+ }
549
+
550
+ // Send current scan paths
551
+ ws.send(JSON.stringify({
552
+ type: 'scan:paths',
553
+ data: getCustomScanPaths()
554
+ }))
555
+ }).catch(err => {
556
+ console.error('Error sending initial projects:', err)
557
+ })
558
+
559
+ ws.on('message', (message) => {
560
+ try {
561
+ const data = JSON.parse(message)
562
+
563
+ switch (data.type) {
564
+ case 'cli:execute':
565
+ handleCliExecute(ws, data.data)
566
+ break
567
+ case 'cli:kill':
568
+ const kill = activeProcesses.get(data.data.projectName)
569
+ if (kill) {
570
+ kill()
571
+ activeProcesses.delete(data.data.projectName)
572
+ broadcast({ type: 'cli:killed', data: { projectName: data.data.projectName } })
573
+ }
574
+ break
575
+ case 'scan:add-path':
576
+ if (data.data?.path) {
577
+ addCustomScanPath(data.data.path)
578
+ broadcast({ type: 'scan:paths', data: getCustomScanPaths() })
579
+ // Resend projects after scan
580
+ discoverProjects().then(projects => {
581
+ broadcast({
582
+ type: 'projects:updated',
583
+ data: projects.map(p => ({
584
+ ...p,
585
+ state: parseProjectState(p.path),
586
+ overview: parseProjectOverview(p.path)
587
+ }))
588
+ })
589
+ })
590
+ }
591
+ break
592
+ case 'scan:remove-path':
593
+ if (data.data?.path) {
594
+ removeCustomScanPath(data.data.path)
595
+ ws.send(JSON.stringify({ type: 'scan:paths', data: getCustomScanPaths() }))
596
+ }
597
+ break
598
+ case 'scan:get-paths':
599
+ ws.send(JSON.stringify({ type: 'scan:paths', data: getCustomScanPaths() }))
600
+ break
601
+ case 'docs:get':
602
+ if (data.data?.projectPath) {
603
+ // SEC-06 对齐:与 /api/docs 同口径,仅已发现项目可列 docs 树
604
+ isKnownProjectPath(data.data.projectPath).then(known => {
605
+ if (!known) {
606
+ ws.send(JSON.stringify({ type: 'docs:tree', data: { error: 'Unknown project path' } }))
607
+ return
608
+ }
609
+ const docs = parseSillyspecDocsTree(data.data.projectPath)
610
+ ws.send(JSON.stringify({ type: 'docs:tree', data: docs }))
611
+ })
612
+ }
613
+ break
614
+ default:
615
+ console.log('Unknown message type:', data.type)
616
+ }
617
+ } catch (err) {
618
+ console.error('Error handling message:', err)
619
+ }
620
+ })
621
+
622
+ ws.on('close', () => {
623
+ console.log('WebSocket client disconnected')
624
+ // Decrement progress watchers — we track which projects this client was watching
625
+ // via the initial projects:init. For simplicity, stop all that we started.
626
+ // (Other clients will re-start via their connection if still active)
627
+ for (const [path] of progressWatchers) {
628
+ stopProgressWatch(path)
629
+ }
630
+ })
631
+
632
+ ws.on('error', (err) => {
633
+ console.error('WebSocket error:', err)
634
+ })
635
+ })
636
+
637
+ // listen 前置:Windows 下 startWatcher 的初始全盘同步扫描(homedir/Temp/桌面 depth 2 +
638
+ // 每项目多次 execSync('git'))实测 150s+,若排在其后则服务永不响应(假死)。
639
+ // 先绑定端口(listen 回调立即可达),扫描用 setImmediate 延后到事件循环——端口立即响应,
640
+ // 初始 projects 数据由扫描完成后 broadcast 补发。
641
+ server.listen(port, '127.0.0.1', () => {
642
+ console.log(`Dashboard server running on http://127.0.0.1:${port}`)
643
+ if (openBrowser) {
644
+ open(`http://127.0.0.1:${port}`)
645
+ }
646
+ })
647
+
648
+ setImmediate(() => {
649
+ try {
650
+ startWatcher((projects) => {
651
+ broadcast({ type: 'projects:updated', data: projects })
652
+ })
653
+ } catch (err) {
654
+ console.error('Failed to start file watcher:', err)
655
+ }
656
+ })
657
+
658
+ const shutdown = () => {
659
+ stopWatcher()
660
+ activeProcesses.forEach(kill => kill())
661
+ activeProcesses.clear()
662
+ // server.close() 只停止接受新连接,不终止已升级的 WS 连接——事件循环被客户端
663
+ // 持有导致 Ctrl+C 无法退出(体检 BUG-10)。先 terminate 客户端再关 wss。
664
+ if (wss) {
665
+ for (const client of wss.clients) {
666
+ try { client.terminate() } catch { /* 已断开的客户端忽略 */ }
667
+ }
668
+ try { wss.close() } catch { /* 关闭失败不阻断退出 */ }
669
+ }
670
+ server.close()
671
+ }
672
+
673
+ process.on('SIGTERM', shutdown)
674
+ process.on('SIGINT', shutdown)
675
+
676
+ return server
677
+ }
678
+
679
+ export { startServer, broadcast, discoverProjects }