sillyspec 3.27.6 → 3.27.7

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 (921) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +87 -87
  2. package/.claude/skills/sillyspec-auto/SKILL.md +93 -93
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -114
  4. package/.claude/skills/sillyspec-commit/SKILL.md +67 -67
  5. package/.claude/skills/sillyspec-continue/SKILL.md +47 -47
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +76 -76
  7. package/.claude/skills/sillyspec-execute/SKILL.md +214 -214
  8. package/.claude/skills/sillyspec-explore/SKILL.md +102 -102
  9. package/.claude/skills/sillyspec-export/SKILL.md +57 -57
  10. package/.claude/skills/sillyspec-init/SKILL.md +177 -177
  11. package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
  12. package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
  13. package/.claude/skills/sillyspec-propose/SKILL.md +87 -0
  14. package/.claude/skills/sillyspec-quick/SKILL.md +116 -116
  15. package/.claude/skills/sillyspec-resume/SKILL.md +70 -70
  16. package/.claude/skills/sillyspec-scan/SKILL.md +79 -79
  17. package/.claude/skills/sillyspec-state/SKILL.md +64 -64
  18. package/.claude/skills/sillyspec-status/SKILL.md +54 -54
  19. package/.claude/skills/sillyspec-verify/SKILL.md +87 -87
  20. package/.claude/skills/sillyspec-workspace/SKILL.md +175 -175
  21. package/.claude/skills/verify-per-user/SKILL.md +6 -0
  22. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  23. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-auto/SKILL.md +93 -0
  24. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  25. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  26. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-continue/SKILL.md +47 -0
  27. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  28. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-execute/SKILL.md +214 -0
  29. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-explore/SKILL.md +102 -0
  30. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-export/SKILL.md +57 -0
  31. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-init/SKILL.md +177 -0
  32. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  33. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  34. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-propose/SKILL.md +87 -0
  35. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  36. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-resume/SKILL.md +70 -0
  37. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  38. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-state/SKILL.md +64 -0
  39. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-status/SKILL.md +54 -0
  40. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  41. package/.worktrees/2c37b1d7/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
  42. package/.worktrees/2c37b1d7/.claude/skills/verify-per-user/SKILL.md +6 -0
  43. package/.worktrees/2c37b1d7/.gitattributes +2 -0
  44. package/.worktrees/2c37b1d7/.husky/pre-push +20 -0
  45. package/.worktrees/2c37b1d7/README.md +203 -0
  46. package/.worktrees/2c37b1d7/SKILL.md +91 -0
  47. package/.worktrees/2c37b1d7/architecture-analysis.md +448 -0
  48. package/.worktrees/2c37b1d7/bin/sillyspec.js +2 -0
  49. package/.worktrees/2c37b1d7/eval/README.md +86 -0
  50. package/.worktrees/2c37b1d7/eval/eval.config.json +28 -0
  51. package/.worktrees/2c37b1d7/eval/lib/adapters/cli.mjs +63 -0
  52. package/.worktrees/2c37b1d7/eval/lib/adapters/dryrun.mjs +27 -0
  53. package/.worktrees/2c37b1d7/eval/lib/config.mjs +82 -0
  54. package/.worktrees/2c37b1d7/eval/lib/db.mjs +76 -0
  55. package/.worktrees/2c37b1d7/eval/lib/report.mjs +120 -0
  56. package/.worktrees/2c37b1d7/eval/lib/runner.mjs +157 -0
  57. package/.worktrees/2c37b1d7/eval/lib/tasks.mjs +73 -0
  58. package/.worktrees/2c37b1d7/eval/lib/util.mjs +107 -0
  59. package/.worktrees/2c37b1d7/eval/lib/workdir.mjs +30 -0
  60. package/.worktrees/2c37b1d7/eval/run.mjs +83 -0
  61. package/.worktrees/2c37b1d7/eval/tasks/001-slugify/files/package.json +6 -0
  62. package/.worktrees/2c37b1d7/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
  63. package/.worktrees/2c37b1d7/eval/tasks/001-slugify/task.json +6 -0
  64. package/.worktrees/2c37b1d7/eval/tasks/001-slugify/verify.mjs +28 -0
  65. package/.worktrees/2c37b1d7/eval/tasks/002-each-day/files/package.json +6 -0
  66. package/.worktrees/2c37b1d7/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
  67. package/.worktrees/2c37b1d7/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
  68. package/.worktrees/2c37b1d7/eval/tasks/002-each-day/task.json +6 -0
  69. package/.worktrees/2c37b1d7/eval/tasks/002-each-day/verify.mjs +31 -0
  70. package/.worktrees/2c37b1d7/eval/tasks/003-word-report/files/data/notes.txt +5 -0
  71. package/.worktrees/2c37b1d7/eval/tasks/003-word-report/files/package.json +6 -0
  72. package/.worktrees/2c37b1d7/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
  73. package/.worktrees/2c37b1d7/eval/tasks/003-word-report/task.json +6 -0
  74. package/.worktrees/2c37b1d7/eval/tasks/003-word-report/verify.mjs +38 -0
  75. package/.worktrees/2c37b1d7/logo.jpg +0 -0
  76. package/.worktrees/2c37b1d7/package-lock.json +977 -0
  77. package/.worktrees/2c37b1d7/package.json +41 -0
  78. package/.worktrees/2c37b1d7/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
  79. package/.worktrees/2c37b1d7/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  80. package/.worktrees/2c37b1d7/packages/dashboard/dist/favicon.jpg +0 -0
  81. package/.worktrees/2c37b1d7/packages/dashboard/dist/index.html +16 -0
  82. package/.worktrees/2c37b1d7/packages/dashboard/dist/logo.jpg +0 -0
  83. package/.worktrees/2c37b1d7/packages/dashboard/dist/prototype-dashboard.html +836 -0
  84. package/.worktrees/2c37b1d7/packages/dashboard/dist/prototype-overview.html +256 -0
  85. package/.worktrees/2c37b1d7/packages/dashboard/package.json +25 -0
  86. package/.worktrees/2c37b1d7/packages/dashboard/server/executor.js +95 -0
  87. package/.worktrees/2c37b1d7/packages/dashboard/server/index.js +679 -0
  88. package/.worktrees/2c37b1d7/packages/dashboard/server/parser.js +526 -0
  89. package/.worktrees/2c37b1d7/packages/dashboard/server/watcher.js +357 -0
  90. package/.worktrees/2c37b1d7/src/agent-session-log.js +803 -0
  91. package/.worktrees/2c37b1d7/src/change-list.js +290 -0
  92. package/.worktrees/2c37b1d7/src/change-risk-profile.js +339 -0
  93. package/.worktrees/2c37b1d7/src/check-primitives.js +65 -0
  94. package/.worktrees/2c37b1d7/src/classify-change.js +109 -0
  95. package/.worktrees/2c37b1d7/src/commit-suggest.js +202 -0
  96. package/.worktrees/2c37b1d7/src/config-cat.js +88 -0
  97. package/.worktrees/2c37b1d7/src/config-schema.js +328 -0
  98. package/.worktrees/2c37b1d7/src/constants.js +97 -0
  99. package/.worktrees/2c37b1d7/src/contract-matrix.js +533 -0
  100. package/.worktrees/2c37b1d7/src/datetime.js +20 -0
  101. package/.worktrees/2c37b1d7/src/db-engine.js +55 -0
  102. package/.worktrees/2c37b1d7/src/db.js +381 -0
  103. package/.worktrees/2c37b1d7/src/decision-distill.js +517 -0
  104. package/.worktrees/2c37b1d7/src/dispatch/backends/local-agent.js +155 -0
  105. package/.worktrees/2c37b1d7/src/dispatch/backends/sillyhub-mcp.js +245 -0
  106. package/.worktrees/2c37b1d7/src/dispatch/probe.js +246 -0
  107. package/.worktrees/2c37b1d7/src/dispatch/strategy.js +121 -0
  108. package/.worktrees/2c37b1d7/src/docs-check.js +825 -0
  109. package/.worktrees/2c37b1d7/src/docs-debt.js +393 -0
  110. package/.worktrees/2c37b1d7/src/docs-gate.js +107 -0
  111. package/.worktrees/2c37b1d7/src/doctor-diagnostics.js +1099 -0
  112. package/.worktrees/2c37b1d7/src/endpoint-extractor.js +409 -0
  113. package/.worktrees/2c37b1d7/src/foreign-declared.js +155 -0
  114. package/.worktrees/2c37b1d7/src/fs-atomic.js +73 -0
  115. package/.worktrees/2c37b1d7/src/git-helper.js +150 -0
  116. package/.worktrees/2c37b1d7/src/hooks/claude-pre-tool-use.cjs +130 -0
  117. package/.worktrees/2c37b1d7/src/hooks/worktree-guard.js +892 -0
  118. package/.worktrees/2c37b1d7/src/index.js +2854 -0
  119. package/.worktrees/2c37b1d7/src/init.js +626 -0
  120. package/.worktrees/2c37b1d7/src/knowledge-match.js +231 -0
  121. package/.worktrees/2c37b1d7/src/local-detect.js +128 -0
  122. package/.worktrees/2c37b1d7/src/local-register.js +104 -0
  123. package/.worktrees/2c37b1d7/src/machine-interface.js +543 -0
  124. package/.worktrees/2c37b1d7/src/migrate.js +127 -0
  125. package/.worktrees/2c37b1d7/src/module-impact.js +166 -0
  126. package/.worktrees/2c37b1d7/src/module-resolve.js +222 -0
  127. package/.worktrees/2c37b1d7/src/modules.js +580 -0
  128. package/.worktrees/2c37b1d7/src/plan-adopt-waves.js +126 -0
  129. package/.worktrees/2c37b1d7/src/progress/change-registry.js +336 -0
  130. package/.worktrees/2c37b1d7/src/progress/consistency-doctor.js +355 -0
  131. package/.worktrees/2c37b1d7/src/progress/shared.js +36 -0
  132. package/.worktrees/2c37b1d7/src/progress/stage-machine.js +594 -0
  133. package/.worktrees/2c37b1d7/src/progress/step-store.js +265 -0
  134. package/.worktrees/2c37b1d7/src/progress.js +1304 -0
  135. package/.worktrees/2c37b1d7/src/quick-recommend.js +115 -0
  136. package/.worktrees/2c37b1d7/src/quicklog.js +883 -0
  137. package/.worktrees/2c37b1d7/src/review-tier.js +77 -0
  138. package/.worktrees/2c37b1d7/src/run/command.js +1641 -0
  139. package/.worktrees/2c37b1d7/src/run/complete-handlers.js +1552 -0
  140. package/.worktrees/2c37b1d7/src/run/complete.js +1259 -0
  141. package/.worktrees/2c37b1d7/src/run/concurrent-detect.js +156 -0
  142. package/.worktrees/2c37b1d7/src/run/gates.js +1111 -0
  143. package/.worktrees/2c37b1d7/src/run/multi-repo-context.js +208 -0
  144. package/.worktrees/2c37b1d7/src/run/next.js +128 -0
  145. package/.worktrees/2c37b1d7/src/run/prompt.js +843 -0
  146. package/.worktrees/2c37b1d7/src/run/quick-audit.js +186 -0
  147. package/.worktrees/2c37b1d7/src/run/scan-profile.js +269 -0
  148. package/.worktrees/2c37b1d7/src/run/shared.js +1562 -0
  149. package/.worktrees/2c37b1d7/src/run/stage.js +587 -0
  150. package/.worktrees/2c37b1d7/src/run.js +23 -0
  151. package/.worktrees/2c37b1d7/src/scan-diff.js +388 -0
  152. package/.worktrees/2c37b1d7/src/scan-postcheck.js +524 -0
  153. package/.worktrees/2c37b1d7/src/scan-staleness.js +95 -0
  154. package/.worktrees/2c37b1d7/src/setup.js +414 -0
  155. package/.worktrees/2c37b1d7/src/sillyhub-mcp/client.js +597 -0
  156. package/.worktrees/2c37b1d7/src/sillyhub-mcp/config.js +62 -0
  157. package/.worktrees/2c37b1d7/src/spec-dir-typo.js +50 -0
  158. package/.worktrees/2c37b1d7/src/spec-sync.js +358 -0
  159. package/.worktrees/2c37b1d7/src/stage-contract-engine.js +171 -0
  160. package/.worktrees/2c37b1d7/src/stage-contract-spec.js +551 -0
  161. package/.worktrees/2c37b1d7/src/stage-contract.js +948 -0
  162. package/.worktrees/2c37b1d7/src/stage-review.js +597 -0
  163. package/.worktrees/2c37b1d7/src/stages/archive.js +199 -0
  164. package/.worktrees/2c37b1d7/src/stages/brainstorm-auto.js +285 -0
  165. package/.worktrees/2c37b1d7/src/stages/brainstorm.js +624 -0
  166. package/.worktrees/2c37b1d7/src/stages/cmd-existence.js +145 -0
  167. package/.worktrees/2c37b1d7/src/stages/doctor.js +540 -0
  168. package/.worktrees/2c37b1d7/src/stages/execute.js +1277 -0
  169. package/.worktrees/2c37b1d7/src/stages/explore.js +43 -0
  170. package/.worktrees/2c37b1d7/src/stages/index.js +29 -0
  171. package/.worktrees/2c37b1d7/src/stages/knowledge.js +514 -0
  172. package/.worktrees/2c37b1d7/src/stages/plan-postcheck.js +1398 -0
  173. package/.worktrees/2c37b1d7/src/stages/plan.js +655 -0
  174. package/.worktrees/2c37b1d7/src/stages/quick.js +167 -0
  175. package/.worktrees/2c37b1d7/src/stages/scan.js +608 -0
  176. package/.worktrees/2c37b1d7/src/stages/status.js +64 -0
  177. package/.worktrees/2c37b1d7/src/stages/verify.js +246 -0
  178. package/.worktrees/2c37b1d7/src/sync.js +1470 -0
  179. package/.worktrees/2c37b1d7/src/task-review.js +1498 -0
  180. package/.worktrees/2c37b1d7/src/taskcard-placeholders.js +21 -0
  181. package/.worktrees/2c37b1d7/src/taskcard.js +250 -0
  182. package/.worktrees/2c37b1d7/src/verify-postcheck.js +1718 -0
  183. package/.worktrees/2c37b1d7/src/verify-probes.js +327 -0
  184. package/.worktrees/2c37b1d7/src/version.js +19 -0
  185. package/.worktrees/2c37b1d7/src/workflow.js +600 -0
  186. package/.worktrees/2c37b1d7/src/workspace.js +125 -0
  187. package/.worktrees/2c37b1d7/src/worktree-apply.js +1611 -0
  188. package/.worktrees/2c37b1d7/src/worktree-deps.js +668 -0
  189. package/.worktrees/2c37b1d7/src/worktree.js +1849 -0
  190. package/.worktrees/2c37b1d7/templates/claude-instruction.md +23 -0
  191. package/.worktrees/2c37b1d7/templates/prompts/taskcard-rules.md +29 -0
  192. package/.worktrees/2c37b1d7/templates/prompts/testcase-design.md +12 -0
  193. package/.worktrees/2c37b1d7/templates/prompts/verify-probes.md +32 -0
  194. package/.worktrees/2c37b1d7/templates/workflows/archive-impact.yaml +79 -0
  195. package/.worktrees/2c37b1d7/templates/workflows/scan-docs.yaml +132 -0
  196. package/.worktrees/542c4415/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  197. package/.worktrees/542c4415/.claude/skills/sillyspec-auto/SKILL.md +93 -0
  198. package/.worktrees/542c4415/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  199. package/.worktrees/542c4415/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  200. package/.worktrees/542c4415/.claude/skills/sillyspec-continue/SKILL.md +47 -0
  201. package/.worktrees/542c4415/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  202. package/.worktrees/542c4415/.claude/skills/sillyspec-execute/SKILL.md +214 -0
  203. package/.worktrees/542c4415/.claude/skills/sillyspec-explore/SKILL.md +102 -0
  204. package/.worktrees/542c4415/.claude/skills/sillyspec-export/SKILL.md +57 -0
  205. package/.worktrees/542c4415/.claude/skills/sillyspec-init/SKILL.md +177 -0
  206. package/.worktrees/542c4415/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  207. package/.worktrees/542c4415/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  208. package/.worktrees/542c4415/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  209. package/.worktrees/542c4415/.claude/skills/sillyspec-resume/SKILL.md +70 -0
  210. package/.worktrees/542c4415/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  211. package/.worktrees/542c4415/.claude/skills/sillyspec-state/SKILL.md +64 -0
  212. package/.worktrees/542c4415/.claude/skills/sillyspec-status/SKILL.md +54 -0
  213. package/.worktrees/542c4415/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  214. package/.worktrees/542c4415/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
  215. package/.worktrees/542c4415/.gitattributes +2 -0
  216. package/.worktrees/542c4415/.husky/pre-push +20 -0
  217. package/.worktrees/542c4415/README.md +203 -0
  218. package/.worktrees/542c4415/SKILL.md +91 -0
  219. package/.worktrees/542c4415/architecture-analysis.md +448 -0
  220. package/.worktrees/542c4415/bin/sillyspec.js +2 -0
  221. package/.worktrees/542c4415/eval/README.md +86 -0
  222. package/.worktrees/542c4415/eval/eval.config.json +28 -0
  223. package/.worktrees/542c4415/eval/lib/adapters/cli.mjs +63 -0
  224. package/.worktrees/542c4415/eval/lib/adapters/dryrun.mjs +27 -0
  225. package/.worktrees/542c4415/eval/lib/config.mjs +82 -0
  226. package/.worktrees/542c4415/eval/lib/db.mjs +76 -0
  227. package/.worktrees/542c4415/eval/lib/report.mjs +120 -0
  228. package/.worktrees/542c4415/eval/lib/runner.mjs +157 -0
  229. package/.worktrees/542c4415/eval/lib/tasks.mjs +73 -0
  230. package/.worktrees/542c4415/eval/lib/util.mjs +107 -0
  231. package/.worktrees/542c4415/eval/lib/workdir.mjs +30 -0
  232. package/.worktrees/542c4415/eval/run.mjs +83 -0
  233. package/.worktrees/542c4415/eval/tasks/001-slugify/files/package.json +6 -0
  234. package/.worktrees/542c4415/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
  235. package/.worktrees/542c4415/eval/tasks/001-slugify/task.json +6 -0
  236. package/.worktrees/542c4415/eval/tasks/001-slugify/verify.mjs +28 -0
  237. package/.worktrees/542c4415/eval/tasks/002-each-day/files/package.json +6 -0
  238. package/.worktrees/542c4415/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
  239. package/.worktrees/542c4415/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
  240. package/.worktrees/542c4415/eval/tasks/002-each-day/task.json +6 -0
  241. package/.worktrees/542c4415/eval/tasks/002-each-day/verify.mjs +31 -0
  242. package/.worktrees/542c4415/eval/tasks/003-word-report/files/data/notes.txt +5 -0
  243. package/.worktrees/542c4415/eval/tasks/003-word-report/files/package.json +6 -0
  244. package/.worktrees/542c4415/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
  245. package/.worktrees/542c4415/eval/tasks/003-word-report/task.json +6 -0
  246. package/.worktrees/542c4415/eval/tasks/003-word-report/verify.mjs +38 -0
  247. package/.worktrees/542c4415/logo.jpg +0 -0
  248. package/.worktrees/542c4415/package-lock.json +977 -0
  249. package/.worktrees/542c4415/package.json +41 -0
  250. package/.worktrees/542c4415/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
  251. package/.worktrees/542c4415/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  252. package/.worktrees/542c4415/packages/dashboard/dist/favicon.jpg +0 -0
  253. package/.worktrees/542c4415/packages/dashboard/dist/index.html +16 -0
  254. package/.worktrees/542c4415/packages/dashboard/dist/logo.jpg +0 -0
  255. package/.worktrees/542c4415/packages/dashboard/dist/prototype-dashboard.html +836 -0
  256. package/.worktrees/542c4415/packages/dashboard/dist/prototype-overview.html +256 -0
  257. package/.worktrees/542c4415/packages/dashboard/package.json +25 -0
  258. package/.worktrees/542c4415/packages/dashboard/server/executor.js +95 -0
  259. package/.worktrees/542c4415/packages/dashboard/server/index.js +679 -0
  260. package/.worktrees/542c4415/packages/dashboard/server/parser.js +526 -0
  261. package/.worktrees/542c4415/packages/dashboard/server/watcher.js +357 -0
  262. package/.worktrees/542c4415/src/agent-session-log.js +803 -0
  263. package/.worktrees/542c4415/src/change-list.js +290 -0
  264. package/.worktrees/542c4415/src/change-risk-profile.js +339 -0
  265. package/.worktrees/542c4415/src/check-primitives.js +65 -0
  266. package/.worktrees/542c4415/src/classify-change.js +109 -0
  267. package/.worktrees/542c4415/src/commit-suggest.js +202 -0
  268. package/.worktrees/542c4415/src/config-cat.js +88 -0
  269. package/.worktrees/542c4415/src/config-schema.js +328 -0
  270. package/.worktrees/542c4415/src/constants.js +97 -0
  271. package/.worktrees/542c4415/src/contract-matrix.js +533 -0
  272. package/.worktrees/542c4415/src/datetime.js +20 -0
  273. package/.worktrees/542c4415/src/db-engine.js +55 -0
  274. package/.worktrees/542c4415/src/db.js +381 -0
  275. package/.worktrees/542c4415/src/decision-distill.js +517 -0
  276. package/.worktrees/542c4415/src/dispatch/backends/local-agent.js +155 -0
  277. package/.worktrees/542c4415/src/dispatch/backends/sillyhub-mcp.js +245 -0
  278. package/.worktrees/542c4415/src/dispatch/probe.js +246 -0
  279. package/.worktrees/542c4415/src/dispatch/strategy.js +121 -0
  280. package/.worktrees/542c4415/src/docs-check.js +825 -0
  281. package/.worktrees/542c4415/src/docs-debt.js +393 -0
  282. package/.worktrees/542c4415/src/docs-gate.js +107 -0
  283. package/.worktrees/542c4415/src/doctor-diagnostics.js +1099 -0
  284. package/.worktrees/542c4415/src/endpoint-extractor.js +409 -0
  285. package/.worktrees/542c4415/src/foreign-declared.js +155 -0
  286. package/.worktrees/542c4415/src/fs-atomic.js +73 -0
  287. package/.worktrees/542c4415/src/git-helper.js +150 -0
  288. package/.worktrees/542c4415/src/hooks/claude-pre-tool-use.cjs +130 -0
  289. package/.worktrees/542c4415/src/hooks/worktree-guard.js +892 -0
  290. package/.worktrees/542c4415/src/index.js +2854 -0
  291. package/.worktrees/542c4415/src/init.js +626 -0
  292. package/.worktrees/542c4415/src/knowledge-match.js +231 -0
  293. package/.worktrees/542c4415/src/local-detect.js +128 -0
  294. package/.worktrees/542c4415/src/local-register.js +104 -0
  295. package/.worktrees/542c4415/src/machine-interface.js +543 -0
  296. package/.worktrees/542c4415/src/migrate.js +127 -0
  297. package/.worktrees/542c4415/src/module-impact.js +166 -0
  298. package/.worktrees/542c4415/src/module-resolve.js +222 -0
  299. package/.worktrees/542c4415/src/modules.js +580 -0
  300. package/.worktrees/542c4415/src/plan-adopt-waves.js +126 -0
  301. package/.worktrees/542c4415/src/progress/change-registry.js +336 -0
  302. package/.worktrees/542c4415/src/progress/consistency-doctor.js +355 -0
  303. package/.worktrees/542c4415/src/progress/shared.js +36 -0
  304. package/.worktrees/542c4415/src/progress/stage-machine.js +594 -0
  305. package/.worktrees/542c4415/src/progress/step-store.js +265 -0
  306. package/.worktrees/542c4415/src/progress.js +1304 -0
  307. package/.worktrees/542c4415/src/quick-recommend.js +115 -0
  308. package/.worktrees/542c4415/src/quicklog.js +883 -0
  309. package/.worktrees/542c4415/src/review-tier.js +77 -0
  310. package/.worktrees/542c4415/src/run/command.js +1641 -0
  311. package/.worktrees/542c4415/src/run/complete-handlers.js +1552 -0
  312. package/.worktrees/542c4415/src/run/complete.js +1259 -0
  313. package/.worktrees/542c4415/src/run/concurrent-detect.js +156 -0
  314. package/.worktrees/542c4415/src/run/gates.js +1111 -0
  315. package/.worktrees/542c4415/src/run/multi-repo-context.js +208 -0
  316. package/.worktrees/542c4415/src/run/next.js +128 -0
  317. package/.worktrees/542c4415/src/run/prompt.js +843 -0
  318. package/.worktrees/542c4415/src/run/quick-audit.js +186 -0
  319. package/.worktrees/542c4415/src/run/scan-profile.js +269 -0
  320. package/.worktrees/542c4415/src/run/shared.js +1562 -0
  321. package/.worktrees/542c4415/src/run/stage.js +587 -0
  322. package/.worktrees/542c4415/src/run.js +23 -0
  323. package/.worktrees/542c4415/src/scan-diff.js +388 -0
  324. package/.worktrees/542c4415/src/scan-postcheck.js +524 -0
  325. package/.worktrees/542c4415/src/scan-staleness.js +95 -0
  326. package/.worktrees/542c4415/src/setup.js +414 -0
  327. package/.worktrees/542c4415/src/sillyhub-mcp/client.js +597 -0
  328. package/.worktrees/542c4415/src/sillyhub-mcp/config.js +62 -0
  329. package/.worktrees/542c4415/src/spec-dir-typo.js +50 -0
  330. package/.worktrees/542c4415/src/spec-sync.js +358 -0
  331. package/.worktrees/542c4415/src/stage-contract-engine.js +171 -0
  332. package/.worktrees/542c4415/src/stage-contract-spec.js +551 -0
  333. package/.worktrees/542c4415/src/stage-contract.js +948 -0
  334. package/.worktrees/542c4415/src/stage-review.js +597 -0
  335. package/.worktrees/542c4415/src/stages/archive.js +199 -0
  336. package/.worktrees/542c4415/src/stages/brainstorm-auto.js +285 -0
  337. package/.worktrees/542c4415/src/stages/brainstorm.js +624 -0
  338. package/.worktrees/542c4415/src/stages/cmd-existence.js +145 -0
  339. package/.worktrees/542c4415/src/stages/doctor.js +540 -0
  340. package/.worktrees/542c4415/src/stages/execute.js +1277 -0
  341. package/.worktrees/542c4415/src/stages/explore.js +43 -0
  342. package/.worktrees/542c4415/src/stages/index.js +29 -0
  343. package/.worktrees/542c4415/src/stages/knowledge.js +514 -0
  344. package/.worktrees/542c4415/src/stages/plan-postcheck.js +1398 -0
  345. package/.worktrees/542c4415/src/stages/plan.js +655 -0
  346. package/.worktrees/542c4415/src/stages/quick.js +167 -0
  347. package/.worktrees/542c4415/src/stages/scan.js +608 -0
  348. package/.worktrees/542c4415/src/stages/status.js +64 -0
  349. package/.worktrees/542c4415/src/stages/verify.js +246 -0
  350. package/.worktrees/542c4415/src/sync.js +1470 -0
  351. package/.worktrees/542c4415/src/task-review.js +1498 -0
  352. package/.worktrees/542c4415/src/taskcard-placeholders.js +21 -0
  353. package/.worktrees/542c4415/src/taskcard.js +250 -0
  354. package/.worktrees/542c4415/src/verify-postcheck.js +1718 -0
  355. package/.worktrees/542c4415/src/verify-probes.js +327 -0
  356. package/.worktrees/542c4415/src/version.js +19 -0
  357. package/.worktrees/542c4415/src/workflow.js +600 -0
  358. package/.worktrees/542c4415/src/workspace.js +125 -0
  359. package/.worktrees/542c4415/src/worktree-apply.js +1611 -0
  360. package/.worktrees/542c4415/src/worktree-deps.js +668 -0
  361. package/.worktrees/542c4415/src/worktree.js +1849 -0
  362. package/.worktrees/542c4415/templates/claude-instruction.md +23 -0
  363. package/.worktrees/542c4415/templates/prompts/taskcard-rules.md +29 -0
  364. package/.worktrees/542c4415/templates/prompts/testcase-design.md +12 -0
  365. package/.worktrees/542c4415/templates/prompts/verify-probes.md +32 -0
  366. package/.worktrees/542c4415/templates/workflows/archive-impact.yaml +79 -0
  367. package/.worktrees/542c4415/templates/workflows/scan-docs.yaml +132 -0
  368. package/.worktrees/c1eae35e/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  369. package/.worktrees/c1eae35e/.claude/skills/sillyspec-auto/SKILL.md +93 -0
  370. package/.worktrees/c1eae35e/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  371. package/.worktrees/c1eae35e/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  372. package/.worktrees/c1eae35e/.claude/skills/sillyspec-continue/SKILL.md +47 -0
  373. package/.worktrees/c1eae35e/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  374. package/.worktrees/c1eae35e/.claude/skills/sillyspec-execute/SKILL.md +214 -0
  375. package/.worktrees/c1eae35e/.claude/skills/sillyspec-explore/SKILL.md +102 -0
  376. package/.worktrees/c1eae35e/.claude/skills/sillyspec-export/SKILL.md +57 -0
  377. package/.worktrees/c1eae35e/.claude/skills/sillyspec-init/SKILL.md +177 -0
  378. package/.worktrees/c1eae35e/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  379. package/.worktrees/c1eae35e/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  380. package/.worktrees/c1eae35e/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  381. package/.worktrees/c1eae35e/.claude/skills/sillyspec-resume/SKILL.md +70 -0
  382. package/.worktrees/c1eae35e/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  383. package/.worktrees/c1eae35e/.claude/skills/sillyspec-state/SKILL.md +64 -0
  384. package/.worktrees/c1eae35e/.claude/skills/sillyspec-status/SKILL.md +54 -0
  385. package/.worktrees/c1eae35e/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  386. package/.worktrees/c1eae35e/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
  387. package/.worktrees/c1eae35e/.gitattributes +2 -0
  388. package/.worktrees/c1eae35e/.husky/pre-push +20 -0
  389. package/.worktrees/c1eae35e/README.md +203 -0
  390. package/.worktrees/c1eae35e/SKILL.md +91 -0
  391. package/.worktrees/c1eae35e/architecture-analysis.md +448 -0
  392. package/.worktrees/c1eae35e/bin/sillyspec.js +2 -0
  393. package/.worktrees/c1eae35e/eval/README.md +86 -0
  394. package/.worktrees/c1eae35e/eval/eval.config.json +28 -0
  395. package/.worktrees/c1eae35e/eval/lib/adapters/cli.mjs +63 -0
  396. package/.worktrees/c1eae35e/eval/lib/adapters/dryrun.mjs +27 -0
  397. package/.worktrees/c1eae35e/eval/lib/config.mjs +82 -0
  398. package/.worktrees/c1eae35e/eval/lib/db.mjs +76 -0
  399. package/.worktrees/c1eae35e/eval/lib/report.mjs +120 -0
  400. package/.worktrees/c1eae35e/eval/lib/runner.mjs +157 -0
  401. package/.worktrees/c1eae35e/eval/lib/tasks.mjs +73 -0
  402. package/.worktrees/c1eae35e/eval/lib/util.mjs +107 -0
  403. package/.worktrees/c1eae35e/eval/lib/workdir.mjs +30 -0
  404. package/.worktrees/c1eae35e/eval/run.mjs +83 -0
  405. package/.worktrees/c1eae35e/eval/tasks/001-slugify/files/package.json +6 -0
  406. package/.worktrees/c1eae35e/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
  407. package/.worktrees/c1eae35e/eval/tasks/001-slugify/task.json +6 -0
  408. package/.worktrees/c1eae35e/eval/tasks/001-slugify/verify.mjs +28 -0
  409. package/.worktrees/c1eae35e/eval/tasks/002-each-day/files/package.json +6 -0
  410. package/.worktrees/c1eae35e/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
  411. package/.worktrees/c1eae35e/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
  412. package/.worktrees/c1eae35e/eval/tasks/002-each-day/task.json +6 -0
  413. package/.worktrees/c1eae35e/eval/tasks/002-each-day/verify.mjs +31 -0
  414. package/.worktrees/c1eae35e/eval/tasks/003-word-report/files/data/notes.txt +5 -0
  415. package/.worktrees/c1eae35e/eval/tasks/003-word-report/files/package.json +6 -0
  416. package/.worktrees/c1eae35e/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
  417. package/.worktrees/c1eae35e/eval/tasks/003-word-report/task.json +6 -0
  418. package/.worktrees/c1eae35e/eval/tasks/003-word-report/verify.mjs +38 -0
  419. package/.worktrees/c1eae35e/logo.jpg +0 -0
  420. package/.worktrees/c1eae35e/package-lock.json +977 -0
  421. package/.worktrees/c1eae35e/package.json +41 -0
  422. package/.worktrees/c1eae35e/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
  423. package/.worktrees/c1eae35e/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  424. package/.worktrees/c1eae35e/packages/dashboard/dist/favicon.jpg +0 -0
  425. package/.worktrees/c1eae35e/packages/dashboard/dist/index.html +16 -0
  426. package/.worktrees/c1eae35e/packages/dashboard/dist/logo.jpg +0 -0
  427. package/.worktrees/c1eae35e/packages/dashboard/dist/prototype-dashboard.html +836 -0
  428. package/.worktrees/c1eae35e/packages/dashboard/dist/prototype-overview.html +256 -0
  429. package/.worktrees/c1eae35e/packages/dashboard/package.json +25 -0
  430. package/.worktrees/c1eae35e/packages/dashboard/server/executor.js +95 -0
  431. package/.worktrees/c1eae35e/packages/dashboard/server/index.js +679 -0
  432. package/.worktrees/c1eae35e/packages/dashboard/server/parser.js +526 -0
  433. package/.worktrees/c1eae35e/packages/dashboard/server/watcher.js +357 -0
  434. package/.worktrees/c1eae35e/src/agent-session-log.js +803 -0
  435. package/.worktrees/c1eae35e/src/change-list.js +290 -0
  436. package/.worktrees/c1eae35e/src/change-risk-profile.js +339 -0
  437. package/.worktrees/c1eae35e/src/check-primitives.js +65 -0
  438. package/.worktrees/c1eae35e/src/classify-change.js +109 -0
  439. package/.worktrees/c1eae35e/src/commit-suggest.js +202 -0
  440. package/.worktrees/c1eae35e/src/config-cat.js +88 -0
  441. package/.worktrees/c1eae35e/src/config-schema.js +328 -0
  442. package/.worktrees/c1eae35e/src/constants.js +97 -0
  443. package/.worktrees/c1eae35e/src/contract-matrix.js +533 -0
  444. package/.worktrees/c1eae35e/src/datetime.js +20 -0
  445. package/.worktrees/c1eae35e/src/db-engine.js +55 -0
  446. package/.worktrees/c1eae35e/src/db.js +381 -0
  447. package/.worktrees/c1eae35e/src/decision-distill.js +517 -0
  448. package/.worktrees/c1eae35e/src/dispatch/backends/local-agent.js +155 -0
  449. package/.worktrees/c1eae35e/src/dispatch/backends/sillyhub-mcp.js +245 -0
  450. package/.worktrees/c1eae35e/src/dispatch/probe.js +246 -0
  451. package/.worktrees/c1eae35e/src/dispatch/strategy.js +121 -0
  452. package/.worktrees/c1eae35e/src/docs-check.js +825 -0
  453. package/.worktrees/c1eae35e/src/docs-debt.js +393 -0
  454. package/.worktrees/c1eae35e/src/docs-gate.js +107 -0
  455. package/.worktrees/c1eae35e/src/doctor-diagnostics.js +1099 -0
  456. package/.worktrees/c1eae35e/src/endpoint-extractor.js +409 -0
  457. package/.worktrees/c1eae35e/src/foreign-declared.js +155 -0
  458. package/.worktrees/c1eae35e/src/fs-atomic.js +73 -0
  459. package/.worktrees/c1eae35e/src/git-helper.js +150 -0
  460. package/.worktrees/c1eae35e/src/hooks/claude-pre-tool-use.cjs +130 -0
  461. package/.worktrees/c1eae35e/src/hooks/worktree-guard.js +892 -0
  462. package/.worktrees/c1eae35e/src/index.js +2854 -0
  463. package/.worktrees/c1eae35e/src/init.js +626 -0
  464. package/.worktrees/c1eae35e/src/knowledge-match.js +231 -0
  465. package/.worktrees/c1eae35e/src/local-detect.js +128 -0
  466. package/.worktrees/c1eae35e/src/local-register.js +104 -0
  467. package/.worktrees/c1eae35e/src/machine-interface.js +543 -0
  468. package/.worktrees/c1eae35e/src/migrate.js +127 -0
  469. package/.worktrees/c1eae35e/src/module-impact.js +166 -0
  470. package/.worktrees/c1eae35e/src/module-resolve.js +222 -0
  471. package/.worktrees/c1eae35e/src/modules.js +580 -0
  472. package/.worktrees/c1eae35e/src/plan-adopt-waves.js +126 -0
  473. package/.worktrees/c1eae35e/src/progress/change-registry.js +336 -0
  474. package/.worktrees/c1eae35e/src/progress/consistency-doctor.js +355 -0
  475. package/.worktrees/c1eae35e/src/progress/shared.js +36 -0
  476. package/.worktrees/c1eae35e/src/progress/stage-machine.js +594 -0
  477. package/.worktrees/c1eae35e/src/progress/step-store.js +265 -0
  478. package/.worktrees/c1eae35e/src/progress.js +1304 -0
  479. package/.worktrees/c1eae35e/src/quick-recommend.js +115 -0
  480. package/.worktrees/c1eae35e/src/quicklog.js +883 -0
  481. package/.worktrees/c1eae35e/src/review-tier.js +77 -0
  482. package/.worktrees/c1eae35e/src/run/command.js +1641 -0
  483. package/.worktrees/c1eae35e/src/run/complete-handlers.js +1552 -0
  484. package/.worktrees/c1eae35e/src/run/complete.js +1259 -0
  485. package/.worktrees/c1eae35e/src/run/concurrent-detect.js +156 -0
  486. package/.worktrees/c1eae35e/src/run/gates.js +1111 -0
  487. package/.worktrees/c1eae35e/src/run/multi-repo-context.js +208 -0
  488. package/.worktrees/c1eae35e/src/run/next.js +128 -0
  489. package/.worktrees/c1eae35e/src/run/prompt.js +843 -0
  490. package/.worktrees/c1eae35e/src/run/quick-audit.js +186 -0
  491. package/.worktrees/c1eae35e/src/run/scan-profile.js +269 -0
  492. package/.worktrees/c1eae35e/src/run/shared.js +1562 -0
  493. package/.worktrees/c1eae35e/src/run/stage.js +587 -0
  494. package/.worktrees/c1eae35e/src/run.js +23 -0
  495. package/.worktrees/c1eae35e/src/scan-diff.js +388 -0
  496. package/.worktrees/c1eae35e/src/scan-postcheck.js +524 -0
  497. package/.worktrees/c1eae35e/src/scan-staleness.js +95 -0
  498. package/.worktrees/c1eae35e/src/setup.js +414 -0
  499. package/.worktrees/c1eae35e/src/sillyhub-mcp/client.js +597 -0
  500. package/.worktrees/c1eae35e/src/sillyhub-mcp/config.js +62 -0
  501. package/.worktrees/c1eae35e/src/spec-dir-typo.js +50 -0
  502. package/.worktrees/c1eae35e/src/spec-sync.js +358 -0
  503. package/.worktrees/c1eae35e/src/stage-contract-engine.js +171 -0
  504. package/.worktrees/c1eae35e/src/stage-contract-spec.js +551 -0
  505. package/.worktrees/c1eae35e/src/stage-contract.js +948 -0
  506. package/.worktrees/c1eae35e/src/stage-review.js +597 -0
  507. package/.worktrees/c1eae35e/src/stages/archive.js +199 -0
  508. package/.worktrees/c1eae35e/src/stages/brainstorm-auto.js +285 -0
  509. package/.worktrees/c1eae35e/src/stages/brainstorm.js +624 -0
  510. package/.worktrees/c1eae35e/src/stages/cmd-existence.js +145 -0
  511. package/.worktrees/c1eae35e/src/stages/doctor.js +540 -0
  512. package/.worktrees/c1eae35e/src/stages/execute.js +1277 -0
  513. package/.worktrees/c1eae35e/src/stages/explore.js +43 -0
  514. package/.worktrees/c1eae35e/src/stages/index.js +29 -0
  515. package/.worktrees/c1eae35e/src/stages/knowledge.js +514 -0
  516. package/.worktrees/c1eae35e/src/stages/plan-postcheck.js +1398 -0
  517. package/.worktrees/c1eae35e/src/stages/plan.js +655 -0
  518. package/.worktrees/c1eae35e/src/stages/quick.js +167 -0
  519. package/.worktrees/c1eae35e/src/stages/scan.js +608 -0
  520. package/.worktrees/c1eae35e/src/stages/status.js +64 -0
  521. package/.worktrees/c1eae35e/src/stages/verify.js +246 -0
  522. package/.worktrees/c1eae35e/src/sync.js +1470 -0
  523. package/.worktrees/c1eae35e/src/task-review.js +1498 -0
  524. package/.worktrees/c1eae35e/src/taskcard-placeholders.js +21 -0
  525. package/.worktrees/c1eae35e/src/taskcard.js +250 -0
  526. package/.worktrees/c1eae35e/src/verify-postcheck.js +1718 -0
  527. package/.worktrees/c1eae35e/src/verify-probes.js +327 -0
  528. package/.worktrees/c1eae35e/src/version.js +19 -0
  529. package/.worktrees/c1eae35e/src/workflow.js +600 -0
  530. package/.worktrees/c1eae35e/src/workspace.js +125 -0
  531. package/.worktrees/c1eae35e/src/worktree-apply.js +1611 -0
  532. package/.worktrees/c1eae35e/src/worktree-deps.js +668 -0
  533. package/.worktrees/c1eae35e/src/worktree.js +1849 -0
  534. package/.worktrees/c1eae35e/templates/claude-instruction.md +23 -0
  535. package/.worktrees/c1eae35e/templates/prompts/taskcard-rules.md +29 -0
  536. package/.worktrees/c1eae35e/templates/prompts/testcase-design.md +12 -0
  537. package/.worktrees/c1eae35e/templates/prompts/verify-probes.md +32 -0
  538. package/.worktrees/c1eae35e/templates/workflows/archive-impact.yaml +79 -0
  539. package/.worktrees/c1eae35e/templates/workflows/scan-docs.yaml +132 -0
  540. package/.worktrees/dafc71ad/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  541. package/.worktrees/dafc71ad/.claude/skills/sillyspec-auto/SKILL.md +93 -0
  542. package/.worktrees/dafc71ad/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  543. package/.worktrees/dafc71ad/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  544. package/.worktrees/dafc71ad/.claude/skills/sillyspec-continue/SKILL.md +47 -0
  545. package/.worktrees/dafc71ad/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  546. package/.worktrees/dafc71ad/.claude/skills/sillyspec-execute/SKILL.md +214 -0
  547. package/.worktrees/dafc71ad/.claude/skills/sillyspec-explore/SKILL.md +102 -0
  548. package/.worktrees/dafc71ad/.claude/skills/sillyspec-export/SKILL.md +57 -0
  549. package/.worktrees/dafc71ad/.claude/skills/sillyspec-init/SKILL.md +177 -0
  550. package/.worktrees/dafc71ad/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  551. package/.worktrees/dafc71ad/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  552. package/.worktrees/dafc71ad/.claude/skills/sillyspec-propose/SKILL.md +87 -0
  553. package/.worktrees/dafc71ad/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  554. package/.worktrees/dafc71ad/.claude/skills/sillyspec-resume/SKILL.md +70 -0
  555. package/.worktrees/dafc71ad/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  556. package/.worktrees/dafc71ad/.claude/skills/sillyspec-state/SKILL.md +64 -0
  557. package/.worktrees/dafc71ad/.claude/skills/sillyspec-status/SKILL.md +54 -0
  558. package/.worktrees/dafc71ad/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  559. package/.worktrees/dafc71ad/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
  560. package/.worktrees/dafc71ad/.gitattributes +2 -0
  561. package/.worktrees/dafc71ad/.husky/pre-push +20 -0
  562. package/.worktrees/dafc71ad/README.md +203 -0
  563. package/.worktrees/dafc71ad/SKILL.md +91 -0
  564. package/.worktrees/dafc71ad/analysis.md +366 -0
  565. package/.worktrees/dafc71ad/architecture-analysis.md +448 -0
  566. package/.worktrees/dafc71ad/bin/sillyspec.js +2 -0
  567. package/.worktrees/dafc71ad/eval/README.md +86 -0
  568. package/.worktrees/dafc71ad/eval/eval.config.json +28 -0
  569. package/.worktrees/dafc71ad/eval/lib/adapters/cli.mjs +63 -0
  570. package/.worktrees/dafc71ad/eval/lib/adapters/dryrun.mjs +27 -0
  571. package/.worktrees/dafc71ad/eval/lib/config.mjs +82 -0
  572. package/.worktrees/dafc71ad/eval/lib/db.mjs +76 -0
  573. package/.worktrees/dafc71ad/eval/lib/report.mjs +120 -0
  574. package/.worktrees/dafc71ad/eval/lib/runner.mjs +157 -0
  575. package/.worktrees/dafc71ad/eval/lib/tasks.mjs +73 -0
  576. package/.worktrees/dafc71ad/eval/lib/util.mjs +107 -0
  577. package/.worktrees/dafc71ad/eval/lib/workdir.mjs +30 -0
  578. package/.worktrees/dafc71ad/eval/run.mjs +83 -0
  579. package/.worktrees/dafc71ad/eval/tasks/001-slugify/files/package.json +6 -0
  580. package/.worktrees/dafc71ad/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
  581. package/.worktrees/dafc71ad/eval/tasks/001-slugify/task.json +6 -0
  582. package/.worktrees/dafc71ad/eval/tasks/001-slugify/verify.mjs +28 -0
  583. package/.worktrees/dafc71ad/eval/tasks/002-each-day/files/package.json +6 -0
  584. package/.worktrees/dafc71ad/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
  585. package/.worktrees/dafc71ad/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
  586. package/.worktrees/dafc71ad/eval/tasks/002-each-day/task.json +6 -0
  587. package/.worktrees/dafc71ad/eval/tasks/002-each-day/verify.mjs +31 -0
  588. package/.worktrees/dafc71ad/eval/tasks/003-word-report/files/data/notes.txt +5 -0
  589. package/.worktrees/dafc71ad/eval/tasks/003-word-report/files/package.json +6 -0
  590. package/.worktrees/dafc71ad/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
  591. package/.worktrees/dafc71ad/eval/tasks/003-word-report/task.json +6 -0
  592. package/.worktrees/dafc71ad/eval/tasks/003-word-report/verify.mjs +38 -0
  593. package/.worktrees/dafc71ad/logo.jpg +0 -0
  594. package/.worktrees/dafc71ad/package-lock.json +977 -0
  595. package/.worktrees/dafc71ad/package.json +41 -0
  596. package/.worktrees/dafc71ad/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
  597. package/.worktrees/dafc71ad/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  598. package/.worktrees/dafc71ad/packages/dashboard/dist/favicon.jpg +0 -0
  599. package/.worktrees/dafc71ad/packages/dashboard/dist/index.html +16 -0
  600. package/.worktrees/dafc71ad/packages/dashboard/dist/logo.jpg +0 -0
  601. package/.worktrees/dafc71ad/packages/dashboard/dist/prototype-dashboard.html +836 -0
  602. package/.worktrees/dafc71ad/packages/dashboard/dist/prototype-overview.html +256 -0
  603. package/.worktrees/dafc71ad/packages/dashboard/package.json +25 -0
  604. package/.worktrees/dafc71ad/packages/dashboard/server/executor.js +95 -0
  605. package/.worktrees/dafc71ad/packages/dashboard/server/index.js +679 -0
  606. package/.worktrees/dafc71ad/packages/dashboard/server/parser.js +526 -0
  607. package/.worktrees/dafc71ad/packages/dashboard/server/watcher.js +357 -0
  608. package/.worktrees/dafc71ad/src/agent-session-log.js +803 -0
  609. package/.worktrees/dafc71ad/src/change-list.js +290 -0
  610. package/.worktrees/dafc71ad/src/change-risk-profile.js +339 -0
  611. package/.worktrees/dafc71ad/src/check-primitives.js +65 -0
  612. package/.worktrees/dafc71ad/src/classify-change.js +109 -0
  613. package/.worktrees/dafc71ad/src/commit-suggest.js +202 -0
  614. package/.worktrees/dafc71ad/src/config-cat.js +88 -0
  615. package/.worktrees/dafc71ad/src/config-schema.js +328 -0
  616. package/.worktrees/dafc71ad/src/constants.js +97 -0
  617. package/.worktrees/dafc71ad/src/contract-matrix.js +533 -0
  618. package/.worktrees/dafc71ad/src/datetime.js +20 -0
  619. package/.worktrees/dafc71ad/src/db-engine.js +55 -0
  620. package/.worktrees/dafc71ad/src/db.js +381 -0
  621. package/.worktrees/dafc71ad/src/decision-distill.js +517 -0
  622. package/.worktrees/dafc71ad/src/dispatch/backends/local-agent.js +155 -0
  623. package/.worktrees/dafc71ad/src/dispatch/backends/sillyhub-mcp.js +245 -0
  624. package/.worktrees/dafc71ad/src/dispatch/probe.js +246 -0
  625. package/.worktrees/dafc71ad/src/dispatch/strategy.js +121 -0
  626. package/.worktrees/dafc71ad/src/docs-check.js +825 -0
  627. package/.worktrees/dafc71ad/src/docs-debt.js +393 -0
  628. package/.worktrees/dafc71ad/src/docs-gate.js +107 -0
  629. package/.worktrees/dafc71ad/src/doctor-diagnostics.js +1099 -0
  630. package/.worktrees/dafc71ad/src/endpoint-extractor.js +409 -0
  631. package/.worktrees/dafc71ad/src/foreign-declared.js +155 -0
  632. package/.worktrees/dafc71ad/src/fs-atomic.js +73 -0
  633. package/.worktrees/dafc71ad/src/git-helper.js +150 -0
  634. package/.worktrees/dafc71ad/src/hooks/claude-pre-tool-use.cjs +130 -0
  635. package/.worktrees/dafc71ad/src/hooks/worktree-guard.js +892 -0
  636. package/.worktrees/dafc71ad/src/index.js +2854 -0
  637. package/.worktrees/dafc71ad/src/init.js +626 -0
  638. package/.worktrees/dafc71ad/src/knowledge-match.js +231 -0
  639. package/.worktrees/dafc71ad/src/local-detect.js +128 -0
  640. package/.worktrees/dafc71ad/src/local-register.js +104 -0
  641. package/.worktrees/dafc71ad/src/machine-interface.js +543 -0
  642. package/.worktrees/dafc71ad/src/migrate.js +127 -0
  643. package/.worktrees/dafc71ad/src/module-impact.js +166 -0
  644. package/.worktrees/dafc71ad/src/module-resolve.js +222 -0
  645. package/.worktrees/dafc71ad/src/modules.js +580 -0
  646. package/.worktrees/dafc71ad/src/plan-adopt-waves.js +126 -0
  647. package/.worktrees/dafc71ad/src/progress/change-registry.js +336 -0
  648. package/.worktrees/dafc71ad/src/progress/consistency-doctor.js +355 -0
  649. package/.worktrees/dafc71ad/src/progress/shared.js +36 -0
  650. package/.worktrees/dafc71ad/src/progress/stage-machine.js +594 -0
  651. package/.worktrees/dafc71ad/src/progress/step-store.js +265 -0
  652. package/.worktrees/dafc71ad/src/progress.js +1304 -0
  653. package/.worktrees/dafc71ad/src/quick-recommend.js +115 -0
  654. package/.worktrees/dafc71ad/src/quicklog.js +883 -0
  655. package/.worktrees/dafc71ad/src/review-tier.js +77 -0
  656. package/.worktrees/dafc71ad/src/run/command.js +1641 -0
  657. package/.worktrees/dafc71ad/src/run/complete-handlers.js +1552 -0
  658. package/.worktrees/dafc71ad/src/run/complete.js +1259 -0
  659. package/.worktrees/dafc71ad/src/run/concurrent-detect.js +156 -0
  660. package/.worktrees/dafc71ad/src/run/gates.js +1111 -0
  661. package/.worktrees/dafc71ad/src/run/multi-repo-context.js +208 -0
  662. package/.worktrees/dafc71ad/src/run/next.js +128 -0
  663. package/.worktrees/dafc71ad/src/run/prompt.js +843 -0
  664. package/.worktrees/dafc71ad/src/run/quick-audit.js +186 -0
  665. package/.worktrees/dafc71ad/src/run/scan-profile.js +269 -0
  666. package/.worktrees/dafc71ad/src/run/shared.js +1562 -0
  667. package/.worktrees/dafc71ad/src/run/stage.js +587 -0
  668. package/.worktrees/dafc71ad/src/run.js +23 -0
  669. package/.worktrees/dafc71ad/src/scan-diff.js +388 -0
  670. package/.worktrees/dafc71ad/src/scan-postcheck.js +524 -0
  671. package/.worktrees/dafc71ad/src/scan-staleness.js +95 -0
  672. package/.worktrees/dafc71ad/src/setup.js +414 -0
  673. package/.worktrees/dafc71ad/src/sillyhub-mcp/client.js +597 -0
  674. package/.worktrees/dafc71ad/src/sillyhub-mcp/config.js +62 -0
  675. package/.worktrees/dafc71ad/src/spec-dir-typo.js +50 -0
  676. package/.worktrees/dafc71ad/src/spec-sync.js +358 -0
  677. package/.worktrees/dafc71ad/src/stage-contract-engine.js +171 -0
  678. package/.worktrees/dafc71ad/src/stage-contract-spec.js +551 -0
  679. package/.worktrees/dafc71ad/src/stage-contract.js +948 -0
  680. package/.worktrees/dafc71ad/src/stage-review.js +597 -0
  681. package/.worktrees/dafc71ad/src/stages/archive.js +199 -0
  682. package/.worktrees/dafc71ad/src/stages/brainstorm-auto.js +285 -0
  683. package/.worktrees/dafc71ad/src/stages/brainstorm.js +624 -0
  684. package/.worktrees/dafc71ad/src/stages/cmd-existence.js +145 -0
  685. package/.worktrees/dafc71ad/src/stages/doctor.js +540 -0
  686. package/.worktrees/dafc71ad/src/stages/execute.js +1277 -0
  687. package/.worktrees/dafc71ad/src/stages/explore.js +43 -0
  688. package/.worktrees/dafc71ad/src/stages/index.js +29 -0
  689. package/.worktrees/dafc71ad/src/stages/knowledge.js +514 -0
  690. package/.worktrees/dafc71ad/src/stages/plan-postcheck.js +1398 -0
  691. package/.worktrees/dafc71ad/src/stages/plan.js +655 -0
  692. package/.worktrees/dafc71ad/src/stages/quick.js +167 -0
  693. package/.worktrees/dafc71ad/src/stages/scan.js +608 -0
  694. package/.worktrees/dafc71ad/src/stages/status.js +64 -0
  695. package/.worktrees/dafc71ad/src/stages/verify.js +246 -0
  696. package/.worktrees/dafc71ad/src/sync.js +1470 -0
  697. package/.worktrees/dafc71ad/src/task-review.js +1498 -0
  698. package/.worktrees/dafc71ad/src/taskcard-placeholders.js +21 -0
  699. package/.worktrees/dafc71ad/src/taskcard.js +250 -0
  700. package/.worktrees/dafc71ad/src/verify-postcheck.js +1718 -0
  701. package/.worktrees/dafc71ad/src/verify-probes.js +327 -0
  702. package/.worktrees/dafc71ad/src/version.js +19 -0
  703. package/.worktrees/dafc71ad/src/workflow.js +600 -0
  704. package/.worktrees/dafc71ad/src/workspace.js +125 -0
  705. package/.worktrees/dafc71ad/src/worktree-apply.js +1611 -0
  706. package/.worktrees/dafc71ad/src/worktree-deps.js +668 -0
  707. package/.worktrees/dafc71ad/src/worktree.js +1849 -0
  708. package/.worktrees/dafc71ad/templates/claude-instruction.md +23 -0
  709. package/.worktrees/dafc71ad/templates/prompts/taskcard-rules.md +29 -0
  710. package/.worktrees/dafc71ad/templates/prompts/testcase-design.md +12 -0
  711. package/.worktrees/dafc71ad/templates/prompts/verify-probes.md +32 -0
  712. package/.worktrees/dafc71ad/templates/workflows/archive-impact.yaml +79 -0
  713. package/.worktrees/dafc71ad/templates/workflows/scan-docs.yaml +132 -0
  714. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  715. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-auto/SKILL.md +93 -0
  716. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  717. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  718. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-continue/SKILL.md +47 -0
  719. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  720. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-execute/SKILL.md +214 -0
  721. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-explore/SKILL.md +102 -0
  722. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-export/SKILL.md +57 -0
  723. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-init/SKILL.md +177 -0
  724. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  725. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  726. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-propose/SKILL.md +87 -0
  727. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  728. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-resume/SKILL.md +70 -0
  729. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  730. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-state/SKILL.md +64 -0
  731. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-status/SKILL.md +54 -0
  732. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  733. package/.worktrees/e58fcc4c/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
  734. package/.worktrees/e58fcc4c/.gitattributes +2 -0
  735. package/.worktrees/e58fcc4c/.husky/pre-push +20 -0
  736. package/.worktrees/e58fcc4c/README.md +203 -0
  737. package/.worktrees/e58fcc4c/SKILL.md +91 -0
  738. package/.worktrees/e58fcc4c/architecture-analysis.md +503 -0
  739. package/.worktrees/e58fcc4c/bin/sillyspec.js +2 -0
  740. package/.worktrees/e58fcc4c/eval/README.md +86 -0
  741. package/.worktrees/e58fcc4c/eval/eval.config.json +28 -0
  742. package/.worktrees/e58fcc4c/eval/lib/adapters/cli.mjs +63 -0
  743. package/.worktrees/e58fcc4c/eval/lib/adapters/dryrun.mjs +27 -0
  744. package/.worktrees/e58fcc4c/eval/lib/config.mjs +82 -0
  745. package/.worktrees/e58fcc4c/eval/lib/db.mjs +76 -0
  746. package/.worktrees/e58fcc4c/eval/lib/report.mjs +120 -0
  747. package/.worktrees/e58fcc4c/eval/lib/runner.mjs +157 -0
  748. package/.worktrees/e58fcc4c/eval/lib/tasks.mjs +73 -0
  749. package/.worktrees/e58fcc4c/eval/lib/util.mjs +107 -0
  750. package/.worktrees/e58fcc4c/eval/lib/workdir.mjs +30 -0
  751. package/.worktrees/e58fcc4c/eval/run.mjs +83 -0
  752. package/.worktrees/e58fcc4c/eval/tasks/001-slugify/files/package.json +6 -0
  753. package/.worktrees/e58fcc4c/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
  754. package/.worktrees/e58fcc4c/eval/tasks/001-slugify/task.json +6 -0
  755. package/.worktrees/e58fcc4c/eval/tasks/001-slugify/verify.mjs +28 -0
  756. package/.worktrees/e58fcc4c/eval/tasks/002-each-day/files/package.json +6 -0
  757. package/.worktrees/e58fcc4c/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
  758. package/.worktrees/e58fcc4c/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
  759. package/.worktrees/e58fcc4c/eval/tasks/002-each-day/task.json +6 -0
  760. package/.worktrees/e58fcc4c/eval/tasks/002-each-day/verify.mjs +31 -0
  761. package/.worktrees/e58fcc4c/eval/tasks/003-word-report/files/data/notes.txt +5 -0
  762. package/.worktrees/e58fcc4c/eval/tasks/003-word-report/files/package.json +6 -0
  763. package/.worktrees/e58fcc4c/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
  764. package/.worktrees/e58fcc4c/eval/tasks/003-word-report/task.json +6 -0
  765. package/.worktrees/e58fcc4c/eval/tasks/003-word-report/verify.mjs +38 -0
  766. package/.worktrees/e58fcc4c/logo.jpg +0 -0
  767. package/.worktrees/e58fcc4c/package-lock.json +977 -0
  768. package/.worktrees/e58fcc4c/package.json +41 -0
  769. package/.worktrees/e58fcc4c/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
  770. package/.worktrees/e58fcc4c/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  771. package/.worktrees/e58fcc4c/packages/dashboard/dist/favicon.jpg +0 -0
  772. package/.worktrees/e58fcc4c/packages/dashboard/dist/index.html +16 -0
  773. package/.worktrees/e58fcc4c/packages/dashboard/dist/logo.jpg +0 -0
  774. package/.worktrees/e58fcc4c/packages/dashboard/dist/prototype-dashboard.html +836 -0
  775. package/.worktrees/e58fcc4c/packages/dashboard/dist/prototype-overview.html +256 -0
  776. package/.worktrees/e58fcc4c/packages/dashboard/package.json +25 -0
  777. package/.worktrees/e58fcc4c/packages/dashboard/server/executor.js +95 -0
  778. package/.worktrees/e58fcc4c/packages/dashboard/server/index.js +679 -0
  779. package/.worktrees/e58fcc4c/packages/dashboard/server/parser.js +526 -0
  780. package/.worktrees/e58fcc4c/packages/dashboard/server/watcher.js +357 -0
  781. package/.worktrees/e58fcc4c/src/agent-session-log.js +803 -0
  782. package/.worktrees/e58fcc4c/src/change-list.js +290 -0
  783. package/.worktrees/e58fcc4c/src/change-risk-profile.js +339 -0
  784. package/.worktrees/e58fcc4c/src/check-primitives.js +65 -0
  785. package/.worktrees/e58fcc4c/src/classify-change.js +109 -0
  786. package/.worktrees/e58fcc4c/src/commit-suggest.js +202 -0
  787. package/.worktrees/e58fcc4c/src/config-cat.js +88 -0
  788. package/.worktrees/e58fcc4c/src/config-schema.js +328 -0
  789. package/.worktrees/e58fcc4c/src/constants.js +97 -0
  790. package/.worktrees/e58fcc4c/src/contract-matrix.js +533 -0
  791. package/.worktrees/e58fcc4c/src/datetime.js +20 -0
  792. package/.worktrees/e58fcc4c/src/db-engine.js +55 -0
  793. package/.worktrees/e58fcc4c/src/db.js +381 -0
  794. package/.worktrees/e58fcc4c/src/decision-distill.js +517 -0
  795. package/.worktrees/e58fcc4c/src/dispatch/backends/local-agent.js +155 -0
  796. package/.worktrees/e58fcc4c/src/dispatch/backends/sillyhub-mcp.js +245 -0
  797. package/.worktrees/e58fcc4c/src/dispatch/probe.js +246 -0
  798. package/.worktrees/e58fcc4c/src/dispatch/strategy.js +121 -0
  799. package/.worktrees/e58fcc4c/src/docs-check.js +825 -0
  800. package/.worktrees/e58fcc4c/src/docs-debt.js +393 -0
  801. package/.worktrees/e58fcc4c/src/docs-gate.js +107 -0
  802. package/.worktrees/e58fcc4c/src/doctor-diagnostics.js +1099 -0
  803. package/.worktrees/e58fcc4c/src/endpoint-extractor.js +409 -0
  804. package/.worktrees/e58fcc4c/src/foreign-declared.js +155 -0
  805. package/.worktrees/e58fcc4c/src/fs-atomic.js +73 -0
  806. package/.worktrees/e58fcc4c/src/git-helper.js +150 -0
  807. package/.worktrees/e58fcc4c/src/hooks/claude-pre-tool-use.cjs +130 -0
  808. package/.worktrees/e58fcc4c/src/hooks/worktree-guard.js +892 -0
  809. package/.worktrees/e58fcc4c/src/index.js +2854 -0
  810. package/.worktrees/e58fcc4c/src/init.js +626 -0
  811. package/.worktrees/e58fcc4c/src/knowledge-match.js +231 -0
  812. package/.worktrees/e58fcc4c/src/local-detect.js +128 -0
  813. package/.worktrees/e58fcc4c/src/local-register.js +104 -0
  814. package/.worktrees/e58fcc4c/src/machine-interface.js +543 -0
  815. package/.worktrees/e58fcc4c/src/migrate.js +127 -0
  816. package/.worktrees/e58fcc4c/src/module-impact.js +166 -0
  817. package/.worktrees/e58fcc4c/src/module-resolve.js +222 -0
  818. package/.worktrees/e58fcc4c/src/modules.js +580 -0
  819. package/.worktrees/e58fcc4c/src/plan-adopt-waves.js +126 -0
  820. package/.worktrees/e58fcc4c/src/progress/change-registry.js +336 -0
  821. package/.worktrees/e58fcc4c/src/progress/consistency-doctor.js +355 -0
  822. package/.worktrees/e58fcc4c/src/progress/shared.js +36 -0
  823. package/.worktrees/e58fcc4c/src/progress/stage-machine.js +594 -0
  824. package/.worktrees/e58fcc4c/src/progress/step-store.js +265 -0
  825. package/.worktrees/e58fcc4c/src/progress.js +1304 -0
  826. package/.worktrees/e58fcc4c/src/quick-recommend.js +115 -0
  827. package/.worktrees/e58fcc4c/src/quicklog.js +883 -0
  828. package/.worktrees/e58fcc4c/src/review-tier.js +77 -0
  829. package/.worktrees/e58fcc4c/src/run/command.js +1641 -0
  830. package/.worktrees/e58fcc4c/src/run/complete-handlers.js +1552 -0
  831. package/.worktrees/e58fcc4c/src/run/complete.js +1259 -0
  832. package/.worktrees/e58fcc4c/src/run/concurrent-detect.js +156 -0
  833. package/.worktrees/e58fcc4c/src/run/gates.js +1111 -0
  834. package/.worktrees/e58fcc4c/src/run/multi-repo-context.js +208 -0
  835. package/.worktrees/e58fcc4c/src/run/next.js +128 -0
  836. package/.worktrees/e58fcc4c/src/run/prompt.js +843 -0
  837. package/.worktrees/e58fcc4c/src/run/quick-audit.js +186 -0
  838. package/.worktrees/e58fcc4c/src/run/scan-profile.js +269 -0
  839. package/.worktrees/e58fcc4c/src/run/shared.js +1562 -0
  840. package/.worktrees/e58fcc4c/src/run/stage.js +587 -0
  841. package/.worktrees/e58fcc4c/src/run.js +23 -0
  842. package/.worktrees/e58fcc4c/src/scan-diff.js +388 -0
  843. package/.worktrees/e58fcc4c/src/scan-postcheck.js +524 -0
  844. package/.worktrees/e58fcc4c/src/scan-staleness.js +95 -0
  845. package/.worktrees/e58fcc4c/src/setup.js +414 -0
  846. package/.worktrees/e58fcc4c/src/sillyhub-mcp/client.js +597 -0
  847. package/.worktrees/e58fcc4c/src/sillyhub-mcp/config.js +62 -0
  848. package/.worktrees/e58fcc4c/src/spec-dir-typo.js +50 -0
  849. package/.worktrees/e58fcc4c/src/spec-sync.js +358 -0
  850. package/.worktrees/e58fcc4c/src/stage-contract-engine.js +171 -0
  851. package/.worktrees/e58fcc4c/src/stage-contract-spec.js +551 -0
  852. package/.worktrees/e58fcc4c/src/stage-contract.js +948 -0
  853. package/.worktrees/e58fcc4c/src/stage-review.js +597 -0
  854. package/.worktrees/e58fcc4c/src/stages/archive.js +199 -0
  855. package/.worktrees/e58fcc4c/src/stages/brainstorm-auto.js +285 -0
  856. package/.worktrees/e58fcc4c/src/stages/brainstorm.js +624 -0
  857. package/.worktrees/e58fcc4c/src/stages/cmd-existence.js +145 -0
  858. package/.worktrees/e58fcc4c/src/stages/doctor.js +540 -0
  859. package/.worktrees/e58fcc4c/src/stages/execute.js +1277 -0
  860. package/.worktrees/e58fcc4c/src/stages/explore.js +43 -0
  861. package/.worktrees/e58fcc4c/src/stages/index.js +29 -0
  862. package/.worktrees/e58fcc4c/src/stages/knowledge.js +514 -0
  863. package/.worktrees/e58fcc4c/src/stages/plan-postcheck.js +1398 -0
  864. package/.worktrees/e58fcc4c/src/stages/plan.js +655 -0
  865. package/.worktrees/e58fcc4c/src/stages/quick.js +167 -0
  866. package/.worktrees/e58fcc4c/src/stages/scan.js +608 -0
  867. package/.worktrees/e58fcc4c/src/stages/status.js +64 -0
  868. package/.worktrees/e58fcc4c/src/stages/verify.js +246 -0
  869. package/.worktrees/e58fcc4c/src/sync.js +1470 -0
  870. package/.worktrees/e58fcc4c/src/task-review.js +1498 -0
  871. package/.worktrees/e58fcc4c/src/taskcard-placeholders.js +21 -0
  872. package/.worktrees/e58fcc4c/src/taskcard.js +250 -0
  873. package/.worktrees/e58fcc4c/src/verify-postcheck.js +1718 -0
  874. package/.worktrees/e58fcc4c/src/verify-probes.js +327 -0
  875. package/.worktrees/e58fcc4c/src/version.js +19 -0
  876. package/.worktrees/e58fcc4c/src/workflow.js +600 -0
  877. package/.worktrees/e58fcc4c/src/workspace.js +125 -0
  878. package/.worktrees/e58fcc4c/src/worktree-apply.js +1611 -0
  879. package/.worktrees/e58fcc4c/src/worktree-deps.js +668 -0
  880. package/.worktrees/e58fcc4c/src/worktree.js +1849 -0
  881. package/.worktrees/e58fcc4c/templates/claude-instruction.md +23 -0
  882. package/.worktrees/e58fcc4c/templates/prompts/taskcard-rules.md +29 -0
  883. package/.worktrees/e58fcc4c/templates/prompts/testcase-design.md +12 -0
  884. package/.worktrees/e58fcc4c/templates/prompts/verify-probes.md +32 -0
  885. package/.worktrees/e58fcc4c/templates/workflows/archive-impact.yaml +79 -0
  886. package/.worktrees/e58fcc4c/templates/workflows/scan-docs.yaml +132 -0
  887. package/package.json +1 -1
  888. package/src/contract-matrix.js +6 -3
  889. package/src/foreign-declared.js +11 -5
  890. package/src/fs-atomic.js +20 -3
  891. package/src/hooks/worktree-guard.js +9 -3
  892. package/src/index.js +27 -12
  893. package/src/module-impact.js +1 -1
  894. package/src/modules.js +23 -21
  895. package/src/progress/stage-machine.js +23 -0
  896. package/src/progress.js +38 -2
  897. package/src/quicklog.js +7 -5
  898. package/src/run/command.js +5 -5
  899. package/src/run/complete-handlers.js +6 -2
  900. package/src/run/complete.js +33 -20
  901. package/src/run/gates.js +14 -4
  902. package/src/run/next.js +4 -3
  903. package/src/run/prompt.js +66 -6
  904. package/src/run/scan-profile.js +1 -0
  905. package/src/run/shared.js +11 -5
  906. package/src/run/stage.js +7 -7
  907. package/src/scan-diff.js +4 -2
  908. package/src/stage-contract.js +16 -8
  909. package/src/stages/brainstorm.js +22 -8
  910. package/src/stages/execute.js +36 -9
  911. package/src/stages/knowledge.js +18 -5
  912. package/src/stages/plan-postcheck.js +63 -7
  913. package/src/stages/plan.js +5 -2
  914. package/src/sync.js +27 -11
  915. package/src/task-review.js +5 -3
  916. package/src/taskcard.js +2 -0
  917. package/src/verify-postcheck.js +24 -13
  918. package/src/verify-probes.js +12 -7
  919. package/src/worktree-apply.js +38 -14
  920. package/src/worktree-deps.js +1 -1
  921. package/src/worktree.js +7 -2
@@ -0,0 +1,1470 @@
1
+ /**
2
+ * SillySpec SyncManager — SillyHub 平台同步模块
3
+ *
4
+ * 独立于 ProgressManager,由 run.js 和 index.js 调用。
5
+ * Best effort:所有网络失败 console.warn,不抛错,不阻塞主流程。
6
+ *
7
+ * 配置来源:.sillyspec/local.yaml 中的 platform 段
8
+ * HTTP 请求:Node.js 原生 fetch(Node 22+)
9
+ */
10
+
11
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, unlinkSync, chmodSync } from 'fs';
12
+ import { writeAtomicSync } from './fs-atomic.js';
13
+ import { join } from 'path';
14
+ import { resolvePlatformSpecDir } from './progress.js';
15
+ import { safeGit } from './git-helper.js';
16
+ import { openDatabase } from './db-engine.js';
17
+ import { PLATFORM_MANAGED_FILENAME } from './run/shared.js';
18
+ import { syncSpecTree } from './spec-sync.js';
19
+
20
+ // sync 是 best-effort(网络失败只 warn):平台指针失效时不抛,跳过平台、回退本地。
21
+ function safePlatformSpecDir(cwd) {
22
+ try {
23
+ return resolvePlatformSpecDir(cwd);
24
+ } catch (e) {
25
+ console.warn(`[sync] 平台指针不可用,跳过平台同步:${String(e.message).split('\n')[0]}`);
26
+ return undefined;
27
+ }
28
+ }
29
+
30
+ const LOCAL_YAML = '.sillyspec/local.yaml';
31
+ const CHANGES_DIR = '.sillyspec/changes';
32
+ const REQUEST_TIMEOUT_MS = 10_000;
33
+
34
+ // 未连接平台是本地独立用户的合法默认状态。sync / checkApproval 由 run 流程在后台 best-effort
35
+ // 触发(每步完成、execute 阶段启动);syncDocuments 仅由手动 `sillyspec platform sync-docs`
36
+ // 命令触发(index.js sync-docs 分支),run 流程不自动推文档。未连接时默认静默跳过——不每步
37
+ // 催连平台制造噪音(本地用户根本不需要平台)。需要排查同步行为时设 SILLYSPEC_DEBUG_SYNC=1。
38
+ function debugLog(msg) {
39
+ if (process.env.SILLYSPEC_DEBUG_SYNC) console.warn(msg)
40
+ }
41
+
42
+ /** 四件套文档文件名 */
43
+ const DOCUMENT_FILES = ['proposal.md', 'design.md', 'requirements.md', 'tasks.md'];
44
+
45
+ // ── YAML 辅助 ──
46
+
47
+ /**
48
+ * 简易 YAML 读取(解析为扁平 obj),供 _getPlatform / status 只读用。
49
+ * 与 worktree-guard.js 的 parseSimpleYaml 保持一致的轻量风格。
50
+ *
51
+ * 写入侧(connect/disconnect)**不再**走 parse→modify→write 有损往返——
52
+ * readLocalYaml/parseSimpleYaml 丢注释(:85 跳 # 行)、丢数组/深嵌套(只认一层 key:value),
53
+ * round-trip 会清空用户手填的注释与其他段。改为 readLocalYamlRaw + replaceTopLevelSection
54
+ * 文本级定向替换 platform 段,原文件注释/空行/其他段/数组/深嵌套/CRLF 字节级保留。
55
+ */
56
+ function readLocalYaml(cwd) {
57
+ const p = join(cwd, LOCAL_YAML);
58
+ if (!existsSync(p)) return {};
59
+ try {
60
+ return parseSimpleYaml(readFileSync(p, 'utf8'));
61
+ } catch {
62
+ return {};
63
+ }
64
+ }
65
+
66
+ /** 读 local.yaml 原始文本(保留注释/换行/结构),不存在返回 ''。供 connect/disconnect 文本级改写。 */
67
+ function readLocalYamlRaw(cwd) {
68
+ const p = join(cwd, LOCAL_YAML);
69
+ if (!existsSync(p)) return '';
70
+ try {
71
+ return readFileSync(p, 'utf8');
72
+ } catch {
73
+ return '';
74
+ }
75
+ }
76
+
77
+ /**
78
+ * 定位顶层 YAML 段(如 platform:/mcp:)的行范围 [start, end)。
79
+ * 段 = key 行(行首非空白 + 以 'name:' 开头)+ 后续连续缩进行(以空格/tab 开头)。
80
+ * 遇空行/注释/下一个顶层 key 即段结束——这些行不属于本段,保留不动。
81
+ * split('\n')/join('\n') 操作:CRLF 下 '\r' 留在行尾,重组原样还原(Windows 兼容,CLAUDE.md #13)。
82
+ * @param {string} text
83
+ * @param {string} name - 段名(不含冒号,如 'platform')
84
+ * @returns {{start: number, end: number} | null} 不存在返回 null
85
+ */
86
+ function findTopLevelSectionRange(text, name) {
87
+ const lines = text.split('\n');
88
+ const prefix = `${name}:`;
89
+ let start = -1;
90
+ for (let i = 0; i < lines.length; i++) {
91
+ const line = lines[i];
92
+ // 顶层 key 行:行首非空白(排除缩进子段)+ 以 'name:' 开头
93
+ if (/^\S/.test(line) && line.startsWith(prefix)) {
94
+ start = i;
95
+ break;
96
+ }
97
+ }
98
+ if (start === -1) return null;
99
+ let end = start + 1;
100
+ while (end < lines.length && /^[ \t]/.test(lines[end])) {
101
+ end++;
102
+ }
103
+ return { start, end };
104
+ }
105
+
106
+ /**
107
+ * 文本级定向替换/删除/追加一个顶层 YAML 段,保留文件其余所有字节
108
+ * (注释/空行/其他段/数组/深嵌套/CRLF 全保留)。
109
+ * @param {string} text - 原始文件文本
110
+ * @param {string} name - 段名(不含冒号)
111
+ * @param {string|null} body - 段体(不含 key 行);null=删除该段;string=替换或追加
112
+ * @returns {string} 新文本
113
+ */
114
+ function replaceTopLevelSection(text, name, body) {
115
+ const lines = text.split('\n');
116
+ const range = findTopLevelSectionRange(text, name);
117
+ if (range) {
118
+ const before = lines.slice(0, range.start);
119
+ const after = lines.slice(range.end);
120
+ if (body === null) {
121
+ return [...before, ...after].join('\n');
122
+ }
123
+ const sectionLines = [`${name}:`, ...body.split('\n')];
124
+ return [...before, ...sectionLines, ...after].join('\n');
125
+ }
126
+ // 段不存在
127
+ if (body === null) return text; // 删不存在的段,原样返回
128
+ // 追加:去尾换行后加空行分隔 + 新段;空文件直接起段
129
+ const stripped = text.replace(/(\r?\n)+$/, '');
130
+ if (stripped === '') {
131
+ return `${name}:\n${body}\n`;
132
+ }
133
+ return `${stripped}\n\n${name}:\n${body}\n`;
134
+ }
135
+
136
+ /** 文本级写 local.yaml(保留传入 text 的注释/结构);确保 .sillyspec 目录存在。
137
+ * 原子写(fs-atomic 契约:local.yaml 被 hook/probe 等其他进程并发读,裸 writeFileSync 的
138
+ * 写入窗口可读到半截文件 → mcp 段解析失败回退 env → 误判 no-config 降级)。 */
139
+ function writeLocalYamlRaw(cwd, text) {
140
+ const dir = join(cwd, '.sillyspec');
141
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
142
+ const yamlPath = join(cwd, LOCAL_YAML);
143
+ writeAtomicSync(yamlPath, text, 'utf8');
144
+ // 体检 SEC-04:local.yaml 含明文 token,收紧为仅属主可读写。POSIX chmod 0600 生效;
145
+ // Windows 上 Node chmod 只映射只读位(近似 no-op),属可接受 best-effort
146
+ try { chmodSync(yamlPath, 0o600); } catch { /* chmod 失败不阻断连接流程 */ }
147
+ }
148
+
149
+ function parseSimpleYaml(content) {
150
+ // 剥 YAML 双/单引号包裹(connect 写侧 yamlStr 加引号防 # : 注入,读侧对称剥;无引号值零影响)
151
+ const unquote = (v) => {
152
+ if (v.length >= 2 && ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")))) {
153
+ return v.slice(1, -1);
154
+ }
155
+ return v;
156
+ };
157
+ const result = {};
158
+ let currentSection = null;
159
+ for (const line of content.split('\n')) {
160
+ const trimmed = line.trim();
161
+ if (!trimmed || trimmed.startsWith('#')) continue;
162
+ // 用原始 line(非 trimmed)判断缩进:trimmed 已去前导空格,startsWith(' ') 恒 false
163
+ // 会导致缩进子段(如 platform 下 url/token)被误判为 root 行、section 恒为空 {}
164
+ if (!line.startsWith(' ')) {
165
+ const m = trimmed.match(/^(\S+)\s*:\s*(.*)$/);
166
+ if (m) {
167
+ const key = m[1];
168
+ const val = unquote(m[2].trim());
169
+ if (val) {
170
+ result[key] = val;
171
+ currentSection = null;
172
+ } else {
173
+ result[key] = {};
174
+ currentSection = key;
175
+ }
176
+ }
177
+ } else if (currentSection) {
178
+ const m = trimmed.match(/^(\S+)\s*:\s*(.*)$/);
179
+ if (m && result[currentSection] && typeof result[currentSection] === 'object') {
180
+ result[currentSection][m[1]] = unquote(m[2].trim());
181
+ }
182
+ }
183
+ }
184
+ return result;
185
+ }
186
+
187
+ /**
188
+ * 解析推送者身份(user):区分多用户,供 push 时 X-SillySpec-User 标识(design D-004)。
189
+ * 优先级:显式参数 > git user.name(与 quicklog / prompt <git-user> 同口径)> process.env.USER
190
+ * (Unix)/ USERNAME(Windows)。Best effort:全失败返回 null,调用方据此决定是否写 user 字段
191
+ * (缺字段等价于「未知推送者」,平台侧自行兜底,不报错)。
192
+ */
193
+ function resolvePlatformUser(cwd, explicitUser) {
194
+ if (typeof explicitUser === 'string' && explicitUser.trim()) {
195
+ return explicitUser.trim();
196
+ }
197
+ // 回退 1:git user.name(safeGit 失败返回 null,不抛)
198
+ const gitUser = safeGit(cwd, ['config', 'user.name']).value;
199
+ if (gitUser) return gitUser;
200
+ // 回退 2:环境变量(USER=Unix / USERNAME=Windows;跨平台兼容 CLAUDE.md #13)
201
+ return process.env.USER || process.env.USERNAME || null;
202
+ }
203
+
204
+ // ── HTTP 辅助 ──
205
+
206
+ // HUB-09:单请求超时与外部 signal 合并——自动同步熔断(run/shared.js trigger* 的 race)
207
+ // 触发 abort 时在飞 fetch 被真实取消,而不是熔断后任由请求自行完成(平台可能已接受,
208
+ // 客户端却当作超时放弃;spec 树推送无 base_ts 自愈兜底)。AbortSignal.any 需 Node ≥20.3。
209
+ function combineSignals(external) {
210
+ const timeoutSignal = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
211
+ return external ? AbortSignal.any([timeoutSignal, external]) : timeoutSignal;
212
+ }
213
+
214
+ async function fetchJson(url, options = {}) {
215
+ const signal = combineSignals(options.signal);
216
+ try {
217
+ const res = await fetch(url, { ...options, signal });
218
+ if (!res.ok) {
219
+ const text = await res.text().catch(() => '');
220
+ console.warn(`[sync] ${options.method || 'GET'} ${url} → ${res.status} ${text.slice(0, 200)}`);
221
+ return null;
222
+ }
223
+ const ct = res.headers.get('content-type') || '';
224
+ if (ct.includes('application/json')) {
225
+ return res.json();
226
+ }
227
+ return null;
228
+ } catch (err) {
229
+ if (err.name === 'AbortError') {
230
+ console.warn(`[sync] ${url} 请求超时/中断 (${REQUEST_TIMEOUT_MS}ms 上限或外部熔断)`);
231
+ } else {
232
+ console.warn(`[sync] ${url} 请求失败: ${err.message}`);
233
+ }
234
+ return null;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * 带状态码的 fetch:不吞非 2xx(fetchJson 在 !res.ok 时返回 null 丢状态,无法区分 409 冲突)。
240
+ * 返回 { ok, status, body }:body 尽力 JSON.parse(平台冲突响应带 progress JSON,读回给调用方)。
241
+ * 仅 sync() 的 progress POST 使用(识别 base_ts 乐观锁冲突,D-015 / task-09);其余调用仍走 fetchJson。
242
+ */
243
+ async function fetchJsonWithStatus(url, options = {}) {
244
+ const signal = combineSignals(options.signal);
245
+ try {
246
+ const res = await fetch(url, { ...options, signal });
247
+ const text = await res.text().catch(() => '');
248
+ let body = null;
249
+ if (text) {
250
+ try { body = JSON.parse(text); } catch { body = null; }
251
+ }
252
+ if (!res.ok) {
253
+ console.warn(`[sync] ${options.method || 'GET'} ${url} → ${res.status} ${text.slice(0, 200)}`);
254
+ }
255
+ return { ok: res.ok, status: res.status, body };
256
+ } catch (err) {
257
+ if (err.name === 'AbortError') {
258
+ console.warn(`[sync] ${url} 请求超时/中断 (${REQUEST_TIMEOUT_MS}ms 上限或外部熔断)`);
259
+ } else {
260
+ console.warn(`[sync] ${url} 请求失败: ${err.message}`);
261
+ }
262
+ return { ok: false, status: 0, body: null };
263
+ }
264
+ }
265
+
266
+ // ── SyncManager ──
267
+
268
+ export class SyncManager {
269
+ constructor(cwd) {
270
+ this.cwd = cwd;
271
+ // 归档尾声静默化旗标(坑 post-archive-sync-noise):sync() 探测到已归档时置位,
272
+ // 链内 syncDocuments 的「变更不存在」降 debug(每次 sync 调用重置)
273
+ this._suppressDocsMissingWarn = false;
274
+ }
275
+
276
+ /**
277
+ * 连接 SillyHub 平台。
278
+ * 保存配置到 .sillyspec/local.yaml,发送 ping 验证连接。
279
+ * @param {string} url - 平台地址
280
+ * @param {string} token - 认证 token
281
+ * @param {string} [user] - 推送者身份(可空,回退 git user.name / env,见 resolvePlatformUser)
282
+ */
283
+ async connect(url, token, user) {
284
+ if (!token) {
285
+ // 体检 HUB-02:falsy token 不允许 connect——health ping 无鉴权会"连接成功",
286
+ // 但 local.yaml 会落盘字符串 "undefined",后续所有同步持续 401
287
+ console.error('[sync] 缺少 token,拒绝连接(请在平台获取 token 后带 --token 重试)');
288
+ return false;
289
+ }
290
+ // 验证连接
291
+ const normalizedUrl = url.replace(/\/+$/, '');
292
+ // 体检 SEC-04:非 https 且非本机回环时 Bearer token 明文上线——MCP client 侧
293
+ // (sillyhub-mcp/client.js 构造器)早有同款 warn,sync 侧此前完全静默
294
+ if (!/^https:\/\//i.test(normalizedUrl) && !/^http:\/\/(localhost|127\.0\.0\.1|\[::1\])(:|\/|$)/i.test(normalizedUrl)) {
295
+ console.warn(`[sync] platform.url 非 https(${normalizedUrl.slice(0, 60)}),同步 token 将明文传输——请确认为受控内网环境`);
296
+ }
297
+ const healthUrl = `${normalizedUrl}/api/health`;
298
+ const result = await fetchJson(healthUrl);
299
+ if (result === null) {
300
+ console.warn(`[sync] 平台连接验证失败: ${url}`);
301
+ return;
302
+ }
303
+ console.log(`[sync] 平台连接成功: ${url}`);
304
+
305
+ // 换发 workspace-scoped 同步 token(task-09 / D-005@v1):用传入 user 级 token
306
+ // (shk_live_ / JWT)作 Bearer + 本地 root_path(connect 的 cwd,与平台
307
+ // Workspace.root_path 绑定值等值匹配)调 resolve-by-root-path。成功取 shpsync_
308
+ // 覆盖 token 写入 platform 段;404(root_path 未绑)/403(无 WORKSPACE_WRITE)/断网
309
+ // 均降级沿用原 token 继续(best-effort 不阻断 connect)。
310
+ let effectiveToken = token;
311
+ const resolveUrl = `${normalizedUrl}/api/workspaces/resolve-by-root-path`;
312
+ const resolved = await fetchJson(resolveUrl, {
313
+ method: 'POST',
314
+ headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
315
+ body: JSON.stringify({ root_path: this.cwd }),
316
+ });
317
+ if (resolved && resolved.token && resolved.token.startsWith('shpsync_')) {
318
+ effectiveToken = resolved.token;
319
+ console.log(
320
+ `[sync] 已换发 workspace-scoped 同步 token (workspace: ${resolved.workspace_id})`
321
+ );
322
+ } else {
323
+ console.warn(
324
+ '[sync] 换发 workspace-scoped token 失败(404/403/断网),沿用原 token 继续'
325
+ );
326
+ }
327
+
328
+ // 解析推送者身份(D-004):显式 > git user.name > env;全失败留空不写 user 字段
329
+ const resolvedUser = resolvePlatformUser(this.cwd, user);
330
+
331
+ // 文本级改写 local.yaml:定向替换 platform 段,原文件注释/其他段/数组/深嵌套/CRLF 字节级保留
332
+ // (旧 read-modify-write 经 parseSimpleYaml 丢注释+丢非扁平结构,round-trip 清空用户手填内容)。
333
+ // YAML 值加引号:token/url 含 # : 或首尾空白时裸写会破坏段结构或注入额外键(shpsync_ token
334
+ // 当前不含这些字符,防御性包裹零成本;parseSimpleYaml 读侧剥引号兼容)
335
+ const yamlStr = (v) => JSON.stringify(String(v));
336
+ const platformEntries = [
337
+ ` url: ${yamlStr(normalizedUrl)}`,
338
+ ` token: ${yamlStr(effectiveToken)}`,
339
+ ` last_connected: ${yamlStr(new Date().toISOString())}`,
340
+ ];
341
+ if (resolvedUser) {
342
+ platformEntries.push(` user: ${yamlStr(resolvedUser)}`);
343
+ }
344
+ let text = readLocalYamlRaw(this.cwd);
345
+ text = replaceTopLevelSection(text, 'platform', platformEntries.join('\n'));
346
+ // mcp 段同源假设(design §7.4):url 复用 platform 的 url;token 用原始 user 级 token
347
+ // (非换发的 effectiveToken——MCP 派发需 user 级权限,platform 段才是最小权限的 shpsync_)。
348
+ // 用户已手填 mcp 段则保留不覆盖(R-09,文本级检测)
349
+ if (findTopLevelSectionRange(text, 'mcp') === null) {
350
+ text = replaceTopLevelSection(text, 'mcp', ` url: ${yamlStr(normalizedUrl)}\n token: ${yamlStr(token)}`);
351
+ }
352
+ writeLocalYamlRaw(this.cwd, text);
353
+ }
354
+
355
+ /**
356
+ * 断开平台连接(三清,D-C@v2)。
357
+ * 1. 从 local.yaml 删除 platform 配置段;
358
+ * 2. 删恢复指针 <cwd>/.sillyspec-platform.json;
359
+ * 3. 删平台接管声明 <cwd>/.sillyspec-platform-managed。
360
+ * 不删后两者则"disconnect 后恢复本地模式"不可达(指针健在时 resolvePlatformSpecDir
361
+ * 仍解析平台 specRoot;声明健在时指针缺失会 fail-closed)。
362
+ */
363
+ disconnect() {
364
+ const p = join(this.cwd, LOCAL_YAML);
365
+ if (existsSync(p)) {
366
+ const text = readFileSync(p, 'utf8');
367
+ if (findTopLevelSectionRange(text, 'platform') !== null) {
368
+ // 文本级删除 platform 段,保留注释/其他段/数组/深嵌套/CRLF
369
+ const newText = replaceTopLevelSection(text, 'platform', null);
370
+ if (newText.trim() === '') {
371
+ // 删段后纯空白(无任何段也无注释)→ 删除整个文件;注释算内容,有注释则保留
372
+ try { unlinkSync(p); } catch { /* best effort */ }
373
+ } else {
374
+ writeAtomicSync(p, newText, 'utf8');
375
+ }
376
+ }
377
+ }
378
+ // 三清之二/三:指针 + 接管声明(disconnect 是声明的唯一退出路径——design.md §5.4)。
379
+ // HUB-12 有意不清 .sillyspec-platform-cleaned marker:它防的是「重新接入平台时 init
380
+ // 重复做残留清理」——disconnect 后重连若清掉 marker,init 会对现在装着本地数据的
381
+ // .sillyspec 再跑一次平台残留清理(误删风险)。marker 跨 disconnect 存活是保护语义。
382
+ let cleaned = [];
383
+ for (const f of ['.sillyspec-platform.json', PLATFORM_MANAGED_FILENAME]) {
384
+ const fp = join(this.cwd, f);
385
+ if (existsSync(fp)) {
386
+ try { unlinkSync(fp); cleaned.push(f); } catch { /* best effort */ }
387
+ }
388
+ }
389
+ if (cleaned.length > 0) console.log(`[sync] 已清理平台指针与接管声明: ${cleaned.join(', ')}`);
390
+ console.log('[sync] 已断开连接');
391
+ }
392
+
393
+ /**
394
+ * 增量同步变更的 progress 状态到平台。
395
+ * 读取 ProgressManager.serializeForSync() 的六表 JSON,POST 到平台。
396
+ * 元字段(user/base_ts/pushed_at)走 HTTP header,body 保持裸 JSON(D-015,sillyhub 老版零回归)。
397
+ * 同步完成后更新 changes 表的 platform_last_sync 字段;409 冲突读回平台最新 JSON(task-12 完整冲突处理)。
398
+ *
399
+ * push 409 自竞态自愈(坑 2026-08-19-platform-sync-base-ts-silent-conflict 根治侧):
400
+ * 同机多进程(CLI + daemon)并发 push 时,B 进程持旧 base_ts POST 撞 A 进程刚推完的 409——
401
+ * 但 A 的成功回填就写在本机共享 DB。409 时 fresh 重读 DB:base_ts 已 ≥ 平台 409 回执 ts
402
+ * ⇒ 赢者是自己人 → 刷新 base_ts 重试一次即收敛,不再落冲突文件卡死人工 resolve。
403
+ * 外来推送(他机/他用户)不可能推进本机 DB 的 base_ts → 走原冲突路径,无误放行。
404
+ */
405
+ async sync(changeName, opts = {}) {
406
+ const { fromResolve = false } = opts;
407
+ const platform = this._getPlatform();
408
+ if (!platform) {
409
+ debugLog('[sync] 未连接平台(本地合法状态);如需平台同步:sillyspec platform connect');
410
+ return { synced: 0, errors: ['未连接平台'] };
411
+ }
412
+
413
+ if (!changeName) {
414
+ console.warn('[sync] sync 需要指定变更名称 (changeName)');
415
+ return { synced: 0, errors: ['未指定变更名称'] };
416
+ }
417
+
418
+ // 冲突降噪(坑 sync-conflict-banner-spam,2026-08-23 实证:push 冲突落 sync-conflict 文件后,
419
+ // 每步完成的 triggerSync 都会再 push 再 409 再打全幅双线横幅——「已卡死不会自愈」连环刷屏,
420
+ // 观感像致命错误。冲突文件本身就代表「待人工三选一的未决状态」,文件在 = 用户已被告知,
421
+ // 后续自动同步单行提示即可(可见但不吓人);resolve 后文件删除,推送自动恢复。fromResolve
422
+ // (keep-local 自动重推)不在此列——resolve 流程刚删冲突文件,它需要真实 push 判定平台态。
423
+ if (!fromResolve) {
424
+ const pendingConflict = this.readConflictFile(changeName);
425
+ if (pendingConflict) {
426
+ console.warn(`⚠️ [sync] 变更 ${changeName} 存在未决平台冲突,跳过自动推送。处理:sillyspec platform resolve ${changeName} --keep-local | --take-platform | --abort`);
427
+ return { synced: 0, errors: [`conflict pending: ${changeName}`], conflict: true, suppressed: true };
428
+ }
429
+ }
430
+
431
+ // 检查变更目录是否存在(归档后目录已移走但 DB 仍有最终状态需推平台,serializeForSync 从
432
+ // DB 读不依赖文件系统目录)。归档终态探测(坑 post-archive-sync-noise,2026-08-21 实证:
433
+ // 归档尾声连打「目录不存在/变更不存在」warn,观感像出错):确认已归档(changes/archive/
434
+ // 有实体 或 DB status='archived')时降为一行 info 措辞——正常时序不是告警。
435
+ const changeDir = join(this.cwd, CHANGES_DIR, changeName);
436
+ let archivedQuietly = false;
437
+ if (!existsSync(changeDir)) {
438
+ archivedQuietly = existsSync(join(this.cwd, CHANGES_DIR, 'archive', changeName))
439
+ || this._isChangeArchivedInDb(changeName);
440
+ if (archivedQuietly) {
441
+ console.log(`[sync] 变更已归档(目录在 archive/),继续从 DB 推送最终状态: ${changeName}`);
442
+ } else {
443
+ console.warn(`[sync] 变更目录不存在(可能是已归档,继续从 DB 同步最终状态): ${changeName}`);
444
+ }
445
+ }
446
+ // 透传给本次 sync 链内的 syncDocuments(best-effort 四件套直推:归档后目录已移走,
447
+ // 其「变更不存在」warn 属正常时序,静默化)
448
+ this._suppressDocsMissingWarn = archivedQuietly;
449
+
450
+ const MAX_PUSH_ATTEMPTS = 2;
451
+ for (let attempt = 1; attempt <= MAX_PUSH_ATTEMPTS; attempt++) {
452
+ // 读取 progress 数据(serializeForSync 六表裸 JSON,task-02 / D-005@v2,替代 read() 聚合视图)。
453
+ // 每次 attempt 重新 serialize:自愈重试时本地 DB 可能已被并发进程推进,base_ts/内容都要刷新
454
+ let progressData;
455
+ try {
456
+ const { ProgressManager } = await import('./progress.js');
457
+ const pm = new ProgressManager({ specDir: safePlatformSpecDir(this.cwd) });
458
+ progressData = pm.serializeForSync(this.cwd, changeName);
459
+ } catch (err) {
460
+ console.warn(`[sync] 读取 progress 失败 (${changeName}): ${err.message}`);
461
+ return { synced: 0, errors: [`读取 progress 失败: ${err.message}`] };
462
+ }
463
+ if (progressData === null) {
464
+ console.warn(`[sync] 变更无进度数据 (无活跃进度): ${changeName}`);
465
+ return { synced: 0, errors: [`变更无进度数据: ${changeName}`] };
466
+ }
467
+
468
+ // 元字段走 HTTP header(D-015 / task-09):body 保持裸六表 JSON,sillyhub 老版忽略 header 零回归
469
+ const headers = {
470
+ 'Content-Type': 'application/json',
471
+ Authorization: `Bearer ${platform.token}`,
472
+ };
473
+ const pushedAt = new Date().toISOString();
474
+ // 推送者身份(task-08):local.yaml platform.user 显式配置优先,缺省回退
475
+ // resolvePlatformUser(git user.name > env USER/USERNAME,与 connect 写入侧同口径)。
476
+ // 修 last_pusher 空:此前仅 platform.user 配置时发送,本地直跑场景 local.yaml
477
+ // 常无 user 字段 → header 缺失 → 平台 last_pusher 恒空(2026-08-19 坑文档改进点 4)。
478
+ const pushUser = platform.user || resolvePlatformUser(this.cwd) || null;
479
+ if (pushUser) headers['X-SillySpec-User'] = pushUser;
480
+ const baseTs = progressData.changes && progressData.changes[0] && progressData.changes[0].last_synced_platform_ts;
481
+ if (baseTs) headers['X-SillySpec-Base-Ts'] = baseTs; // base_ts 乐观锁(NULL=首次同步不设,平台接受首次 push)
482
+ headers['X-SillySpec-Pushed-At'] = pushedAt; // 平台存 last_pushed_at,作下次其他用户 push 的 base_ts 比对基准
483
+
484
+ // POST 到平台(带状态码版本:识别 409 冲突,读回平台最新 JSON)。changeName encode 对齐 pull(:673)
485
+ const syncUrl = `${platform.url}/api/changes/${encodeURIComponent(changeName)}/progress`;
486
+ const res = await fetchJsonWithStatus(syncUrl, {
487
+ method: 'POST',
488
+ headers,
489
+ body: JSON.stringify(progressData),
490
+ signal: opts.signal, // HUB-09:熔断 abort 传到底层请求
491
+ });
492
+
493
+ if (res.ok) {
494
+ // 更新 platform_last_sync + 推进 base_ts(ql-20260818-008:值优先平台回执 last_pushed_at,
495
+ // 缺省用本次 pushedAt——服务器 _apply 存的就是客户端 X-SillySpec-Pushed-At 原值,回写一致。
496
+ // 修复前该列从不写,下次 push 永不带 X-SillySpec-Base-Ts、pull 脏度检测恒 false)
497
+ // 回填加固:共享 SQLite 并发(daemon+CLI)下 WAL 忙窗口可能偶发失败——重试一次,
498
+ // 仍失败打醒目 warn(base_ts 停留在旧值,下次 push 会 409,届时自竞态自愈会收敛)
499
+ let backfilled = false;
500
+ for (let bt = 1; bt <= 2 && !backfilled; bt++) {
501
+ try {
502
+ const { ProgressManager } = await import('./progress.js');
503
+ const pm = new ProgressManager({ specDir: safePlatformSpecDir(this.cwd) });
504
+ const ackTs = res.body && typeof res.body.last_pushed_at === 'string' ? res.body.last_pushed_at : pushedAt;
505
+ pm._updatePlatformLastSync(this.cwd, changeName, ackTs);
506
+ backfilled = true;
507
+ } catch (err) {
508
+ if (bt === 2) {
509
+ console.warn(`⚠️ [sync] base_ts 回填失败(下次 push 可能 409,自愈机制会处理): ${err.message}`);
510
+ } else {
511
+ await new Promise((r) => setTimeout(r, 200));
512
+ }
513
+ }
514
+ }
515
+
516
+ console.log(`[sync] 已同步变更: ${changeName}`);
517
+
518
+ // 2026-08-16-auto-sync-from-repo:进度上行成功后顺带推四件套文档(best-effort)。
519
+ // 根因:run 流程此前只推进度不推文档(本文件头注释),本地直跑 sillyspec 的产出
520
+ // 文档永远不自动到平台(变更中心"进度到了文档没到")。生产者直连,不依赖 daemon
521
+ // 缓存链路。失败仅 debugLog——文档同步失败不得影响进度上行的返回值与流程;
522
+ // 四件套全缺失时 syncDocuments 内部已有跳过(syncedCount===0 提前返回,不调端点)。
523
+ try {
524
+ await this.syncDocuments(changeName, { signal: opts.signal });
525
+ } catch (err) {
526
+ debugLog(`[sync] 文档同步失败(不影响进度): ${changeName}: ${err.message}`);
527
+ }
528
+
529
+ // 2026-08-17-spec-file-incremental-sync:进度上行+文档直推成功后顺带推整个 spec 树
530
+ // (plan.md、tasks/、module-impact.md 等),让变更中心文件树自动更新。
531
+ // 无 daemon 时 CLI 短进程无本地清单缓存,以服务器清单为锚做增量 diff。
532
+ // 树根与进度锚点同源(safePlatformSpecDir,平台模式 → specRoot):此前硬编码
533
+ // cwd/.sillyspec,平台模式下该目录只有 local.yaml(被 walk 排除)→ 本地树恒空
534
+ // → 服务器清单全量 delete(BUG-01,computeSpecOps 护栏为第二道防线)。
535
+ try {
536
+ await syncSpecTree(safePlatformSpecDir(this.cwd) || join(this.cwd, '.sillyspec'), this._getPlatform(), changeName, { signal: opts.signal });
537
+ } catch (err) {
538
+ debugLog(`[sync] spec 树增量同步失败(不影响进度): ${changeName}: ${err.message}`);
539
+ }
540
+
541
+ return { synced: 1, errors: [] };
542
+ }
543
+
544
+ if (res.status !== 409) {
545
+ return { synced: 0, errors: [`同步请求失败: ${changeName}`] };
546
+ }
547
+
548
+ // ── 409 = base_ts 乐观锁冲突(平台已有更新)──
549
+ // 平台 409 响应 { conflict:true, platform_progress, last_pushed_at },platform_progress 即平台最新 progress JSON
550
+ const platformProgress = res.body && res.body.platform_progress ? res.body.platform_progress : res.body;
551
+ const platformLastPushedAt = (res.body && res.body.last_pushed_at) || null;
552
+
553
+ // 自竞态自愈:fresh 重读本机 DB base_ts,若已被并发进程回填到 ≥ 平台 409 回执 ts,
554
+ // 说明赢者是本机自己人——刷新 base_ts 重试一次即收敛,不落冲突文件(外来推送不可能
555
+ // 推进本机 DB 的 base_ts,不满足条件自然落回下方真冲突分支,零误放行)
556
+ if (attempt < MAX_PUSH_ATTEMPTS && await this._localBaseTsCovers(changeName, platformLastPushedAt)) {
557
+ console.warn(`⚠️ [sync] push 409 自竞态判定:本机并发推送已回填 base_ts(平台 ts=${platformLastPushedAt}),刷新后自动重试`);
558
+ continue;
559
+ }
560
+
561
+ // push 侧内容一致自愈(坑 push-409-foreign-noise,2026-08-23 实证:他机/部署重推了内容
562
+ // 相同的进度——pull 侧已有 _progressContentEquals 自愈(坑 pull-deploy-noise-conflict),
563
+ // push 409 路径没有:外来噪声形态落真冲突 + 横幅 + 人工 resolve,而内容其实零分歧)。
564
+ // 409 回执的 platform_progress 与本次待推内容过同一比对(忽略时间戳列),一致 → 平台
565
+ // 「更新」只是噪声重推,本地无可丢失 → 跳过本次推送 + base_ts 推进到平台 ts(与
566
+ // resolve --keep-local 同语义,自动闭环),不落冲突文件。内容不同 → 真分歧,维持原判。
567
+ try {
568
+ if (platformProgress && this._progressContentEquals(progressData, platformProgress)) {
569
+ try {
570
+ const { ProgressManager } = await import('./progress.js');
571
+ const pm = new ProgressManager({ specDir: safePlatformSpecDir(this.cwd) });
572
+ pm._ensureDB(this.cwd).getDb().prepare(
573
+ 'UPDATE changes SET last_synced_platform_ts = MAX(?, COALESCE(last_synced_platform_ts, ?)) WHERE name = ?'
574
+ ).run(platformLastPushedAt || pushedAt, platformLastPushedAt || pushedAt, changeName);
575
+ } catch {}
576
+ console.warn(`⚠️ [sync] push 409 内容一致自愈: 平台 ts=${platformLastPushedAt} 为外来噪声重推(六表内容与本地一致),base_ts 已推进,不落冲突文件`);
577
+ return { synced: 1, errors: [], selfHealed: true, reason: 'push 409 平台内容与本地一致(外来噪声重推),base_ts 已推进' };
578
+ }
579
+ } catch { /* 比对失败维持原判(fail-closed 到真冲突分支) */ }
580
+
581
+ // resolve --keep-local 的自动重推再撞 409:不落新冲突文件、不打「已卡死」横幅——
582
+ // 原 conflict 已按用户裁决处理完(base_ts 已推进),此刻的 409 只是「平台在用户裁决期间
583
+ // 又动了」;立即落新文件会破坏「keep-local 清冲突文件回 clean」的生命周期契约(冲突文件
584
+ // 代表待人工三选一的未决状态,而用户刚做完选择)。下次常规同步会按新 base 重新判定,
585
+ // 真有新分歧自然再进 conflict 态。
586
+ if (fromResolve) {
587
+ console.warn(`⚠️ [sync] keep-local 自动重推被拒(平台又有更新,ts=${platformLastPushedAt});base_ts 已推进,下次常规同步将重新判定`);
588
+ return { synced: 0, errors: [`keep-local 自动重推被拒: ${changeName}`], conflict: true, platform_progress: platformProgress, suppressedConflictFile: true };
589
+ }
590
+
591
+ // 醒目冲突横幅(坑 2026-08-19-platform-sync-base-ts-silent-conflict 改进点 2):
592
+ // 此前仅单行 warn,冲突文件静默落 .runtime——同步已卡死但 Agent/用户无从察觉,
593
+ // 平台页面永久停在旧阶段。横幅明确告知「已卡死 + 卡死原因 + 恢复命令」,不能只落文件。
594
+ console.warn('');
595
+ console.warn('⚠️⚠️⚠️════════════════════════════════════════════════════');
596
+ console.warn(`⚠️ 平台同步冲突:变更「${changeName}」推送被拒(base_ts 过期,平台已有更新)`);
597
+ console.warn('⚠️ 该变更的自动同步已暂停,等待人工 resolve 恢复(期间不再重复刷本横幅,后续自动同步单行提示):');
598
+ console.warn(`⚠️ 1. sillyspec platform status # 查看未决冲突`);
599
+ console.warn(`⚠️ 2. sillyspec platform resolve ${changeName} --keep-local | --take-platform | --abort`);
600
+ console.warn(`⚠️ 3. sillyspec platform sync --change ${changeName}`);
601
+ console.warn('⚠️⚠️⚠️════════════════════════════════════════════════════');
602
+ // 本地脏度(progressData 是 serializeForSync 输出,changes[0] 含 last_local_modified_ts)
603
+ const localModified = (progressData.changes && progressData.changes[0] && progressData.changes[0].last_local_modified_ts) || null;
604
+ // 写冲突文件(task-12 / D-002):base_ts=本次 push 的 base,强制提示走 resolve
605
+ const conflictPath = this._writeConflictFile(changeName, {
606
+ base_ts: baseTs || null,
607
+ local_modified_ts: localModified,
608
+ platform_last_pushed_at: platformLastPushedAt,
609
+ platform_progress: platformProgress,
610
+ });
611
+ console.warn(`⚠️ 冲突详情已落盘: ${conflictPath}`);
612
+ return { synced: 0, errors: [`冲突: ${changeName}`], conflict: true, platform_progress: platformProgress, conflictPath };
613
+ }
614
+
615
+ // 循环耗尽(自愈重试后仍 409——两次都撞且第二次非自竞态;理论上第二次会落上方真冲突分支返回,
616
+ // 此行只作结构兜底)
617
+ return { synced: 0, errors: [`同步重试耗尽(仍冲突): ${changeName}`] };
618
+ }
619
+
620
+ /**
621
+ * 自竞态判定辅助:fresh 读本地 DB 的 last_synced_platform_ts(base_ts),判断它是否已覆盖
622
+ * 平台 409 回执的 last_pushed_at(字符串字典序比较,契约 §4.2——ISO 8601 字典序 == 时间序)。
623
+ * 已覆盖 ⇒ 打赢 409 的推送来自本机并发进程(其成功回填写的就是这个共享 DB)。
624
+ * @param {string} changeName
625
+ * @param {string|null} platformTs 409 回执 last_pushed_at
626
+ * @returns {Promise<boolean>}
627
+ */
628
+ async _localBaseTsCovers(changeName, platformTs) {
629
+ if (!platformTs) return false;
630
+ try {
631
+ const { ProgressManager } = await import('./progress.js');
632
+ const pm = new ProgressManager({ specDir: safePlatformSpecDir(this.cwd) });
633
+ const row = pm._ensureDB(this.cwd).getDb().prepare(
634
+ 'SELECT last_synced_platform_ts FROM changes WHERE name = ?'
635
+ ).get(changeName);
636
+ const localTs = row && row.last_synced_platform_ts;
637
+ if (!localTs) return false;
638
+ return localTs >= platformTs;
639
+ } catch {
640
+ return false; // 读失败按非自竞态处理(fail-closed 到真冲突分支)
641
+ }
642
+ }
643
+
644
+ /**
645
+ * 同步四件套文档到平台(全量同步)。
646
+ * POST {url}/api/changes/{changeName}/documents
647
+ * @param {string} changeName
648
+ * @param {{manual?: boolean}} [opts] - manual=true 手动 sync-docs 命令路径(四件套缺失打 warn);
649
+ * 默认自动路径(sync() 顺带推)四件套缺失是流程早期正常状态,降 debug 不噪音(ql-20260818-008)
650
+ */
651
+ async syncDocuments(changeName, opts = {}) {
652
+ const { manual = false } = opts;
653
+ const platform = this._getPlatform();
654
+ if (!platform) {
655
+ debugLog('[sync] 未连接平台(本地合法状态);如需平台同步:sillyspec platform connect');
656
+ return { synced: 0, errors: ['未连接平台'] };
657
+ }
658
+
659
+ if (!changeName) {
660
+ console.warn('[sync] syncDocuments 需要指定变更名称 (changeName)');
661
+ return { synced: 0, errors: ['未指定变更名称'] };
662
+ }
663
+
664
+ // 树根与进度/spec 树同源(safePlatformSpecDir,BUG-01 同族):此前硬编码 cwd/.sillyspec/changes,
665
+ // 平台模式(specRoot 指向外部)下路径不存在 → syncDocuments 恒报「变更不存在」且调用方不查
666
+ // 返回值 → 文档在平台模式下静默永不同步。
667
+ const changeDir = join(safePlatformSpecDir(this.cwd) || join(this.cwd, '.sillyspec'), 'changes', changeName);
668
+ if (!existsSync(changeDir)) {
669
+ // 归档尾声静默化(坑 post-archive-sync-noise):sync() 链内调用(_suppressDocsMissingWarn
670
+ // 置位,目录已移 archive/ 的正常时序)降为 debug;独立调用(手动 sync-docs)保留 warn
671
+ if (this._suppressDocsMissingWarn) {
672
+ debugLog(`[sync] 四件套目录不存在(变更已归档,跳过文档直推): ${changeName}`);
673
+ return { synced: 0, errors: [] };
674
+ }
675
+ console.warn(`[sync] 变更不存在: ${changeName}`);
676
+ return { synced: 0, errors: [`变更不存在: ${changeName}`] };
677
+ }
678
+
679
+ const documents = {};
680
+ let syncedCount = 0;
681
+ const errors = [];
682
+
683
+ for (const docFile of DOCUMENT_FILES) {
684
+ const docPath = join(changeDir, docFile);
685
+ if (existsSync(docPath)) {
686
+ try {
687
+ documents[docFile] = readFileSync(docPath, 'utf8');
688
+ syncedCount++;
689
+ } catch (err) {
690
+ errors.push(`读取 ${docFile} 失败: ${err.message}`);
691
+ }
692
+ }
693
+ }
694
+
695
+ if (syncedCount === 0) {
696
+ // ql-20260818-008:四件套缺失在流程早期是正常状态(brainstorm 中途 design/proposal 还没写),
697
+ // 自动路径降 debug——原 warn「未找到可同步的文档」不说只找四件套,曾让用户误判同步逻辑错了;
698
+ // 手动 platform sync-docs 是显式意图,保留 warn 但说清范围。
699
+ if (manual) {
700
+ console.warn(`[sync] 未找到可同步的四件套文档(proposal/design/requirements/tasks.md): ${changeName}`);
701
+ } else {
702
+ debugLog(`[sync] 四件套尚未生成,跳过文档直推: ${changeName}`);
703
+ }
704
+ return { synced: 0, errors: [...errors, '无可用文档'] };
705
+ }
706
+
707
+ const docUrl = `${platform.url}/api/changes/${encodeURIComponent(changeName)}/documents`;
708
+ const result = await fetchJson(docUrl, {
709
+ method: 'POST',
710
+ headers: {
711
+ 'Content-Type': 'application/json',
712
+ Authorization: `Bearer ${platform.token}`,
713
+ },
714
+ body: JSON.stringify(documents),
715
+ signal: opts.signal, // HUB-09:熔断 abort 传到底层请求
716
+ });
717
+
718
+ if (!result) {
719
+ return { synced: 0, errors: [...errors, '文档同步请求失败'] };
720
+ }
721
+
722
+ console.log(`[sync] 已同步 ${syncedCount} 个文档: ${changeName}`);
723
+ return { synced: syncedCount, errors };
724
+ }
725
+
726
+ /**
727
+ * 检查变更的审批状态。
728
+ * GET {url}/api/changes/{changeName}/approval
729
+ * 返回 { status: 'pending'|'approved'|'rejected', reason?: string }
730
+ */
731
+ async checkApproval(changeName) {
732
+ const platform = this._getPlatform();
733
+ if (!platform) {
734
+ debugLog('[sync] 未连接平台(本地合法状态);如需平台同步:sillyspec platform connect');
735
+ return { status: 'pending', reason: '未连接平台' };
736
+ }
737
+
738
+ if (!changeName) {
739
+ console.warn('[sync] checkApproval 需要指定变更名称 (changeName)');
740
+ return { status: 'pending', reason: '未指定变更名称' };
741
+ }
742
+
743
+ const approvalUrl = `${platform.url}/api/changes/${encodeURIComponent(changeName)}/approval`;
744
+ const result = await fetchJson(approvalUrl, {
745
+ headers: { Authorization: `Bearer ${platform.token}` },
746
+ });
747
+
748
+ if (!result) {
749
+ console.warn(`[sync] 检查审批状态失败: ${changeName}`);
750
+ return { status: 'unknown', reason: '请求失败(404/断网/超时),无法核实审批状态' };
751
+ }
752
+
753
+ // 更新本地 approvals 表
754
+ try {
755
+ const { ProgressManager } = await import('./progress.js');
756
+ const pm = new ProgressManager({ specDir: safePlatformSpecDir(this.cwd) });
757
+ pm._updateApprovalStatus(this.cwd, changeName, result.status, result.reason);
758
+ } catch (err) {
759
+ console.warn(`[sync] 更新本地审批状态失败: ${err.message}`);
760
+ }
761
+
762
+ if (result.status === 'rejected') {
763
+ console.warn(`[sync] 审批被拒绝 (${changeName}): ${result.reason || '无原因'}`);
764
+ }
765
+
766
+ return result;
767
+ }
768
+
769
+ /**
770
+ * 查看同步状态。
771
+ * 读取 local.yaml 中的 platform 配置,返回连接信息。
772
+ */
773
+ status() {
774
+ const config = readLocalYaml(this.cwd);
775
+ const platform = config.platform;
776
+ if (!platform) {
777
+ return { connected: false };
778
+ }
779
+ return {
780
+ connected: true,
781
+ url: platform.url,
782
+ lastSync: platform.last_connected || null,
783
+ };
784
+ }
785
+
786
+ /** 获取当前平台配置,未连接返回 null */
787
+ _getPlatform() {
788
+ const config = readLocalYaml(this.cwd);
789
+ return config.platform || null;
790
+ }
791
+
792
+ /**
793
+ * DB 侧归档态探测(坑 post-archive-sync-noise):changes 表 status='archived' 即真。
794
+ * 只读直查(node:sqlite,不经 ProgressManager——sync 是短进程,惰性 import 无收益且
795
+ * 本调用点在同步代码段);DB 不存在/无行/读失败 → false(保守退回普通 warn 措辞)。
796
+ * @param {string} changeName
797
+ * @returns {boolean}
798
+ */
799
+ _isChangeArchivedInDb(changeName) {
800
+ try {
801
+ const dbPath = join(safePlatformSpecDir(this.cwd) || join(this.cwd, '.sillyspec'), '.runtime', 'sillyspec.db');
802
+ if (!existsSync(dbPath)) return false;
803
+ const db = openDatabase(dbPath, { readOnly: true });
804
+ try {
805
+ const row = db.prepare("SELECT status FROM changes WHERE name = ?").get(changeName);
806
+ return row?.status === 'archived';
807
+ } finally { try { db.close() } catch {} }
808
+ } catch { return false }
809
+ }
810
+
811
+ /**
812
+ * 写冲突文件 .runtime/sync-conflict-<change>.json(task-12 / D-002 / D-008 / D-010 / FR-05)。
813
+ * push 409 与 pull 本地脏度双向冲突命中时调用,强制提示用户走 platform resolve 三选一。
814
+ * 绝不字段级 auto-merge;文件在 .runtime 下不入版本控制(gitignore)。
815
+ * @param {string} changeName
816
+ * @param {{base_ts?: string|null, local_modified_ts?: string|null, platform_last_pushed_at?: string|null, platform_progress?: object|null}} info
817
+ * @returns {string|null} 冲突文件路径(specDir 不可达返回 null)
818
+ */
819
+ /**
820
+ * 六表内容比对(坑 pull-deploy-noise-conflict 自愈用):本地 serializeForSync 快照与平台
821
+ * 拉回的 JSON 逐表深度比对,忽略时间戳/同步元数据列(last_active/last_synced_platform_ts/
822
+ * last_local_modified_ts/started_at/completed_at/pushed_at/checked_at/completed_at 等——部署
823
+ * 噪声重推会刷新这些列但内容不变)。任一实质差异 → false。
824
+ * @param {object} local - serializeForSync 输出
825
+ * @param {object} platform - 平台 GET progress JSON(serializeForSync 同构)
826
+ * @returns {boolean}
827
+ */
828
+ _progressContentEquals(local, platform) {
829
+ try {
830
+ const IGNORE_KEYS = new Set([
831
+ 'last_active', 'last_synced_platform_ts', 'last_local_modified_ts',
832
+ 'started_at', 'completed_at', 'pushed_at', 'last_pushed_at', 'created_at',
833
+ 'deps_checked_at', 'checked_at', 'waited_at', 'completedat',
834
+ ]);
835
+ const strip = (v) => {
836
+ if (Array.isArray(v)) return v.map(strip);
837
+ if (v && typeof v === 'object') {
838
+ const out = {};
839
+ // sort 必须按 key 字符串比较(坑 content-equals-sort-typeerror,2026-08-23 实证:
840
+ // 默认比较器把 [key, value] 元素转字符串,serializeForSync 输出的 project 字段是
841
+ // null-prototype 对象(不可转原始值)→ sort 抛 TypeError 被下方 catch 吞成恒 false
842
+ // ——pull/push 两侧内容一致自愈形同虚设,部署噪声全部落真冲突人工 resolve)。
843
+ const entries = Object.entries(v).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
844
+ for (const [k, val] of entries) {
845
+ if (IGNORE_KEYS.has(k)) continue;
846
+ out[k] = strip(val);
847
+ }
848
+ return out;
849
+ }
850
+ return v;
851
+ };
852
+ return JSON.stringify(strip(local)) === JSON.stringify(strip(platform));
853
+ } catch { return false }
854
+ }
855
+
856
+ _writeConflictFile(changeName, info = {}) {
857
+ const specDir = safePlatformSpecDir(this.cwd);
858
+ if (!specDir) return null;
859
+ const runtimeDir = join(specDir, '.runtime');
860
+ if (!existsSync(runtimeDir)) mkdirSync(runtimeDir, { recursive: true });
861
+ const conflictPath = join(runtimeDir, `sync-conflict-${changeName}.json`);
862
+ const payload = {
863
+ change: changeName,
864
+ base_ts: info.base_ts ?? null,
865
+ local_modified_ts: info.local_modified_ts ?? null,
866
+ platform_last_pushed_at: info.platform_last_pushed_at ?? null,
867
+ created_at: new Date().toISOString(),
868
+ // 额外存平台最新 progress JSON,供 resolve --take-platform 直接 import(无需再 pull)
869
+ platform_progress: info.platform_progress ?? null,
870
+ };
871
+ writeFileSync(conflictPath, JSON.stringify(payload, null, 2) + '\n', 'utf8');
872
+ return conflictPath;
873
+ }
874
+
875
+ /**
876
+ * 读取冲突文件(task-13 resolve / task-14 status 用)。
877
+ * @returns {object|null} 冲突文件内容;无文件返回 null
878
+ */
879
+ readConflictFile(changeName) {
880
+ const specDir = safePlatformSpecDir(this.cwd);
881
+ if (!specDir) return null;
882
+ const conflictPath = join(specDir, '.runtime', `sync-conflict-${changeName}.json`);
883
+ if (!existsSync(conflictPath)) return null;
884
+ try {
885
+ return JSON.parse(readFileSync(conflictPath, 'utf8'));
886
+ } catch {
887
+ return null;
888
+ }
889
+ }
890
+
891
+ // ── HUB-08:spec 树冲突文件(spec-sync-conflict-<change>.json,由 spec-sync.js 写入)──
892
+
893
+ /** 读取 spec 树冲突文件;无/损坏返回 null */
894
+ readSpecConflictFile(changeName) {
895
+ const specDir = safePlatformSpecDir(this.cwd);
896
+ if (!specDir) return null;
897
+ const p = join(specDir, '.runtime', `spec-sync-conflict-${changeName}.json`);
898
+ if (!existsSync(p)) return null;
899
+ try {
900
+ return JSON.parse(readFileSync(p, 'utf8'));
901
+ } catch {
902
+ return null;
903
+ }
904
+ }
905
+
906
+ /** 删除 spec 树冲突文件 */
907
+ clearSpecConflictFile(changeName) {
908
+ const specDir = safePlatformSpecDir(this.cwd);
909
+ if (!specDir) return false;
910
+ const p = join(specDir, '.runtime', `spec-sync-conflict-${changeName}.json`);
911
+ if (!existsSync(p)) return false;
912
+ try { unlinkSync(p); return true; }
913
+ catch { return false; }
914
+ }
915
+
916
+ /**
917
+ * 删除冲突文件(resolve 完成后清理,task-13 用)。
918
+ * @returns {boolean} 是否删除(文件不存在返回 false)
919
+ */
920
+ clearConflictFile(changeName) {
921
+ const specDir = safePlatformSpecDir(this.cwd);
922
+ if (!specDir) return false;
923
+ const conflictPath = join(specDir, '.runtime', `sync-conflict-${changeName}.json`);
924
+ if (!existsSync(conflictPath)) return false;
925
+ try { unlinkSync(conflictPath); return true; }
926
+ catch { return false; }
927
+ }
928
+
929
+ /**
930
+ * 两级 pull 第一级:轻量 change 列表(design §7 / D-001 / D-006 / task-06 / FR-01 / FR-03)。
931
+ * GET {url}/api/changes → [{ name, current_stage, last_pushed_at, last_pusher }](兼容 { changes: [...] } 包裹)。
932
+ * CLI 比对本地决定哪些 change 需更新,控制 pull 性能(不每次全量拉单 change JSON)。
933
+ * Best Effort:未连接 / 网络失败 console.warn 不抛,返回 PullListResult { ok, changes, reason? }。
934
+ * @returns {Promise<{ok: boolean, changes: Array<object>, reason?: string}>}
935
+ */
936
+ async pullList() {
937
+ const platform = this._getPlatform();
938
+ if (!platform) {
939
+ debugLog('[sync] 未连接平台(本地合法状态);pullList 跳过');
940
+ return { ok: false, changes: [], reason: '未连接平台' };
941
+ }
942
+
943
+ const listUrl = `${platform.url}/api/changes`;
944
+ const result = await fetchJson(listUrl, {
945
+ headers: { Authorization: `Bearer ${platform.token}` },
946
+ });
947
+
948
+ if (result === null) {
949
+ console.warn(`[sync] 拉取变更列表失败: ${listUrl}`);
950
+ return { ok: false, changes: [], reason: '拉取变更列表失败' };
951
+ }
952
+
953
+ // 兼容两种响应形态:裸数组 / { changes: [...] } 包裹(sillyhub 后端实际 API 而定)
954
+ const changes = Array.isArray(result) ? result : (Array.isArray(result.changes) ? result.changes : []);
955
+ return { ok: true, changes, reason: undefined };
956
+ }
957
+
958
+ /**
959
+ * 两级 pull 第二级:按需拉平台单 change 完整 JSON 并 import 重建本地 DB 行(design §7 / D-001 / D-006 / D-014 / task-07 / FR-01 / FR-03 / FR-09)。
960
+ * GET {url}/api/changes/{name}/progress → 平台权威 JSON(serializeForSync 六表 + 顶层 last_pushed_at)。
961
+ * 本地脏度比对:last_local_modified_ts > last_synced_platform_ts(本地有未同步改动)且 平台 last_pushed_at 更新 → 冲突,不 import 返回 conflict:true(task-12 负责写 sync-conflict 文件)。
962
+ * 无冲突 → import() 重建 DB 行;import 后本地 base_ts/脏度重置为 pushed_at(D-013)。
963
+ * Best Effort:未连接 / 网络失败 / 404 console.warn 不抛,返回 PullResult { ok, imported, conflict, reason? }。
964
+ * @param {string} changeName
965
+ * @param {{force?: boolean, skipIfLocalDirty?: boolean}} [opts] - force 跳过本地脏度冲突检测直接
966
+ * import(task-12 resolve --take-platform 用);skipIfLocalDirty 自动注入点(triggerPull/
967
+ * triggerPullActiveChange)专用保守守卫——本地有未同步改动时跳过 import(ql-20260818-008)
968
+ * @returns {Promise<{ok: boolean, imported: boolean, conflict: boolean, reason?: string}>}
969
+ */
970
+ async pull(changeName, opts = {}) {
971
+ const { force = false, skipIfLocalDirty = false } = opts;
972
+ const platform = this._getPlatform();
973
+ if (!platform) {
974
+ debugLog('[sync] 未连接平台(本地合法状态);pull 跳过');
975
+ return { ok: false, imported: false, conflict: false, reason: '未连接平台' };
976
+ }
977
+ if (!changeName) {
978
+ console.warn('[sync] pull 需要指定变更名称 (changeName)');
979
+ return { ok: false, imported: false, conflict: false, reason: '未指定变更名称' };
980
+ }
981
+
982
+ // 1. GET 平台完整 progress JSON
983
+ const progressUrl = `${platform.url}/api/changes/${encodeURIComponent(changeName)}/progress`;
984
+ const result = await fetchJson(progressUrl, {
985
+ headers: { Authorization: `Bearer ${platform.token}` },
986
+ signal: opts.signal, // HUB-09:熔断 abort 传到底层请求
987
+ });
988
+ if (result === null) {
989
+ // sillyhub 未就绪 / 404 / 网络失败均 fetchJson 返回 null → Best Effort 降级
990
+ console.warn(`[sync] 拉取变更进度失败: ${changeName}(sillyhub 未就绪或变更不存在)`);
991
+ return { ok: false, imported: false, conflict: false, reason: '拉取变更进度失败' };
992
+ }
993
+
994
+ // 平台响应:serializeForSync 六表 + 顶层 last_pushed_at(兼容 { progress: {...} } 包裹)
995
+ const platformProgress = (result && result.project && result.changes) ? result : (result.progress || result);
996
+ const platformPushedAt = (result && result.last_pushed_at) || null;
997
+
998
+ // 2. 本地脏度比对(非 force 时):本地脏 AND 平台更新 → 冲突
999
+ if (!force) {
1000
+ let localLastModified = null;
1001
+ let localLastSynced = null;
1002
+ try {
1003
+ const { ProgressManager } = await import('./progress.js');
1004
+ const pm = new ProgressManager({ specDir: safePlatformSpecDir(this.cwd) });
1005
+ const row = pm._ensureDB(this.cwd).getDb().prepare(
1006
+ 'SELECT last_local_modified_ts, last_synced_platform_ts FROM changes WHERE name = ?'
1007
+ ).get(changeName);
1008
+ if (row) {
1009
+ localLastModified = row.last_local_modified_ts;
1010
+ localLastSynced = row.last_synced_platform_ts;
1011
+ }
1012
+ } catch (err) {
1013
+ console.warn(`[sync] 读取本地脏度失败 (${changeName}): ${err.message}`);
1014
+ }
1015
+ let localDirty = localLastModified && localLastSynced && localLastModified > localLastSynced;
1016
+ let platformNewer = platformPushedAt && (!localLastSynced || platformPushedAt > localLastSynced);
1017
+ // 自竞态防御(坑 2026-08-19-platform-sync-base-ts-silent-conflict 改进点 3):
1018
+ // 判冲突用的 localLastSynced 若落后于本进程刚完成的 push 回填(另一连接 3ms 前推完、
1019
+ // 本 pull 在其回填写库前读了旧值),会把自己刚写的 platformPushedAt 误判为「平台有更新」
1020
+ // → 卡死。重读一次 DB:期间 push 回填已落库则 base_ts 已推进(>= platformPushedAt),
1021
+ // platformNewer 翻 false,冲突自愈。重读仍旧则真冲突,维持原判。
1022
+ if (localDirty && platformNewer) {
1023
+ try {
1024
+ const { ProgressManager } = await import('./progress.js');
1025
+ const pm = new ProgressManager({ specDir: safePlatformSpecDir(this.cwd) });
1026
+ const fresh = pm._ensureDB(this.cwd).getDb().prepare(
1027
+ 'SELECT last_synced_platform_ts FROM changes WHERE name = ?'
1028
+ ).get(changeName);
1029
+ if (fresh && fresh.last_synced_platform_ts) {
1030
+ localLastSynced = fresh.last_synced_platform_ts;
1031
+ platformNewer = platformPushedAt && platformPushedAt > localLastSynced;
1032
+ if (!platformNewer) {
1033
+ debugLog(`[sync] pull 自竞态解除: base_ts 已被本进程 push 回填推进到 ${localLastSynced},平台 ts ${platformPushedAt} 非更新`);
1034
+ }
1035
+ }
1036
+ } catch { /* 重读失败维持原判(fail-closed 到真冲突分支) */ }
1037
+ }
1038
+ // ── 内容一致自愈(坑 pull-deploy-noise-conflict,2026-08-22 实证:并行会话部署扰动——
1039
+ // 他机 push 了内容相同的进度(如重启/重部署触发重推),本地脏 + 平台 ts 更新 → 拉冲突落
1040
+ // sync-conflict 文件需人工 resolve,但内容其实无分歧。比对平台 JSON 与本地 serializeForSync
1041
+ // 的六表内容(忽略时间戳列),一致 → 跳过 import(本地为准)+ base_ts 推进到平台 ts——
1042
+ // 与 resolve --keep-local 同语义,自动闭环。内容不同 → 真冲突,维持原判。
1043
+ if (localDirty && platformNewer && platformProgress) {
1044
+ try {
1045
+ const { ProgressManager } = await import('./progress.js');
1046
+ const pm = new ProgressManager({ specDir: safePlatformSpecDir(this.cwd) });
1047
+ const localSnapshot = pm.serializeForSync(this.cwd, changeName);
1048
+ if (localSnapshot && this._progressContentEquals(localSnapshot, platformProgress)) {
1049
+ try {
1050
+ const db = pm._ensureDB(this.cwd).getDb();
1051
+ db.prepare(
1052
+ 'UPDATE changes SET last_synced_platform_ts = MAX(?, COALESCE(last_synced_platform_ts, ?)) WHERE name = ?'
1053
+ ).run(platformPushedAt, platformPushedAt, changeName);
1054
+ } catch {}
1055
+ debugLog(`[sync] pull 内容一致自愈: 平台 ts ${platformPushedAt} 为部署噪声重推(六表内容与本地一致),base_ts 已推进,不落冲突文件`);
1056
+ return { ok: true, imported: false, conflict: false, reason: '平台重推内容与本地一致(部署噪声),base_ts 已推进' };
1057
+ }
1058
+ } catch { /* 比对失败维持原判(fail-closed 到真冲突分支) */ }
1059
+ }
1060
+ if (localDirty && platformNewer) {
1061
+ console.warn('');
1062
+ console.warn('⚠️⚠️⚠️════════════════════════════════════════════════════');
1063
+ console.warn(`⚠️ 平台同步冲突:变更「${changeName}」pull 判定冲突(本地有未同步改动且平台已更新,base_ts 过期)`);
1064
+ console.warn('⚠️ 该变更的自动同步已暂停,等待人工 resolve 恢复(推送侧后续自动同步单行提示,不再重复刷本横幅):');
1065
+ console.warn(`⚠️ 1. sillyspec platform status # 查看未决冲突`);
1066
+ console.warn(`⚠️ 2. sillyspec platform resolve ${changeName} --keep-local | --take-platform | --abort`);
1067
+ console.warn(`⚠️ 3. sillyspec platform sync --change ${changeName}`);
1068
+ console.warn('⚠️⚠️⚠️════════════════════════════════════════════════════');
1069
+ // 写冲突文件(task-12 / D-002):强制提示,绝不 auto-merge
1070
+ const conflictPath = this._writeConflictFile(changeName, {
1071
+ base_ts: localLastSynced,
1072
+ local_modified_ts: localLastModified,
1073
+ platform_last_pushed_at: platformPushedAt,
1074
+ platform_progress: platformProgress,
1075
+ });
1076
+ console.warn(`⚠️ 冲突详情已落盘: ${conflictPath}`); // HUB-12d:真实落点(平台模式在 specRoot/.runtime),勿硬编码
1077
+ return { ok: false, imported: false, conflict: true, reason: `冲突: ${changeName} 本地脏且平台更新`, conflictPath };
1078
+ }
1079
+ // ql-20260818-008:自动注入点(triggerPull/triggerPullActiveChange)保守守卫——本地有未同步
1080
+ // 改动时跳过 import。原逻辑只拦「本地脏 且 平台更新」的真冲突,「本地领先」(本地脏、平台更旧)
1081
+ // 仍会 import 平台旧快照覆盖本地新进度(数据丢失)。手动 platform pull 不传该 flag,语义不变。
1082
+ if (skipIfLocalDirty && localDirty) {
1083
+ debugLog(`[sync] pull 跳过(本地有未同步改动,防平台旧快照覆盖): ${changeName}`);
1084
+ return { ok: false, imported: false, conflict: false, reason: '本地有未同步改动,自动 pull 跳过' };
1085
+ }
1086
+ }
1087
+
1088
+ // 3. 无冲突(或 force)→ import 平台 JSON 重建本地 DB 行
1089
+ try {
1090
+ const { ProgressManager } = await import('./progress.js');
1091
+ const pm = new ProgressManager({ specDir: safePlatformSpecDir(this.cwd) });
1092
+ const importInput = { ...platformProgress, pushed_at: platformPushedAt || new Date().toISOString() };
1093
+ pm.import(this.cwd, importInput, changeName);
1094
+ console.log(`[sync] 已拉取变更进度: ${changeName}`);
1095
+ return { ok: true, imported: true, conflict: false, reason: undefined };
1096
+ } catch (err) {
1097
+ console.warn(`[sync] import 失败 (${changeName}): ${err.message}`);
1098
+ return { ok: false, imported: false, conflict: false, reason: `import 失败: ${err.message}` };
1099
+ }
1100
+ }
1101
+
1102
+ /**
1103
+ * 冲突解决三选一(task-13 / D-002 / D-010 / D-013 / FR-05)。
1104
+ * 读 task-12 写的 sync-conflict-<change>.json,按 mode 处理后必清冲突文件防累积(R-04 / constraints)。
1105
+ *
1106
+ * 三种语义(design 生命周期契约表):
1107
+ * - keep-local:本地 DB 不变,仅把 last_synced_platform_ts(base_ts)推进到平台 last_pushed_at
1108
+ * (表示「已知平台最新,本地为准」),用户后续手动 platform sync push 本地。
1109
+ * - take-platform:用冲突文件存的 platform_progress 调 import() 覆盖本地(保隔离状态,D-013 重置脏度)。
1110
+ * - abort:本地 DB 与 base_ts 均不变,仅清冲突文件(放弃本次同步)。
1111
+ *
1112
+ * 无冲突文件 → ok:false resolved:false(index.js 据此提示「无可解决冲突」)。
1113
+ * 绝不字段级 auto-merge(D-002)。
1114
+ * @param {string} changeName
1115
+ * @param {'keep-local'|'take-platform'|'abort'} mode
1116
+ * @returns {Promise<{ok: boolean, resolved: boolean, mode?: string, reason: string}>}
1117
+ */
1118
+ async resolve(changeName, mode) {
1119
+ const cf = this.readConflictFile(changeName);
1120
+ const specCf = this.readSpecConflictFile(changeName);
1121
+ if (!cf && !specCf) {
1122
+ return { ok: false, resolved: false, reason: `无可解决冲突: ${changeName}(无 sync-conflict / spec-sync-conflict 文件)` };
1123
+ }
1124
+ const platformPushedAt = (cf && cf.platform_last_pushed_at) || null;
1125
+
1126
+ // HUB-08:resolve 同时管辖进度冲突(sync-conflict-*)与 spec 树冲突(spec-sync-conflict-*),
1127
+ // 各自产出 outcome 后合并返回;abort 统一清两类标记。
1128
+ let progressOutcome = null;
1129
+ if (mode === 'keep-local' && cf) {
1130
+ // base_ts 推进到平台最新 last_pushed_at;本地 DB 不 import(用户本地为准,后续手动 push)。
1131
+ // MAX() 单调防回退(坑 2026-08-19-resolve-keep-local-base-ts-rollback):冲突文件是历史快照,
1132
+ // 其 platform_last_pushed_at 可能早于 DB 已由后续成功 push 回填的 base_ts——无条件覆盖会把
1133
+ // base_ts 拉回过去,下次 sync 立即撞 409 再落冲突文件(恢复时实测二轮才收敛)。
1134
+ // COALESCE 防 NULL:SQLite 标量 MAX(x, NULL) 恒 NULL,首同步前 base_ts NULL 应直取平台 ts。
1135
+ try {
1136
+ const { ProgressManager } = await import('./progress.js');
1137
+ const pm = new ProgressManager({ specDir: safePlatformSpecDir(this.cwd) });
1138
+ const db = pm._ensureDB(this.cwd).getDb();
1139
+ // 参数侧同样 COALESCE(坑 keep-local-base-ts-null-param):SQLite 标量 MAX() 任一参数
1140
+ // 为 NULL 即返回 NULL——冲突文件 platform_last_pushed_at 为 null 时 MAX(NULL, 旧值)
1141
+ // 会把已回填的 base_ts 清空,恰好违背本注释的单调防回退意图。
1142
+ db.prepare('UPDATE changes SET last_synced_platform_ts = MAX(COALESCE(?, last_synced_platform_ts), COALESCE(last_synced_platform_ts, ?)) WHERE name = ?')
1143
+ .run(platformPushedAt, platformPushedAt, changeName);
1144
+ } catch (err) {
1145
+ return { ok: false, resolved: false, reason: `keep-local 更新 base_ts 失败: ${err.message}` };
1146
+ }
1147
+ this.clearConflictFile(changeName);
1148
+ // 自动重推闭环(坑 2026-08-19-platform-sync-base-ts-silent-conflict 收尾侧):keep-local 语义 =
1149
+ // 本地为准,必然收尾是把本地推上平台。此前停在「后续请手动 platform sync」——忘了推,
1150
+ // 期间他人再推 → 下次自动同步又 409 落冲突文件(「冲突再现」的根源之一)。resolve 成功即
1151
+ // 自动 push 一次闭环;真撞上外来更新时软提示(fromResolve 抑制新冲突文件——冲突文件代表
1152
+ // 待人工三选一的未决状态,用户刚做完选择,下次常规同步按新 base 重新判定)。
1153
+ let repush = null;
1154
+ try {
1155
+ repush = await this.sync(changeName, { fromResolve: true });
1156
+ } catch (err) {
1157
+ debugLog(`[sync] keep-local 自动重推异常(不阻断 resolve 结果): ${changeName}: ${err.message}`);
1158
+ }
1159
+ if (repush && repush.synced === 1) {
1160
+ progressOutcome = { ok: true, resolved: true, reason: '保留本地,并已自动推送平台——冲突闭环,无需再手动 sync' };
1161
+ } else if (repush && repush.conflict) {
1162
+ progressOutcome = { ok: true, resolved: true, reason: '保留本地,base_ts 已推进;自动重推被拒(平台在裁决期间又有更新),下次常规同步将重新判定' };
1163
+ } else {
1164
+ progressOutcome = { ok: true, resolved: true, reason: '保留本地,base_ts 已推进;自动重推未成功(未连接/网络),请手动 sillyspec platform sync --change ' + changeName };
1165
+ }
1166
+ }
1167
+
1168
+ if (mode === 'take-platform' && cf) {
1169
+ // 用冲突文件的 platform_progress 调 import 覆盖本地(保隔离:import 不覆盖 isolation_*)。
1170
+ // fail-closed(坑 take-platform-empty-import):缺 platform_progress 时必须直接返回——
1171
+ // { ...null } 展开为 {},import 会照常跑完整事务(DELETE stages 后按空 JSON 重建 =
1172
+ // 清空本地进度),返回值却报 ok:false「未执行」,用户以为无事发生实则数据已丢。
1173
+ if (!cf.platform_progress) {
1174
+ return { ok: false, resolved: false, reason: '冲突文件缺 platform_progress,无法 take-platform(建议先 platform pull)' };
1175
+ }
1176
+ try {
1177
+ const { ProgressManager } = await import('./progress.js');
1178
+ const pm = new ProgressManager({ specDir: safePlatformSpecDir(this.cwd) });
1179
+ pm.import(this.cwd, { ...cf.platform_progress, pushed_at: platformPushedAt || new Date().toISOString() }, changeName);
1180
+ } catch (err) {
1181
+ progressOutcome = { ok: false, resolved: false, reason: `take-platform import 失败: ${err.message}` };
1182
+ }
1183
+ if (progressOutcome === null) {
1184
+ this.clearConflictFile(changeName);
1185
+ progressOutcome = { ok: true, resolved: true, reason: '已用平台进度覆盖本地' };
1186
+ }
1187
+ }
1188
+
1189
+ // ── spec 树冲突处置(HUB-08)──
1190
+ let specOutcome = null;
1191
+ if (specCf && mode === 'keep-local') {
1192
+ // keep-local = 本地胜出:重新 GET 清单重定 base 后 POST 本地内容(last-writer-wins),
1193
+ // 成功清冲突文件闭环;仍冲突(平台又有更新)保留文件待下次裁决
1194
+ try {
1195
+ const r = await syncSpecTree(safePlatformSpecDir(this.cwd) || join(this.cwd, '.sillyspec'), this._getPlatform(), changeName);
1196
+ if (!r.conflict) {
1197
+ this.clearSpecConflictFile(changeName);
1198
+ specOutcome = { ok: true, resolved: true, reason: 'spec 树已以本地为准重定基线重推,冲突闭环' };
1199
+ } else {
1200
+ specOutcome = { ok: true, resolved: false, reason: 'spec 树重推仍冲突(平台在裁决期间又有更新),冲突文件保留待下次裁决' };
1201
+ }
1202
+ } catch (err) {
1203
+ specOutcome = { ok: false, resolved: false, reason: `spec 树重推异常: ${err.message}` };
1204
+ }
1205
+ } else if (specCf && mode === 'take-platform') {
1206
+ // fail-closed:平台无文件下载端点,无法把服务器内容写回本地——明确报错指路,不清文件
1207
+ specOutcome = { ok: false, resolved: false, reason: 'spec 树冲突不支持 take-platform(平台无文件下载端点,无法把服务器内容写回本地):请手动对齐本地文件后跑 --keep-local' };
1208
+ }
1209
+
1210
+ // ── abort:两类标记一并清(本地 DB 与文件均不变,下次 push/pull 重新检测)──
1211
+ if (mode === 'abort') {
1212
+ if (cf) this.clearConflictFile(changeName);
1213
+ if (specCf) this.clearSpecConflictFile(changeName);
1214
+ return { ok: true, resolved: true, mode: 'abort', reason: '放弃本次同步,本地不变' };
1215
+ }
1216
+
1217
+ if (mode !== 'keep-local' && mode !== 'take-platform') {
1218
+ return { ok: false, resolved: false, reason: `未知 resolve 模式: ${mode}(--keep-local / --take-platform / --abort)` };
1219
+ }
1220
+
1221
+ // ── 合并两类冲突的处置结果 ──
1222
+ const outcomes = [progressOutcome, specOutcome].filter(Boolean);
1223
+ if (outcomes.length === 0) {
1224
+ return { ok: false, resolved: false, reason: `无可解决冲突: ${changeName}` };
1225
+ }
1226
+ return {
1227
+ ok: outcomes.every((o) => o.ok),
1228
+ resolved: outcomes.every((o) => o.resolved),
1229
+ mode,
1230
+ reason: outcomes.map((o) => o.reason).join(';'),
1231
+ };
1232
+ }
1233
+
1234
+ /**
1235
+ * 扫描 .runtime/sync-conflict-*.json 列出未决冲突(task-14 / D-002 / D-010 / FR-05)。
1236
+ * 只读 + 容错:损坏文件跳过不崩(constraints)。供 collectStatus / platform status 展示。
1237
+ * @returns {Array<{change: string, created_at: string|null, path: string}>}
1238
+ */
1239
+ listConflictFiles() {
1240
+ const specDir = safePlatformSpecDir(this.cwd);
1241
+ if (!specDir) return [];
1242
+ const runtimeDir = join(specDir, '.runtime');
1243
+ if (!existsSync(runtimeDir)) return [];
1244
+ let files = [];
1245
+ try { files = readdirSync(runtimeDir); }
1246
+ catch { return []; }
1247
+ const conflicts = [];
1248
+ // HUB-08:同时扫描进度冲突(sync-conflict-*)与 spec 树冲突(spec-sync-conflict-*),
1249
+ // type 字段供 status 展示与 resolve 分流
1250
+ for (const f of files) {
1251
+ let prefix = null;
1252
+ if (f.startsWith('sync-conflict-') && f.endsWith('.json')) prefix = 'sync-conflict-';
1253
+ else if (f.startsWith('spec-sync-conflict-') && f.endsWith('.json')) prefix = 'spec-sync-conflict-';
1254
+ else continue;
1255
+ const type = prefix === 'sync-conflict-' ? 'progress' : 'spec-tree';
1256
+ const filePath = join(runtimeDir, f);
1257
+ try {
1258
+ const cf = JSON.parse(readFileSync(filePath, 'utf8'));
1259
+ conflicts.push({
1260
+ change: cf.change || f.replace(/^spec-sync-conflict-|^sync-conflict-/, '').replace(/\.json$/, ''),
1261
+ created_at: cf.created_at || null,
1262
+ path: filePath,
1263
+ type,
1264
+ });
1265
+ } catch {
1266
+ // 损坏文件跳过不崩(constraints:容错)
1267
+ }
1268
+ }
1269
+ return conflicts;
1270
+ }
1271
+
1272
+ /**
1273
+ * 扩展 status(task-14 / FR-05):连接信息 + 落后标记 + 未决冲突列表。
1274
+ * 只读展示,不修改任何进度(constraints)。未连接返回 base.status(connected:false,behind/conflicts 空)。
1275
+ * 连接时调 pullList(轻量 GET)拿平台各 change last_pushed_at,比对本地 last_synced_platform_ts
1276
+ * 标记本地可能落后;扫描 sync-conflict-*.json 列未决冲突。网络失败 listFailed=true 不崩。
1277
+ * @returns {Promise<{connected: boolean, url?: string, lastSync?: string|null, behind: Array<object>, conflicts: Array<object>, listFailed: boolean}>}
1278
+ */
1279
+ async collectStatus() {
1280
+ const base = this.status();
1281
+ if (!base.connected) {
1282
+ return { ...base, behind: [], conflicts: [], listFailed: false };
1283
+ }
1284
+ const behind = [];
1285
+ let listFailed = false;
1286
+ const list = await this.pullList();
1287
+ if (list.ok) {
1288
+ for (const ch of list.changes) {
1289
+ const name = typeof ch === 'string' ? ch : ch.name;
1290
+ if (!name) continue;
1291
+ const platformPushedAt = (typeof ch === 'object' && (ch.last_pushed_at || ch.last_active)) || null;
1292
+ if (!platformPushedAt) continue;
1293
+ let localSynced = null;
1294
+ try {
1295
+ const { ProgressManager } = await import('./progress.js');
1296
+ const pm = new ProgressManager({ specDir: safePlatformSpecDir(this.cwd) });
1297
+ const row = pm._ensureDB(this.cwd).getDb().prepare('SELECT last_synced_platform_ts FROM changes WHERE name = ?').get(name);
1298
+ if (row) localSynced = row.last_synced_platform_ts;
1299
+ } catch {
1300
+ // 本地无该 change 记录 / DB 不可达 → 不算落后,跳过
1301
+ }
1302
+ // 仅本地有同步基准(last_synced_platform_ts)且平台更新晚于本地 → 落后
1303
+ if (localSynced && platformPushedAt > localSynced) {
1304
+ behind.push({ name, local_synced: localSynced, platform_pushed_at: platformPushedAt });
1305
+ }
1306
+ }
1307
+ } else {
1308
+ listFailed = true;
1309
+ }
1310
+ const conflicts = this.listConflictFiles();
1311
+ return { ...base, behind, conflicts, listFailed };
1312
+ }
1313
+ }
1314
+
1315
+ // ── CLI 入口函数 ──
1316
+
1317
+ /**
1318
+ * 便捷函数导出 — 供 index.js 直接调用(platform 子命令分发生在 index.js case 'platform')。
1319
+ * HUB-10:原此处另有内嵌 syncModule(args) CLI 分发死代码(全仓零调用、参数形态/子命令面与
1320
+ * index.js 实际分叉——位置参数 token、缺 pull/resolve/pointer、多余 approval/check-approval),
1321
+ * 误导维护者改错地方,已删除。
1322
+ */
1323
+ export async function connect(url, token, cwd) {
1324
+ return new SyncManager(cwd).connect(url, token);
1325
+ }
1326
+
1327
+ export async function disconnect(cwd) {
1328
+ return new SyncManager(cwd).disconnect();
1329
+ }
1330
+
1331
+ export async function sync(changeName, cwd, opts) {
1332
+ return new SyncManager(cwd).sync(changeName, opts);
1333
+ }
1334
+
1335
+ // manual=true:本导出对应 CLI platform sync-docs 手动命令(index.js platform 分支唯一调用方),
1336
+ // 四件套缺失打 warn;sync() 内的自动顺带推走 SyncManager 实例方法默认 auto(ql-20260818-008)
1337
+ export async function syncDocuments(changeName, cwd) {
1338
+ return new SyncManager(cwd).syncDocuments(changeName, { manual: true });
1339
+ }
1340
+
1341
+ export async function checkApproval(changeName, cwd) {
1342
+ return new SyncManager(cwd).checkApproval(changeName);
1343
+ }
1344
+
1345
+ // task-11 / D-006 / D-009 / FR-03:手动 pull 便捷导出(index.js platform pull 子命令用,
1346
+ // 与自动 triggerPull 共用 SyncManager.pull 实例方法,行为一致)
1347
+ export async function pull(changeName, opts, cwd) {
1348
+ return new SyncManager(cwd).pull(changeName, opts);
1349
+ }
1350
+
1351
+ // 两级 pull 第一级(轻量 change 列表),platform pull 无 --change 时先拉列表再按需 pull
1352
+ export async function pullList(cwd) {
1353
+ return new SyncManager(cwd).pullList();
1354
+ }
1355
+
1356
+ // task-13 / D-002 / D-010 / D-013 / FR-05:冲突解决三选一(index.js platform resolve 子命令用)
1357
+ export async function resolve(changeName, mode, cwd) {
1358
+ return new SyncManager(cwd).resolve(changeName, mode);
1359
+ }
1360
+
1361
+ // task-14 / FR-05:扩展 status(连接信息 + 落后标记 + 未决冲突列表),index.js platform status 用
1362
+ export async function collectStatus(cwd) {
1363
+ return new SyncManager(cwd).collectStatus();
1364
+ }
1365
+
1366
+ // 未决冲突文件列表便捷导出(index.js platform resolve 参数解析/报错兜底用,只读)
1367
+ export function listConflictFiles(cwd) {
1368
+ return new SyncManager(cwd).listConflictFiles();
1369
+ }
1370
+
1371
+ // ql-20260818-011:quick 会话专用 spec 树同步入口。quick-<hex8> 会话按设计无
1372
+ // changes/<name>/ 实体目录,progress/四件套上行对它是孤儿数据(平台变更中心按磁盘
1373
+ // change_key join 永不命中),但 spec 树增量(QUICKLOG/模块文档的上行通道)以服务器
1374
+ // 清单为锚做全树 diff,与变更目录无关——triggerSync 对 quick 会话降级只调本函数,
1375
+ // 不走 sync()(后者第二道 existsSync 门会以「变更不存在」提前 return)。
1376
+ // 未连接平台 → {synced: 0} 静默(本地合法状态,与 syncSpecTree 内部口径一致)。
1377
+ export async function syncSpecTreeOnly(changeName, cwd, opts = {}) {
1378
+ const sm = new SyncManager(cwd);
1379
+ const platform = sm._getPlatform();
1380
+ if (!platform) return { synced: 0 };
1381
+ // 树根与 sync() 内链式推送同源(BUG-01:平台模式必须锚 specRoot,防本地空树触发全量 delete)
1382
+ return syncSpecTree(safePlatformSpecDir(cwd) || join(cwd, '.sillyspec'), platform, changeName, { signal: opts.signal });
1383
+ }
1384
+
1385
+ // TBD-hub-api: approve/reject 端点路径与请求体以 SillyHub 仓库实际 API 为准;
1386
+ // 对齐时只改本函数(_submitApproval),无需动 approve/reject 入口。
1387
+ /**
1388
+ * 向平台提交审批决定(approve/reject 共用)。
1389
+ *
1390
+ * 显式用户/daemon 动作:网络/平台失败必须可见(decisions.md D-006@v1)——
1391
+ * 与 best-effort 的 sync 不同,此处失败打 error 并置 process.exitCode = 1。
1392
+ *
1393
+ * @param {string} cwd - 工作目录
1394
+ * @param {string} changeName - 变更名称
1395
+ * @param {'approved'|'rejected'} decision - 审批决定
1396
+ * @param {string|null} reason - rejected 时的原因(approved 时忽略)
1397
+ * @returns {Promise<boolean>} 是否成功
1398
+ */
1399
+ async function _submitApproval(cwd, changeName, decision, reason = null) {
1400
+ const sm = new SyncManager(cwd);
1401
+ const platform = sm._getPlatform();
1402
+ if (!platform) {
1403
+ console.error('❌ 未连接平台,请先 sillyspec platform connect');
1404
+ process.exitCode = 1;
1405
+ return false;
1406
+ }
1407
+
1408
+ if (!changeName) {
1409
+ console.error('❌ 审批需要指定变更名称 (changeName)');
1410
+ process.exitCode = 1;
1411
+ return false;
1412
+ }
1413
+
1414
+ // TBD-hub-api: 端点路径/body 以 SillyHub 实际 API 为准,对齐时只改此处
1415
+ const approvalUrl = `${platform.url}/api/changes/${encodeURIComponent(changeName)}/approval`;
1416
+ const body = decision === 'rejected'
1417
+ ? { decision: 'rejected', reason }
1418
+ : { decision: 'approved' };
1419
+
1420
+ const result = await fetchJson(approvalUrl, {
1421
+ method: 'POST',
1422
+ headers: {
1423
+ 'Content-Type': 'application/json',
1424
+ Authorization: `Bearer ${platform.token}`,
1425
+ },
1426
+ body: JSON.stringify(body),
1427
+ });
1428
+
1429
+ // fetchJson 在非 2xx / 网络错 / 超时 / 非 JSON 时返回 null
1430
+ if (result === null) {
1431
+ console.error(`❌ 审批请求失败 (${changeName} → ${decision}),详情见上方 [sync] 警告`);
1432
+ process.exitCode = 1;
1433
+ return false;
1434
+ }
1435
+
1436
+ // HTTP 成功后更新本地 approvals 表(HTTP 已成功是主要目标,落库失败只 warn 不阻断)
1437
+ try {
1438
+ const { ProgressManager } = await import('./progress.js');
1439
+ const pm = new ProgressManager({ specDir: safePlatformSpecDir(cwd) });
1440
+ pm._updateApprovalStatus(cwd, changeName, decision, reason);
1441
+ } catch (err) {
1442
+ console.warn(`[sync] 更新本地审批状态失败 (${changeName}): ${err.message}`);
1443
+ }
1444
+
1445
+ if (decision === 'approved') {
1446
+ console.log(`✅ 已批准变更 ${changeName}`);
1447
+ } else {
1448
+ console.log(`✅ 已拒绝变更 ${changeName}${reason ? `(原因: ${reason})` : ''}`);
1449
+ }
1450
+ return true;
1451
+ }
1452
+
1453
+ export async function approve(changeName, cwd) {
1454
+ return _submitApproval(cwd, changeName, 'approved');
1455
+ }
1456
+
1457
+ export async function reject(changeName, reason, cwd) {
1458
+ return _submitApproval(cwd, changeName, 'rejected', reason);
1459
+ }
1460
+
1461
+ export async function status(cwd) {
1462
+ const sm = new SyncManager(cwd);
1463
+ const st = sm.status();
1464
+ if (!st.connected) {
1465
+ console.log('平台: 未连接');
1466
+ } else {
1467
+ console.log(`平台: ${st.url}`);
1468
+ console.log(`上次连接: ${st.lastSync || '未知'}`);
1469
+ }
1470
+ }