zero-workspace 0.0.2

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 (507) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +187 -0
  3. package/config/component-versions.json +16 -0
  4. package/config/scenarioCapabilities.json +29 -0
  5. package/config/version-notes.yaml +244 -0
  6. package/dist/adapters/OutputAdapter.d.ts +79 -0
  7. package/dist/adapters/OutputAdapter.d.ts.map +1 -0
  8. package/dist/adapters/OutputAdapter.js +124 -0
  9. package/dist/adapters/OutputAdapter.js.map +1 -0
  10. package/dist/cli/check-node-version.d.ts +3 -0
  11. package/dist/cli/check-node-version.d.ts.map +1 -0
  12. package/dist/cli/check-node-version.js +153 -0
  13. package/dist/cli/check-node-version.js.map +1 -0
  14. package/dist/cli/plugins.d.ts +41 -0
  15. package/dist/cli/plugins.d.ts.map +1 -0
  16. package/dist/cli/plugins.js +742 -0
  17. package/dist/cli/plugins.js.map +1 -0
  18. package/dist/cli/rebuild.d.ts +63 -0
  19. package/dist/cli/rebuild.d.ts.map +1 -0
  20. package/dist/cli/rebuild.js +989 -0
  21. package/dist/cli/rebuild.js.map +1 -0
  22. package/dist/cli/repair.d.ts +7 -0
  23. package/dist/cli/repair.d.ts.map +1 -0
  24. package/dist/cli/repair.js +925 -0
  25. package/dist/cli/repair.js.map +1 -0
  26. package/dist/cli/setup.d.ts +7 -0
  27. package/dist/cli/setup.d.ts.map +1 -0
  28. package/dist/cli/setup.js +452 -0
  29. package/dist/cli/setup.js.map +1 -0
  30. package/dist/cli/update.d.ts +10 -0
  31. package/dist/cli/update.d.ts.map +1 -0
  32. package/dist/cli/update.js +426 -0
  33. package/dist/cli/update.js.map +1 -0
  34. package/dist/cli/webui.d.ts +6 -0
  35. package/dist/cli/webui.d.ts.map +1 -0
  36. package/dist/cli/webui.js +210 -0
  37. package/dist/cli/webui.js.map +1 -0
  38. package/dist/http/index.d.ts +3 -0
  39. package/dist/http/index.d.ts.map +1 -0
  40. package/dist/http/index.js +15 -0
  41. package/dist/http/index.js.map +1 -0
  42. package/dist/http/middleware/errorHandler.d.ts +16 -0
  43. package/dist/http/middleware/errorHandler.d.ts.map +1 -0
  44. package/dist/http/middleware/errorHandler.js +79 -0
  45. package/dist/http/middleware/errorHandler.js.map +1 -0
  46. package/dist/http/routes/admin.d.ts +3 -0
  47. package/dist/http/routes/admin.d.ts.map +1 -0
  48. package/dist/http/routes/admin.js +730 -0
  49. package/dist/http/routes/admin.js.map +1 -0
  50. package/dist/http/routes/backup.d.ts +3 -0
  51. package/dist/http/routes/backup.d.ts.map +1 -0
  52. package/dist/http/routes/backup.js +172 -0
  53. package/dist/http/routes/backup.js.map +1 -0
  54. package/dist/http/routes/config.d.ts +3 -0
  55. package/dist/http/routes/config.d.ts.map +1 -0
  56. package/dist/http/routes/config.js +157 -0
  57. package/dist/http/routes/config.js.map +1 -0
  58. package/dist/http/routes/context.d.ts +3 -0
  59. package/dist/http/routes/context.d.ts.map +1 -0
  60. package/dist/http/routes/context.js +82 -0
  61. package/dist/http/routes/context.js.map +1 -0
  62. package/dist/http/routes/log.d.ts +3 -0
  63. package/dist/http/routes/log.d.ts.map +1 -0
  64. package/dist/http/routes/log.js +105 -0
  65. package/dist/http/routes/log.js.map +1 -0
  66. package/dist/http/routes/memo.d.ts +6 -0
  67. package/dist/http/routes/memo.d.ts.map +1 -0
  68. package/dist/http/routes/memo.js +29 -0
  69. package/dist/http/routes/memo.js.map +1 -0
  70. package/dist/http/routes/node.d.ts +3 -0
  71. package/dist/http/routes/node.d.ts.map +1 -0
  72. package/dist/http/routes/node.js +251 -0
  73. package/dist/http/routes/node.js.map +1 -0
  74. package/dist/http/routes/state.d.ts +3 -0
  75. package/dist/http/routes/state.d.ts.map +1 -0
  76. package/dist/http/routes/state.js +48 -0
  77. package/dist/http/routes/state.js.map +1 -0
  78. package/dist/http/routes/workspace.d.ts +3 -0
  79. package/dist/http/routes/workspace.d.ts.map +1 -0
  80. package/dist/http/routes/workspace.js +249 -0
  81. package/dist/http/routes/workspace.js.map +1 -0
  82. package/dist/http/server.d.ts +10 -0
  83. package/dist/http/server.d.ts.map +1 -0
  84. package/dist/http/server.js +284 -0
  85. package/dist/http/server.js.map +1 -0
  86. package/dist/http/services.d.ts +93 -0
  87. package/dist/http/services.d.ts.map +1 -0
  88. package/dist/http/services.js +297 -0
  89. package/dist/http/services.js.map +1 -0
  90. package/dist/index.d.ts +3 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +1073 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/prompts/guidanceContent.d.ts +18 -0
  95. package/dist/prompts/guidanceContent.d.ts.map +1 -0
  96. package/dist/prompts/guidanceContent.js +814 -0
  97. package/dist/prompts/guidanceContent.js.map +1 -0
  98. package/dist/prompts/index.d.ts +2 -0
  99. package/dist/prompts/index.d.ts.map +1 -0
  100. package/dist/prompts/index.js +4 -0
  101. package/dist/prompts/index.js.map +1 -0
  102. package/dist/prompts/instructions.d.ts +56 -0
  103. package/dist/prompts/instructions.d.ts.map +1 -0
  104. package/dist/prompts/instructions.js +1343 -0
  105. package/dist/prompts/instructions.js.map +1 -0
  106. package/dist/services/BackupService.d.ts +104 -0
  107. package/dist/services/BackupService.d.ts.map +1 -0
  108. package/dist/services/BackupService.js +549 -0
  109. package/dist/services/BackupService.js.map +1 -0
  110. package/dist/services/CapabilityService.d.ts +38 -0
  111. package/dist/services/CapabilityService.d.ts.map +1 -0
  112. package/dist/services/CapabilityService.js +256 -0
  113. package/dist/services/CapabilityService.js.map +1 -0
  114. package/dist/services/ConfigService.d.ts +35 -0
  115. package/dist/services/ConfigService.d.ts.map +1 -0
  116. package/dist/services/ConfigService.js +105 -0
  117. package/dist/services/ConfigService.js.map +1 -0
  118. package/dist/services/ContextService.d.ts +65 -0
  119. package/dist/services/ContextService.d.ts.map +1 -0
  120. package/dist/services/ContextService.js +503 -0
  121. package/dist/services/ContextService.js.map +1 -0
  122. package/dist/services/DetectionService.d.ts +76 -0
  123. package/dist/services/DetectionService.d.ts.map +1 -0
  124. package/dist/services/DetectionService.js +262 -0
  125. package/dist/services/DetectionService.js.map +1 -0
  126. package/dist/services/DispatchService.d.ts +267 -0
  127. package/dist/services/DispatchService.d.ts.map +1 -0
  128. package/dist/services/DispatchService.js +1357 -0
  129. package/dist/services/DispatchService.js.map +1 -0
  130. package/dist/services/EventService.d.ts +81 -0
  131. package/dist/services/EventService.d.ts.map +1 -0
  132. package/dist/services/EventService.js +187 -0
  133. package/dist/services/EventService.js.map +1 -0
  134. package/dist/services/GuidanceService.d.ts +64 -0
  135. package/dist/services/GuidanceService.d.ts.map +1 -0
  136. package/dist/services/GuidanceService.js +259 -0
  137. package/dist/services/GuidanceService.js.map +1 -0
  138. package/dist/services/HealthService.d.ts +43 -0
  139. package/dist/services/HealthService.d.ts.map +1 -0
  140. package/dist/services/HealthService.js +276 -0
  141. package/dist/services/HealthService.js.map +1 -0
  142. package/dist/services/InstallationService.d.ts +62 -0
  143. package/dist/services/InstallationService.d.ts.map +1 -0
  144. package/dist/services/InstallationService.js +204 -0
  145. package/dist/services/InstallationService.js.map +1 -0
  146. package/dist/services/LogService.d.ts +35 -0
  147. package/dist/services/LogService.d.ts.map +1 -0
  148. package/dist/services/LogService.js +189 -0
  149. package/dist/services/LogService.js.map +1 -0
  150. package/dist/services/MemoService.d.ts +39 -0
  151. package/dist/services/MemoService.d.ts.map +1 -0
  152. package/dist/services/MemoService.js +288 -0
  153. package/dist/services/MemoService.js.map +1 -0
  154. package/dist/services/NodeService.d.ts +90 -0
  155. package/dist/services/NodeService.d.ts.map +1 -0
  156. package/dist/services/NodeService.js +958 -0
  157. package/dist/services/NodeService.js.map +1 -0
  158. package/dist/services/OpenSpecParser.d.ts +43 -0
  159. package/dist/services/OpenSpecParser.d.ts.map +1 -0
  160. package/dist/services/OpenSpecParser.js +191 -0
  161. package/dist/services/OpenSpecParser.js.map +1 -0
  162. package/dist/services/ReferenceService.d.ts +35 -0
  163. package/dist/services/ReferenceService.d.ts.map +1 -0
  164. package/dist/services/ReferenceService.js +195 -0
  165. package/dist/services/ReferenceService.js.map +1 -0
  166. package/dist/services/RepairService.d.ts +36 -0
  167. package/dist/services/RepairService.d.ts.map +1 -0
  168. package/dist/services/RepairService.js +429 -0
  169. package/dist/services/RepairService.js.map +1 -0
  170. package/dist/services/SearchService.d.ts +34 -0
  171. package/dist/services/SearchService.d.ts.map +1 -0
  172. package/dist/services/SearchService.js +293 -0
  173. package/dist/services/SearchService.js.map +1 -0
  174. package/dist/services/SessionService.d.ts +136 -0
  175. package/dist/services/SessionService.d.ts.map +1 -0
  176. package/dist/services/SessionService.js +297 -0
  177. package/dist/services/SessionService.js.map +1 -0
  178. package/dist/services/StateService.d.ts +97 -0
  179. package/dist/services/StateService.d.ts.map +1 -0
  180. package/dist/services/StateService.js +846 -0
  181. package/dist/services/StateService.js.map +1 -0
  182. package/dist/services/TutorialService.d.ts +114 -0
  183. package/dist/services/TutorialService.d.ts.map +1 -0
  184. package/dist/services/TutorialService.js +1262 -0
  185. package/dist/services/TutorialService.js.map +1 -0
  186. package/dist/services/WorkspaceService.d.ts +273 -0
  187. package/dist/services/WorkspaceService.d.ts.map +1 -0
  188. package/dist/services/WorkspaceService.js +1764 -0
  189. package/dist/services/WorkspaceService.js.map +1 -0
  190. package/dist/services/index.d.ts +15 -0
  191. package/dist/services/index.d.ts.map +1 -0
  192. package/dist/services/index.js +14 -0
  193. package/dist/services/index.js.map +1 -0
  194. package/dist/storage/FileSystemAdapter.d.ts +223 -0
  195. package/dist/storage/FileSystemAdapter.d.ts.map +1 -0
  196. package/dist/storage/FileSystemAdapter.js +384 -0
  197. package/dist/storage/FileSystemAdapter.js.map +1 -0
  198. package/dist/storage/JsonStorage.d.ts +158 -0
  199. package/dist/storage/JsonStorage.d.ts.map +1 -0
  200. package/dist/storage/JsonStorage.js +613 -0
  201. package/dist/storage/JsonStorage.js.map +1 -0
  202. package/dist/storage/MarkdownStorage.d.ts +178 -0
  203. package/dist/storage/MarkdownStorage.d.ts.map +1 -0
  204. package/dist/storage/MarkdownStorage.js +918 -0
  205. package/dist/storage/MarkdownStorage.js.map +1 -0
  206. package/dist/storage/SessionBindingStorage.d.ts +69 -0
  207. package/dist/storage/SessionBindingStorage.d.ts.map +1 -0
  208. package/dist/storage/SessionBindingStorage.js +131 -0
  209. package/dist/storage/SessionBindingStorage.js.map +1 -0
  210. package/dist/storage/index.d.ts +6 -0
  211. package/dist/storage/index.d.ts.map +1 -0
  212. package/dist/storage/index.js +6 -0
  213. package/dist/storage/index.js.map +1 -0
  214. package/dist/tools/capability.d.ts +18 -0
  215. package/dist/tools/capability.d.ts.map +1 -0
  216. package/dist/tools/capability.js +73 -0
  217. package/dist/tools/capability.js.map +1 -0
  218. package/dist/tools/config.d.ts +14 -0
  219. package/dist/tools/config.d.ts.map +1 -0
  220. package/dist/tools/config.js +61 -0
  221. package/dist/tools/config.js.map +1 -0
  222. package/dist/tools/context.d.ts +22 -0
  223. package/dist/tools/context.d.ts.map +1 -0
  224. package/dist/tools/context.js +139 -0
  225. package/dist/tools/context.js.map +1 -0
  226. package/dist/tools/dispatch.d.ts +41 -0
  227. package/dist/tools/dispatch.d.ts.map +1 -0
  228. package/dist/tools/dispatch.js +380 -0
  229. package/dist/tools/dispatch.js.map +1 -0
  230. package/dist/tools/help.d.ts +44 -0
  231. package/dist/tools/help.d.ts.map +1 -0
  232. package/dist/tools/help.js +227 -0
  233. package/dist/tools/help.js.map +1 -0
  234. package/dist/tools/import.d.ts +17 -0
  235. package/dist/tools/import.d.ts.map +1 -0
  236. package/dist/tools/import.js +96 -0
  237. package/dist/tools/import.js.map +1 -0
  238. package/dist/tools/index.d.ts +12 -0
  239. package/dist/tools/index.d.ts.map +1 -0
  240. package/dist/tools/index.js +13 -0
  241. package/dist/tools/index.js.map +1 -0
  242. package/dist/tools/log.d.ts +21 -0
  243. package/dist/tools/log.d.ts.map +1 -0
  244. package/dist/tools/log.js +93 -0
  245. package/dist/tools/log.js.map +1 -0
  246. package/dist/tools/memo.d.ts +26 -0
  247. package/dist/tools/memo.d.ts.map +1 -0
  248. package/dist/tools/memo.js +188 -0
  249. package/dist/tools/memo.js.map +1 -0
  250. package/dist/tools/node.d.ts +34 -0
  251. package/dist/tools/node.d.ts.map +1 -0
  252. package/dist/tools/node.js +328 -0
  253. package/dist/tools/node.js.map +1 -0
  254. package/dist/tools/search.d.ts +14 -0
  255. package/dist/tools/search.d.ts.map +1 -0
  256. package/dist/tools/search.js +95 -0
  257. package/dist/tools/search.js.map +1 -0
  258. package/dist/tools/session.d.ts +22 -0
  259. package/dist/tools/session.d.ts.map +1 -0
  260. package/dist/tools/session.js +127 -0
  261. package/dist/tools/session.js.map +1 -0
  262. package/dist/tools/state.d.ts +10 -0
  263. package/dist/tools/state.d.ts.map +1 -0
  264. package/dist/tools/state.js +79 -0
  265. package/dist/tools/state.js.map +1 -0
  266. package/dist/tools/workspace.d.ts +38 -0
  267. package/dist/tools/workspace.d.ts.map +1 -0
  268. package/dist/tools/workspace.js +240 -0
  269. package/dist/tools/workspace.js.map +1 -0
  270. package/dist/types/capability.d.ts +36 -0
  271. package/dist/types/capability.d.ts.map +1 -0
  272. package/dist/types/capability.js +3 -0
  273. package/dist/types/capability.js.map +1 -0
  274. package/dist/types/confirmation.d.ts +35 -0
  275. package/dist/types/confirmation.d.ts.map +1 -0
  276. package/dist/types/confirmation.js +3 -0
  277. package/dist/types/confirmation.js.map +1 -0
  278. package/dist/types/context.d.ts +174 -0
  279. package/dist/types/context.d.ts.map +1 -0
  280. package/dist/types/context.js +3 -0
  281. package/dist/types/context.js.map +1 -0
  282. package/dist/types/errors.d.ts +81 -0
  283. package/dist/types/errors.d.ts.map +1 -0
  284. package/dist/types/errors.js +154 -0
  285. package/dist/types/errors.js.map +1 -0
  286. package/dist/types/guidance.d.ts +162 -0
  287. package/dist/types/guidance.d.ts.map +1 -0
  288. package/dist/types/guidance.js +4 -0
  289. package/dist/types/guidance.js.map +1 -0
  290. package/dist/types/health.d.ts +61 -0
  291. package/dist/types/health.d.ts.map +1 -0
  292. package/dist/types/health.js +3 -0
  293. package/dist/types/health.js.map +1 -0
  294. package/dist/types/index.d.ts +10 -0
  295. package/dist/types/index.d.ts.map +1 -0
  296. package/dist/types/index.js +11 -0
  297. package/dist/types/index.js.map +1 -0
  298. package/dist/types/memo.d.ts +132 -0
  299. package/dist/types/memo.d.ts.map +1 -0
  300. package/dist/types/memo.js +3 -0
  301. package/dist/types/memo.js.map +1 -0
  302. package/dist/types/node.d.ts +316 -0
  303. package/dist/types/node.d.ts.map +1 -0
  304. package/dist/types/node.js +3 -0
  305. package/dist/types/node.js.map +1 -0
  306. package/dist/types/repair.d.ts +62 -0
  307. package/dist/types/repair.d.ts.map +1 -0
  308. package/dist/types/repair.js +4 -0
  309. package/dist/types/repair.js.map +1 -0
  310. package/dist/types/search.d.ts +58 -0
  311. package/dist/types/search.d.ts.map +1 -0
  312. package/dist/types/search.js +3 -0
  313. package/dist/types/search.js.map +1 -0
  314. package/dist/types/settings.d.ts +109 -0
  315. package/dist/types/settings.d.ts.map +1 -0
  316. package/dist/types/settings.js +30 -0
  317. package/dist/types/settings.js.map +1 -0
  318. package/dist/types/workspace.d.ts +357 -0
  319. package/dist/types/workspace.d.ts.map +1 -0
  320. package/dist/types/workspace.js +3 -0
  321. package/dist/types/workspace.js.map +1 -0
  322. package/dist/utils/contentValidation.d.ts +47 -0
  323. package/dist/utils/contentValidation.d.ts.map +1 -0
  324. package/dist/utils/contentValidation.js +93 -0
  325. package/dist/utils/contentValidation.js.map +1 -0
  326. package/dist/utils/devLog.d.ts +43 -0
  327. package/dist/utils/devLog.d.ts.map +1 -0
  328. package/dist/utils/devLog.js +94 -0
  329. package/dist/utils/devLog.js.map +1 -0
  330. package/dist/utils/errorLogger.d.ts +27 -0
  331. package/dist/utils/errorLogger.d.ts.map +1 -0
  332. package/dist/utils/errorLogger.js +105 -0
  333. package/dist/utils/errorLogger.js.map +1 -0
  334. package/dist/utils/git.d.ts +123 -0
  335. package/dist/utils/git.d.ts.map +1 -0
  336. package/dist/utils/git.js +400 -0
  337. package/dist/utils/git.js.map +1 -0
  338. package/dist/utils/hash.d.ts +32 -0
  339. package/dist/utils/hash.d.ts.map +1 -0
  340. package/dist/utils/hash.js +37 -0
  341. package/dist/utils/hash.js.map +1 -0
  342. package/dist/utils/id.d.ts +54 -0
  343. package/dist/utils/id.d.ts.map +1 -0
  344. package/dist/utils/id.js +96 -0
  345. package/dist/utils/id.js.map +1 -0
  346. package/dist/utils/index.d.ts +8 -0
  347. package/dist/utils/index.d.ts.map +1 -0
  348. package/dist/utils/index.js +9 -0
  349. package/dist/utils/index.js.map +1 -0
  350. package/dist/utils/logger.d.ts +42 -0
  351. package/dist/utils/logger.d.ts.map +1 -0
  352. package/dist/utils/logger.js +228 -0
  353. package/dist/utils/logger.js.map +1 -0
  354. package/dist/utils/manualChangeFormatter.d.ts +8 -0
  355. package/dist/utils/manualChangeFormatter.d.ts.map +1 -0
  356. package/dist/utils/manualChangeFormatter.js +21 -0
  357. package/dist/utils/manualChangeFormatter.js.map +1 -0
  358. package/dist/utils/paramValidator.d.ts +35 -0
  359. package/dist/utils/paramValidator.d.ts.map +1 -0
  360. package/dist/utils/paramValidator.js +214 -0
  361. package/dist/utils/paramValidator.js.map +1 -0
  362. package/dist/utils/port.d.ts +7 -0
  363. package/dist/utils/port.d.ts.map +1 -0
  364. package/dist/utils/port.js +28 -0
  365. package/dist/utils/port.js.map +1 -0
  366. package/dist/utils/processManager.d.ts +53 -0
  367. package/dist/utils/processManager.d.ts.map +1 -0
  368. package/dist/utils/processManager.js +267 -0
  369. package/dist/utils/processManager.js.map +1 -0
  370. package/dist/utils/sessionLogger.d.ts +28 -0
  371. package/dist/utils/sessionLogger.d.ts.map +1 -0
  372. package/dist/utils/sessionLogger.js +142 -0
  373. package/dist/utils/sessionLogger.js.map +1 -0
  374. package/dist/utils/time.d.ts +15 -0
  375. package/dist/utils/time.d.ts.map +1 -0
  376. package/dist/utils/time.js +32 -0
  377. package/dist/utils/time.js.map +1 -0
  378. package/dist/utils/validation.d.ts +23 -0
  379. package/dist/utils/validation.d.ts.map +1 -0
  380. package/dist/utils/validation.js +88 -0
  381. package/dist/utils/validation.js.map +1 -0
  382. package/docs//346/227/245/345/277/227/347/263/273/347/273/237.md +389 -0
  383. package/docs//347/224/250/346/210/267/346/211/213/345/206/214.md +1446 -0
  384. package/docs//347/224/250/346/210/267/346/211/213/345/206/214/344/270/216/346/212/200/346/234/257/346/214/207/345/215/227.md +873 -0
  385. package/package.json +94 -0
  386. package/plugin/README.md +141 -0
  387. package/plugin/agents/zero-executor.md +114 -0
  388. package/plugin/agents/zero-reviewer.md +133 -0
  389. package/plugin/docs/diagnostic-guide.md +128 -0
  390. package/plugin/hooks/hooks.json.deprecated +70 -0
  391. package/plugin/scripts/cursor-hook-entry.cjs +217 -0
  392. package/plugin/scripts/hook-entry.cjs +663 -0
  393. package/plugin/scripts/openspec-import.cjs +714 -0
  394. package/plugin/scripts/shared/binding.cjs +98 -0
  395. package/plugin/scripts/shared/config.cjs +65 -0
  396. package/plugin/scripts/shared/context.cjs +120 -0
  397. package/plugin/scripts/shared/index.cjs +34 -0
  398. package/plugin/scripts/shared/logger.cjs +196 -0
  399. package/plugin/scripts/shared/reminder.cjs +261 -0
  400. package/plugin/scripts/shared/utils.cjs +62 -0
  401. package/plugin/scripts/shared/workspace.cjs +322 -0
  402. package/plugin/skills/aligning-intent/SKILL.md +275 -0
  403. package/plugin/skills/analyzing-measurements/SKILL.md +223 -0
  404. package/plugin/skills/bootstrapping-workspace/SKILL.md +260 -0
  405. package/plugin/skills/designing-solutions/SKILL.md +363 -0
  406. package/plugin/skills/diagnosing-issues/SKILL.md +219 -0
  407. package/plugin/skills/discovering-context/SKILL.md +283 -0
  408. package/plugin/skills/dispatching-parent/SKILL.md +399 -0
  409. package/plugin/skills/executing-task/SKILL.md +340 -0
  410. package/plugin/skills/memo-create/SKILL.md +222 -0
  411. package/plugin/skills/planning-verification/SKILL.md +245 -0
  412. package/plugin/skills/preparing-dispatch/SKILL.md +299 -0
  413. package/plugin/skills/researching-tech/SKILL.md +223 -0
  414. package/plugin/skills/reviewing-quality/SKILL.md +354 -0
  415. package/plugin/skills/reviewing-spec/SKILL.md +333 -0
  416. package/plugin/skills/starting-info-flow/SKILL.md +196 -0
  417. package/web/README.md +5 -0
  418. package/web/dist/assets/DocsView-Bls_Vjsr.css +1 -0
  419. package/web/dist/assets/DocsView-Cxc0B63r.js +1447 -0
  420. package/web/dist/assets/HomeView-C7df9thb.js +9 -0
  421. package/web/dist/assets/HomeView-ufUdnfHk.css +1 -0
  422. package/web/dist/assets/MarkdownContent-DXp6CtSP.js +308 -0
  423. package/web/dist/assets/MarkdownContent-NFqiOBLH.css +1 -0
  424. package/web/dist/assets/NotFoundView-BYX1oZAn.css +1 -0
  425. package/web/dist/assets/NotFoundView-zrc0lT9q.js +1 -0
  426. package/web/dist/assets/WorkspaceView-BckqgNcX.js +27 -0
  427. package/web/dist/assets/WorkspaceView-J1dgpYMx.css +1 -0
  428. package/web/dist/assets/WsConfirmDialog-C1CvL4my.css +1 -0
  429. package/web/dist/assets/WsConfirmDialog-gLEP7uBD.js +4 -0
  430. package/web/dist/assets/arc-DPkKTkUT.js +1 -0
  431. package/web/dist/assets/architectureDiagram-VXUJARFQ-CEGpqUlZ.js +36 -0
  432. package/web/dist/assets/blockDiagram-VD42YOAC-Bv-mqdQH.js +122 -0
  433. package/web/dist/assets/c4Diagram-YG6GDRKO-DRyPatZ_.js +10 -0
  434. package/web/dist/assets/channel-B84mKLDZ.js +1 -0
  435. package/web/dist/assets/chunk-4BX2VUAB-c7DivX0u.js +1 -0
  436. package/web/dist/assets/chunk-55IACEB6-CGKTaLlo.js +1 -0
  437. package/web/dist/assets/chunk-B4BG7PRW-Czhx5Q_P.js +165 -0
  438. package/web/dist/assets/chunk-DI55MBZ5-CQVA7hcZ.js +220 -0
  439. package/web/dist/assets/chunk-FMBD7UC4-hEiPmi7V.js +15 -0
  440. package/web/dist/assets/chunk-QN33PNHL-rL6yYI-E.js +1 -0
  441. package/web/dist/assets/chunk-QZHKN3VN-BRyHBBzq.js +1 -0
  442. package/web/dist/assets/chunk-TZMSLE5B-D4PXmTz9.js +1 -0
  443. package/web/dist/assets/classDiagram-2ON5EDUG-CNn53ohi.js +1 -0
  444. package/web/dist/assets/classDiagram-v2-WZHVMYZB-CNn53ohi.js +1 -0
  445. package/web/dist/assets/cose-bilkent-S5V4N54A-BAREnRga.js +1 -0
  446. package/web/dist/assets/cytoscape.esm-BnkdMOzK.js +321 -0
  447. package/web/dist/assets/dagre-6UL2VRFP-DaYzb3MT.js +4 -0
  448. package/web/dist/assets/defaultLocale-C4B-KCzX.js +1 -0
  449. package/web/dist/assets/diagram-PSM6KHXK-BFltDqvd.js +24 -0
  450. package/web/dist/assets/diagram-QEK2KX5R-CR4VU2La.js +43 -0
  451. package/web/dist/assets/diagram-S2PKOQOG-0UfIeT-1.js +24 -0
  452. package/web/dist/assets/erDiagram-Q2GNP2WA-Bo17Xmng.js +60 -0
  453. package/web/dist/assets/flowDiagram-NV44I4VS-CzqhQp8s.js +162 -0
  454. package/web/dist/assets/ganttDiagram-JELNMOA3-TXwXtUcq.js +267 -0
  455. package/web/dist/assets/gitGraphDiagram-NY62KEGX-CoFQTy9O.js +65 -0
  456. package/web/dist/assets/graph-CIQcRIVd.js +1 -0
  457. package/web/dist/assets/index-BgLd_o_M.css +1 -0
  458. package/web/dist/assets/index-Cd_J3fZn.js +30 -0
  459. package/web/dist/assets/infoDiagram-WHAUD3N6-Dq0xXfVu.js +2 -0
  460. package/web/dist/assets/init-Gi6I4Gst.js +1 -0
  461. package/web/dist/assets/journeyDiagram-XKPGCS4Q-jIg5BOfC.js +139 -0
  462. package/web/dist/assets/kanban-definition-3W4ZIXB7-D2giu6aZ.js +89 -0
  463. package/web/dist/assets/katex-XbL3y5x-.js +261 -0
  464. package/web/dist/assets/layout-Bm-XCM-8.js +1 -0
  465. package/web/dist/assets/linear-FbekP9OZ.js +1 -0
  466. package/web/dist/assets/min-BrRCpYmF.js +1 -0
  467. package/web/dist/assets/mindmap-definition-VGOIOE7T-o-4ubbY9.js +68 -0
  468. package/web/dist/assets/noto-emoji-0-400-normal-BTQbhB77.woff +0 -0
  469. package/web/dist/assets/noto-emoji-0-400-normal-DHdy6Uhy.woff2 +0 -0
  470. package/web/dist/assets/noto-emoji-1-400-normal-0IvkdXBB.woff +0 -0
  471. package/web/dist/assets/noto-emoji-1-400-normal-BY9OovbM.woff2 +0 -0
  472. package/web/dist/assets/noto-emoji-10-400-normal-D9w4QCof.woff2 +0 -0
  473. package/web/dist/assets/noto-emoji-10-400-normal-DtCumcZR.woff +0 -0
  474. package/web/dist/assets/noto-emoji-11-400-normal-BboTlyvx.woff +0 -0
  475. package/web/dist/assets/noto-emoji-12-400-normal-BB5pgBKj.woff2 +0 -0
  476. package/web/dist/assets/noto-emoji-12-400-normal-g186qhiA.woff +0 -0
  477. package/web/dist/assets/noto-emoji-2-400-normal-BKCR1azW.woff2 +0 -0
  478. package/web/dist/assets/noto-emoji-2-400-normal-BYH0KhDr.woff +0 -0
  479. package/web/dist/assets/noto-emoji-3-400-normal-CnPTUeEK.woff +0 -0
  480. package/web/dist/assets/noto-emoji-3-400-normal-TrTb2VQM.woff2 +0 -0
  481. package/web/dist/assets/noto-emoji-4-400-normal-BxD0KVdj.woff +0 -0
  482. package/web/dist/assets/noto-emoji-4-400-normal-s_n9EyG1.woff2 +0 -0
  483. package/web/dist/assets/noto-emoji-5-400-normal-C190AIxR.woff +0 -0
  484. package/web/dist/assets/noto-emoji-5-400-normal-Ctfx4xc6.woff2 +0 -0
  485. package/web/dist/assets/noto-emoji-6-400-normal-DlXlXWt7.woff +0 -0
  486. package/web/dist/assets/noto-emoji-6-400-normal-NzsjD754.woff2 +0 -0
  487. package/web/dist/assets/noto-emoji-7-400-normal-BHP8KeA6.woff2 +0 -0
  488. package/web/dist/assets/noto-emoji-7-400-normal-CtuKhtAZ.woff +0 -0
  489. package/web/dist/assets/noto-emoji-8-400-normal-DR49ZFe7.woff +0 -0
  490. package/web/dist/assets/noto-emoji-8-400-normal-Dvmkf6b2.woff2 +0 -0
  491. package/web/dist/assets/noto-emoji-9-400-normal-BeHJQ2iK.woff2 +0 -0
  492. package/web/dist/assets/noto-emoji-9-400-normal-BlXmCgeQ.woff +0 -0
  493. package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  494. package/web/dist/assets/pieDiagram-ADFJNKIX-C45wSpld.js +30 -0
  495. package/web/dist/assets/quadrantDiagram-AYHSOK5B-tv-_fe-W.js +7 -0
  496. package/web/dist/assets/reduce-CoLNNlNb.js +1 -0
  497. package/web/dist/assets/requirementDiagram-UZGBJVZJ-Dn6PDfkL.js +64 -0
  498. package/web/dist/assets/sankeyDiagram-TZEHDZUN-CbXTZAsG.js +10 -0
  499. package/web/dist/assets/sequenceDiagram-WL72ISMW-B-o1CUJ5.js +145 -0
  500. package/web/dist/assets/stateDiagram-FKZM4ZOC-hzrupXQi.js +1 -0
  501. package/web/dist/assets/stateDiagram-v2-4FDKWEC3-DN-c2M96.js +1 -0
  502. package/web/dist/assets/timeline-definition-IT6M3QCI-R5SP9GDo.js +61 -0
  503. package/web/dist/assets/treemap-KMMF4GRG-Pg9KlUOt.js +128 -0
  504. package/web/dist/assets/xychartDiagram-PRI3JC2R-C3vbJhd1.js +7 -0
  505. package/web/dist/favicon.svg +13 -0
  506. package/web/dist/index.html +17 -0
  507. package//351/205/215/347/275/256/346/226/271/345/274/217.md +330 -0
@@ -0,0 +1,958 @@
1
+ // src/services/NodeService.ts
2
+ import * as crypto from "node:crypto";
3
+ import { computeConclusionsHash } from "../utils/hash.js";
4
+ import { ZeroError } from "../types/errors.js";
5
+ import { generateNodeId, generateNodeDirName, extractShortId } from "../utils/id.js";
6
+ import { now } from "../utils/time.js";
7
+ import { validateNodeTitle } from "../utils/validation.js";
8
+ import { computeNodeHash } from "../utils/hash.js";
9
+ import { devLog } from "../utils/devLog.js";
10
+ import { GuidanceService } from "./GuidanceService.js";
11
+ /**
12
+ * 内部调用专用的 magic hash,绕过 rulesHash 验证
13
+ * 仅供 capability_select 等内部模块使用
14
+ */
15
+ export const INTERNAL_RULES_HASH = "__internal__";
16
+ import { eventService } from "./EventService.js";
17
+ /**
18
+ * 节点服务
19
+ * 处理节点相关的业务逻辑
20
+ */
21
+ export class NodeService {
22
+ json;
23
+ md;
24
+ fs;
25
+ stateService;
26
+ guidanceService;
27
+ constructor(json, md, fs) {
28
+ this.json = json;
29
+ this.md = md;
30
+ this.fs = fs;
31
+ this.guidanceService = new GuidanceService();
32
+ }
33
+ /**
34
+ * 设置 StateService 依赖(用于 token 生成)
35
+ */
36
+ setStateService(stateService) {
37
+ this.stateService = stateService;
38
+ }
39
+ /**
40
+ * 根据 workspaceId 获取 projectRoot 和 wsDirName
41
+ */
42
+ async resolveProjectRoot(workspaceId) {
43
+ const index = await this.json.readIndex();
44
+ const entry = index.workspaces.find(ws => ws.id === workspaceId);
45
+ if (!entry) {
46
+ throw new ZeroError("WORKSPACE_NOT_FOUND", `工作区 "${workspaceId}" 不存在`);
47
+ }
48
+ if (entry.status === "error" && entry.errorInfo) {
49
+ throw new ZeroError("WORKSPACE_ERROR", `工作区 "${workspaceId}" 处于错误状态: ${entry.errorInfo.message}`);
50
+ }
51
+ return {
52
+ projectRoot: entry.projectRoot,
53
+ wsDirName: entry.dirName || entry.id // 向后兼容
54
+ };
55
+ }
56
+ /**
57
+ * 根据 workspaceId 获取工作区信息(包括归档状态和目录名)
58
+ */
59
+ async resolveWorkspaceInfo(workspaceId) {
60
+ const index = await this.json.readIndex();
61
+ const wsEntry = index.workspaces.find(ws => ws.id === workspaceId);
62
+ if (!wsEntry) {
63
+ devLog.workspaceLookup(workspaceId, false);
64
+ throw new ZeroError("WORKSPACE_NOT_FOUND", `工作区 "${workspaceId}" 不存在`);
65
+ }
66
+ // 检查 error 状态
67
+ if (wsEntry.status === "error" && wsEntry.errorInfo) {
68
+ throw new ZeroError("WORKSPACE_ERROR", `工作区 "${workspaceId}" 处于错误状态: ${wsEntry.errorInfo.message}\n` +
69
+ `请先修复工作区后再操作节点。`);
70
+ }
71
+ const isArchived = wsEntry.status === "archived";
72
+ const wsDirName = wsEntry.dirName || wsEntry.id; // 向后兼容:旧数据没有 dirName
73
+ devLog.workspaceLookup(workspaceId, true, wsEntry.status);
74
+ if (isArchived) {
75
+ devLog.archivePath(workspaceId, isArchived, this.fs.getWorkspaceBasePath(wsEntry.projectRoot, wsDirName, true));
76
+ }
77
+ return {
78
+ projectRoot: wsEntry.projectRoot,
79
+ wsDirName,
80
+ isArchived,
81
+ };
82
+ }
83
+ /**
84
+ * 解析并验证节点目录名
85
+ * 当 graph.json 中的 dirName 对应的目录不存在时,尝试通过 shortId 查找实际目录
86
+ * 这是运行时兜底机制,用于处理迁移失败或遗漏的情况
87
+ *
88
+ * @param projectRoot 项目根目录
89
+ * @param wsDirName 工作区目录名
90
+ * @param nodeId 节点 ID
91
+ * @param meta 节点元数据
92
+ * @param isArchived 是否归档
93
+ * @returns 实际的节点目录名
94
+ */
95
+ async resolveNodeDirName(projectRoot, wsDirName, nodeId, meta, isArchived) {
96
+ const dirName = meta.dirName || nodeId;
97
+ // 检查目录是否存在
98
+ const nodePath = this.fs.getNodePath(projectRoot, wsDirName, dirName);
99
+ if (await this.fs.exists(nodePath)) {
100
+ return dirName;
101
+ }
102
+ // 目录不存在,尝试通过 shortId 查找
103
+ const shortId = extractShortId(nodeId);
104
+ const nodesDir = this.fs.getNodesDir(projectRoot, wsDirName);
105
+ try {
106
+ const entries = await this.fs.readdir(nodesDir);
107
+ // 优先匹配 `_shortId` 后缀
108
+ for (const entry of entries) {
109
+ if (entry.endsWith(`_${shortId}`)) {
110
+ devLog.warn(`[runtime] 节点目录名修复: ${nodeId} → ${entry}`);
111
+ // 更新 meta 中的 dirName(调用方需要保存 graph.json)
112
+ meta.dirName = entry;
113
+ return entry;
114
+ }
115
+ }
116
+ // 兜底:匹配包含 shortId 的目录
117
+ if (shortId.length >= 6) {
118
+ for (const entry of entries) {
119
+ if (entry.includes(shortId)) {
120
+ devLog.warn(`[runtime] 节点目录名修复(模糊匹配): ${nodeId} → ${entry}`);
121
+ meta.dirName = entry;
122
+ return entry;
123
+ }
124
+ }
125
+ }
126
+ }
127
+ catch {
128
+ // 读取目录失败,返回原始值
129
+ }
130
+ // 未找到匹配目录,返回原始值(后续可能会抛出文件不存在错误)
131
+ return dirName;
132
+ }
133
+ /**
134
+ * 创建节点
135
+ */
136
+ async create(params) {
137
+ const { workspaceId, parentId, type, title, requirement = "", docs = [], role, acceptanceCriteria, isNeedTest, testRequirement } = params;
138
+ // 1. 获取 projectRoot 和 wsDirName
139
+ const { projectRoot, wsDirName } = await this.resolveProjectRoot(workspaceId);
140
+ // 2. 验证父节点存在
141
+ const graph = await this.json.readGraph(projectRoot, wsDirName);
142
+ const parentMeta = graph.nodes[parentId];
143
+ if (!parentMeta) {
144
+ throw new ZeroError("PARENT_NOT_FOUND", `父节点 "${parentId}" 不存在`);
145
+ }
146
+ // 3. 验证父节点是规划节点(只有规划节点可以有子节点)
147
+ if (parentMeta.type === "execution") {
148
+ throw new ZeroError("EXECUTION_CANNOT_HAVE_CHILDREN", "执行节点不能创建子节点,如需分解任务请 fail 后回到父规划节点处理");
149
+ }
150
+ // 4. 如果父节点是 completed 状态,自动 reopen 到 planning
151
+ let autoReopened = false;
152
+ let archivedConclusion = null;
153
+ if (parentMeta.status === "completed") {
154
+ parentMeta.status = "planning";
155
+ parentMeta.updatedAt = now();
156
+ // 保留原有结论作为历史引用(不清空)
157
+ const oldConclusion = parentMeta.conclusion;
158
+ if (oldConclusion) {
159
+ // 将原有结论转换为引用格式,标注为历史结论
160
+ const timestamp = new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
161
+ const quotedConclusion = oldConclusion.split("\n").map(line => `> ${line}`).join("\n");
162
+ archivedConclusion = `**[历史结论 - ${timestamp}]**\n${quotedConclusion}\n\n---\n\n`;
163
+ parentMeta.conclusion = archivedConclusion;
164
+ // 自动 reopen 时,如果父节点有 conclusion,设置 stale
165
+ parentMeta.conclusionStale = true;
166
+ }
167
+ autoReopened = true;
168
+ // 同步更新 Info.md 中的状态和结论(使用父节点的 dirName)
169
+ const parentDirName = parentMeta.dirName || parentId; // 向后兼容
170
+ await this.md.updateNodeStatus(projectRoot, wsDirName, parentDirName, "planning");
171
+ if (archivedConclusion) {
172
+ await this.md.updateConclusion(projectRoot, wsDirName, parentDirName, archivedConclusion);
173
+ }
174
+ }
175
+ // 5. 验证父节点状态允许创建子节点(pending/planning/monitoring 状态)
176
+ const allowedStatuses = new Set(["pending", "planning", "monitoring"]);
177
+ if (!allowedStatuses.has(parentMeta.status)) {
178
+ throw new ZeroError("INVALID_PARENT_STATUS", `父节点状态 "${parentMeta.status}" 不允许创建子节点,需要处于 pending、planning 或 monitoring 状态`);
179
+ }
180
+ // 5.1 验证规则哈希(如果工作区有规则)
181
+ // 内部调用使用 INTERNAL_RULES_HASH 可绕过验证
182
+ const workspaceMdData = await this.md.readWorkspaceMd(projectRoot, wsDirName);
183
+ if (workspaceMdData.rules.length > 0 && params.rulesHash !== INTERNAL_RULES_HASH) {
184
+ const expectedHash = crypto.createHash("md5").update(workspaceMdData.rules.join("\n")).digest("hex").substring(0, 8);
185
+ if (params.rulesHash !== expectedHash) {
186
+ throw new ZeroError("RULES_HASH_MISMATCH", `工作区有 ${workspaceMdData.rules.length} 条规则,请先通过 workspace_get 或 context_get 获取 rulesHash,并在创建节点时传入。\n规则内容:\n${workspaceMdData.rules.map((r, i) => `${i + 1}. ${r}`).join("\n")}`);
187
+ }
188
+ }
189
+ // 6. 验证节点类型
190
+ if (!type || (type !== "planning" && type !== "execution")) {
191
+ throw new ZeroError("INVALID_NODE_TYPE", `节点类型 "${type}" 无效,必须是 "planning" 或 "execution"`);
192
+ }
193
+ // 6. 验证标题合法性
194
+ validateNodeTitle(title);
195
+ // 7. 生成节点 ID 和目录名
196
+ const nodeId = generateNodeId();
197
+ const nodeDirName = generateNodeDirName(title, nodeId);
198
+ const currentTime = now();
199
+ // 8. 创建节点目录(使用可读目录名)
200
+ const nodePath = this.fs.getNodePath(projectRoot, wsDirName, nodeDirName);
201
+ await this.fs.mkdir(nodePath);
202
+ // 9. 写入 Info.md
203
+ // 将字面量 \\n 转换为真正的换行符(MCP 工具调用时可能传入转义字符串)
204
+ const normalizedRequirement = requirement.replace(/\\n/g, "\n");
205
+ const nodeInfo = {
206
+ id: nodeId,
207
+ type,
208
+ title,
209
+ status: "pending",
210
+ createdAt: currentTime,
211
+ updatedAt: currentTime,
212
+ requirement: normalizedRequirement,
213
+ docs,
214
+ notes: "",
215
+ conclusion: "",
216
+ acceptanceCriteria,
217
+ };
218
+ await this.md.writeNodeInfo(projectRoot, wsDirName, nodeDirName, nodeInfo);
219
+ // 10. 创建空的 Log.md 和 Problem.md
220
+ await this.md.createEmptyLog(projectRoot, wsDirName, nodeDirName);
221
+ await this.md.createEmptyProblem(projectRoot, wsDirName, nodeDirName);
222
+ // 11. 更新 graph.json
223
+ const newNode = {
224
+ id: nodeId,
225
+ dirName: nodeDirName,
226
+ type,
227
+ parentId,
228
+ children: [],
229
+ status: "pending",
230
+ isolate: false,
231
+ references: [],
232
+ conclusion: null,
233
+ role, // 节点角色(可选)
234
+ acceptanceCriteria, // 验收标准(可选)
235
+ createdAt: currentTime,
236
+ updatedAt: currentTime,
237
+ };
238
+ graph.nodes[nodeId] = newNode;
239
+ graph.nodes[parentId].children.push(nodeId);
240
+ graph.nodes[parentId].updatedAt = currentTime;
241
+ // 11.1 处理测试节点附属化(isNeedTest=true)
242
+ let upgradedToPlanning = false;
243
+ let createdExecNodeId;
244
+ let createdTestNodeId;
245
+ if (isNeedTest && type === "execution") {
246
+ // 执行节点 + isNeedTest=true:升级为 planning 管理节点
247
+ upgradedToPlanning = true;
248
+ newNode.type = "planning";
249
+ // 更新 Info.md 中的类型
250
+ const updatedNodeInfo = {
251
+ id: nodeId,
252
+ type: "planning", // 升级为 planning
253
+ title: `[管理] ${title}`,
254
+ status: "pending",
255
+ createdAt: currentTime,
256
+ updatedAt: currentTime,
257
+ requirement: normalizedRequirement,
258
+ docs,
259
+ notes: "",
260
+ conclusion: "",
261
+ acceptanceCriteria,
262
+ };
263
+ await this.md.writeNodeInfo(projectRoot, wsDirName, nodeDirName, updatedNodeInfo);
264
+ // 创建执行子节点
265
+ const execNodeId = generateNodeId();
266
+ const execNodeDirName = generateNodeDirName(`[执行] ${title}`, execNodeId);
267
+ const execNodePath = this.fs.getNodePath(projectRoot, wsDirName, execNodeDirName);
268
+ await this.fs.mkdir(execNodePath);
269
+ const execNodeInfo = {
270
+ id: execNodeId,
271
+ type: "execution",
272
+ title: `[执行] ${title}`,
273
+ status: "pending",
274
+ createdAt: currentTime,
275
+ updatedAt: currentTime,
276
+ requirement: normalizedRequirement,
277
+ docs,
278
+ notes: "",
279
+ conclusion: "",
280
+ acceptanceCriteria,
281
+ };
282
+ await this.md.writeNodeInfo(projectRoot, wsDirName, execNodeDirName, execNodeInfo);
283
+ await this.md.createEmptyLog(projectRoot, wsDirName, execNodeDirName);
284
+ await this.md.createEmptyProblem(projectRoot, wsDirName, execNodeDirName);
285
+ const execNodeMeta = {
286
+ id: execNodeId,
287
+ dirName: execNodeDirName,
288
+ type: "execution",
289
+ parentId: nodeId, // 父节点是管理节点
290
+ children: [],
291
+ status: "pending",
292
+ isolate: false,
293
+ references: [],
294
+ conclusion: null,
295
+ acceptanceCriteria,
296
+ createdAt: currentTime,
297
+ updatedAt: currentTime,
298
+ };
299
+ graph.nodes[execNodeId] = execNodeMeta;
300
+ newNode.children.push(execNodeId);
301
+ createdExecNodeId = execNodeId;
302
+ // 创建测试子节点
303
+ const testNodeId = generateNodeId();
304
+ const testNodeDirName = generateNodeDirName(`[测试] ${title}`, testNodeId);
305
+ const testNodePath = this.fs.getNodePath(projectRoot, wsDirName, testNodeDirName);
306
+ await this.fs.mkdir(testNodePath);
307
+ const testNodeInfo = {
308
+ id: testNodeId,
309
+ type: "execution",
310
+ title: `[测试] ${title}`,
311
+ status: "pending",
312
+ createdAt: currentTime,
313
+ updatedAt: currentTime,
314
+ requirement: testRequirement || "(需要补充验收标准)",
315
+ docs: [],
316
+ notes: "",
317
+ conclusion: "",
318
+ };
319
+ await this.md.writeNodeInfo(projectRoot, wsDirName, testNodeDirName, testNodeInfo);
320
+ await this.md.createEmptyLog(projectRoot, wsDirName, testNodeDirName);
321
+ await this.md.createEmptyProblem(projectRoot, wsDirName, testNodeDirName);
322
+ const testNodeMeta = {
323
+ id: testNodeId,
324
+ dirName: testNodeDirName,
325
+ type: "execution",
326
+ parentId: nodeId, // 父节点是管理节点
327
+ children: [],
328
+ status: "pending",
329
+ isolate: false,
330
+ references: [],
331
+ conclusion: null,
332
+ createdAt: currentTime,
333
+ updatedAt: currentTime,
334
+ };
335
+ graph.nodes[testNodeId] = testNodeMeta;
336
+ newNode.children.push(testNodeId);
337
+ createdTestNodeId = testNodeId;
338
+ }
339
+ else if (isNeedTest && type === "planning") {
340
+ // 规划节点 + isNeedTest=true:创建测试子节点(集成测试)
341
+ const testNodeId = generateNodeId();
342
+ const integrationTestDirName = generateNodeDirName(`[集成测试] ${title}`, testNodeId);
343
+ const testNodePath = this.fs.getNodePath(projectRoot, wsDirName, integrationTestDirName);
344
+ await this.fs.mkdir(testNodePath);
345
+ const testNodeInfo = {
346
+ id: testNodeId,
347
+ type: "execution",
348
+ title: `[集成测试] ${title}`,
349
+ status: "pending",
350
+ createdAt: currentTime,
351
+ updatedAt: currentTime,
352
+ requirement: testRequirement || "(需要补充集成测试验收标准)",
353
+ docs: [],
354
+ notes: "",
355
+ conclusion: "",
356
+ };
357
+ await this.md.writeNodeInfo(projectRoot, wsDirName, integrationTestDirName, testNodeInfo);
358
+ await this.md.createEmptyLog(projectRoot, wsDirName, integrationTestDirName);
359
+ await this.md.createEmptyProblem(projectRoot, wsDirName, integrationTestDirName);
360
+ const testNodeMeta = {
361
+ id: testNodeId,
362
+ dirName: integrationTestDirName,
363
+ type: "execution",
364
+ parentId: nodeId, // 父节点是当前规划节点
365
+ children: [],
366
+ status: "pending",
367
+ isolate: false,
368
+ references: [],
369
+ conclusion: null,
370
+ createdAt: currentTime,
371
+ updatedAt: currentTime,
372
+ };
373
+ graph.nodes[testNodeId] = testNodeMeta;
374
+ newNode.children.push(testNodeId);
375
+ createdTestNodeId = testNodeId;
376
+ }
377
+ // 12. 自动状态转换:如果父节点是 pending/planning,创建第一个子节点时转为 monitoring
378
+ const isFirstChild = graph.nodes[parentId].children.length === 1;
379
+ if (isFirstChild && (parentMeta.status === "pending" || parentMeta.status === "planning")) {
380
+ graph.nodes[parentId].status = "monitoring";
381
+ // 同步更新 Info.md 中的状态(使用父节点的 dirName)
382
+ const pDirName = parentMeta.dirName || parentId; // 向后兼容
383
+ await this.md.updateNodeStatus(projectRoot, wsDirName, pDirName, "monitoring");
384
+ }
385
+ await this.json.writeGraph(projectRoot, wsDirName, graph);
386
+ // 12. 更新工作区 updatedAt
387
+ const config = await this.json.readWorkspaceConfig(projectRoot, wsDirName);
388
+ config.updatedAt = currentTime;
389
+ await this.json.writeWorkspaceConfig(projectRoot, wsDirName, config);
390
+ // 13. 同步更新索引中的 updatedAt
391
+ const index = await this.json.readIndex();
392
+ const wsEntry = index.workspaces.find(ws => ws.id === workspaceId);
393
+ if (wsEntry) {
394
+ wsEntry.updatedAt = currentTime;
395
+ await this.json.writeIndex(index);
396
+ }
397
+ // 14. 追加日志
398
+ let logEvent;
399
+ if (upgradedToPlanning) {
400
+ logEvent = `管理节点 "[管理] ${title}" (${nodeId}) 已创建,包含执行子节点 (${createdExecNodeId}) 和测试子节点 (${createdTestNodeId})`;
401
+ }
402
+ else if (isNeedTest && type === "planning") {
403
+ logEvent = `规划节点 "${title}" (${nodeId}) 已创建,包含集成测试子节点 (${createdTestNodeId})`;
404
+ }
405
+ else {
406
+ const typeLabel = newNode.type === "planning" ? "规划" : "执行";
407
+ logEvent = `${typeLabel}节点 "${title}" (${nodeId}) 已创建`;
408
+ }
409
+ await this.md.appendLog(projectRoot, wsDirName, {
410
+ time: currentTime,
411
+ operator: "system",
412
+ event: logEvent,
413
+ });
414
+ // 14. 生成提示
415
+ const hasDispatchedDocs = docs.length > 0;
416
+ let hint;
417
+ if (upgradedToPlanning) {
418
+ // isNeedTest=true 的执行节点已升级为管理节点
419
+ hint = `💡 已创建管理节点 "[管理] ${title}",自动生成了:\n` +
420
+ ` - [执行] 子节点 (${createdExecNodeId}):实际执行任务\n` +
421
+ ` - [测试] 子节点 (${createdTestNodeId}):验收测试\n` +
422
+ `下一步:调用 node_transition(action="start") 开始管理节点,然后派发 [执行] 子节点。`;
423
+ }
424
+ else if (isNeedTest && type === "planning") {
425
+ // 规划节点 + isNeedTest=true
426
+ hint = `💡 规划节点已创建,自动生成了集成测试子节点 (${createdTestNodeId})。\n` +
427
+ `下一步:调用 node_transition(action="start") 进入规划状态,创建执行子节点。所有执行完成后执行集成测试。`;
428
+ }
429
+ else if (newNode.type === "execution") {
430
+ hint = hasDispatchedDocs
431
+ ? "💡 执行节点已创建并派发了文档。下一步:调用 node_transition(action=\"start\") 开始执行。"
432
+ : "💡 执行节点已创建。提醒:如需参考文档请用 node_reference 添加。下一步:调用 node_transition(action=\"start\") 开始执行。";
433
+ }
434
+ else {
435
+ hint = hasDispatchedDocs
436
+ ? "💡 规划节点已创建并派发了文档。下一步:调用 node_transition(action=\"start\") 进入规划状态,分析需求并创建子节点。"
437
+ : "💡 规划节点已创建。下一步:调用 node_transition(action=\"start\") 进入规划状态。";
438
+ }
439
+ // 如果自动 reopen 了父节点,追加提示
440
+ if (autoReopened) {
441
+ hint = `⚠️ 父节点 ${parentId} 已自动从 completed 重开为 planning。` + hint;
442
+ }
443
+ // 14.1 如果工作区有规则,在 hint 末尾追加规则提醒
444
+ if (workspaceMdData.rules.length > 0) {
445
+ const rulesReminder = workspaceMdData.rules
446
+ .map((r, i) => ` ${i + 1}. ${r}`)
447
+ .join("\n");
448
+ hint += `\n\n📋 工作区规则提醒:\n${rulesReminder}`;
449
+ }
450
+ // 14.2 如果在根节点下创建非信息收集的子节点,提示需要用户确认计划
451
+ if (parentId === "root" && role !== "info_collection") {
452
+ hint += `\n\n⚠️ **重要**:完成所有计划节点创建后,请向用户展示完整计划并等待确认,再开始执行第一个任务。`;
453
+ }
454
+ // 生成引导内容
455
+ const guidanceContext = {
456
+ toolName: "node_create",
457
+ nodeType: type,
458
+ nodeRole: role,
459
+ toolInput: { type, role, parentId },
460
+ };
461
+ const guidance = this.guidanceService.generateFromContext(guidanceContext, 0);
462
+ // 构建返回结果
463
+ const result = {
464
+ nodeId,
465
+ path: nodePath,
466
+ autoReopened: autoReopened ? parentId : undefined,
467
+ hint,
468
+ // 测试节点附属化输出
469
+ upgradedToPlanning,
470
+ execNodeId: createdExecNodeId,
471
+ guidance: guidance.content,
472
+ testNodeId: createdTestNodeId,
473
+ };
474
+ // 如果在根节点下创建非信息收集的子节点,添加 show_plan actionRequired
475
+ if (parentId === "root" && role !== "info_collection") {
476
+ // 生成 confirmation token(如果 StateService 可用)
477
+ let confirmationToken;
478
+ if (this.stateService) {
479
+ const confirmation = this.stateService.createPendingConfirmation(workspaceId, nodeId, "show_plan", {
480
+ nodeId,
481
+ title,
482
+ type,
483
+ });
484
+ confirmationToken = confirmation.token;
485
+ }
486
+ result.actionRequired = {
487
+ type: "show_plan",
488
+ message: "已创建计划节点,请向用户展示当前计划并等待确认后再开始执行。",
489
+ data: {
490
+ nodeId,
491
+ title,
492
+ type,
493
+ },
494
+ confirmationToken,
495
+ };
496
+ }
497
+ // 推送 SSE 事件通知前端
498
+ eventService.emitNodeUpdate(workspaceId, nodeId);
499
+ return result;
500
+ }
501
+ /**
502
+ * 获取节点详情
503
+ */
504
+ async get(params) {
505
+ const { workspaceId, nodeId } = params;
506
+ // 获取工作区信息(包括归档状态和目录名)
507
+ const { projectRoot, wsDirName, isArchived } = await this.resolveWorkspaceInfo(workspaceId);
508
+ // 验证节点存在
509
+ const graph = await this.json.readGraph(projectRoot, wsDirName, isArchived);
510
+ if (!graph.nodes[nodeId]) {
511
+ throw new ZeroError("NODE_NOT_FOUND", `节点 "${nodeId}" 不存在`);
512
+ }
513
+ const meta = graph.nodes[nodeId];
514
+ const originalDirName = meta.dirName;
515
+ // 解析并验证节点目录名(运行时兜底机制)
516
+ const nodeDirName = await this.resolveNodeDirName(projectRoot, wsDirName, nodeId, meta, isArchived);
517
+ // 如果目录名被修复,保存更新后的 graph.json(仅非归档工作区)
518
+ if (!isArchived && meta.dirName !== originalDirName) {
519
+ await this.json.writeGraph(projectRoot, wsDirName, graph);
520
+ }
521
+ const infoMd = await this.md.readNodeInfoRaw(projectRoot, wsDirName, nodeDirName, isArchived);
522
+ // 读取并压缩日志:截取最新 5 条,简化格式
523
+ const MAX_LOG_ENTRIES = 5;
524
+ const logContent = await this.md.readLogRaw(projectRoot, wsDirName, nodeDirName, isArchived);
525
+ const logs = this.md.parseLogTable(logContent);
526
+ const recentLogs = logs.slice(-MAX_LOG_ENTRIES);
527
+ let logMd = "";
528
+ if (recentLogs.length > 0) {
529
+ const lines = recentLogs.map(log => {
530
+ const operator = log.operator !== "AI" ? `[${log.operator}] ` : "";
531
+ return `- [${log.timestamp}] ${operator}${log.event}`;
532
+ });
533
+ if (logs.length > MAX_LOG_ENTRIES) {
534
+ lines.unshift(`(共 ${logs.length} 条,显示最新 ${MAX_LOG_ENTRIES} 条)`);
535
+ }
536
+ logMd = lines.join("\n");
537
+ }
538
+ const problemMd = await this.md.readProblemRaw(projectRoot, wsDirName, nodeDirName, isArchived);
539
+ // 兼容旧数据:从 Info.md 补充缺失的 meta 字段
540
+ if (!meta.status || !meta.createdAt || !meta.updatedAt) {
541
+ const nodeInfo = await this.md.readNodeInfoFull(projectRoot, wsDirName, nodeDirName, isArchived);
542
+ if (!meta.status)
543
+ meta.status = nodeInfo.status;
544
+ if (!meta.createdAt)
545
+ meta.createdAt = nodeInfo.createdAt;
546
+ if (!meta.updatedAt)
547
+ meta.updatedAt = nodeInfo.updatedAt;
548
+ if (!meta.conclusion && nodeInfo.conclusion)
549
+ meta.conclusion = nodeInfo.conclusion;
550
+ if (!meta.references)
551
+ meta.references = [];
552
+ }
553
+ // 计算 nodeHash(用于先读后写校验)
554
+ const nodeInfoParsed = await this.md.readNodeInfo(projectRoot, wsDirName, nodeDirName, isArchived);
555
+ const nodeHash = computeNodeHash({
556
+ title: nodeInfoParsed.title,
557
+ requirement: nodeInfoParsed.requirement,
558
+ note: nodeInfoParsed.notes,
559
+ conclusion: nodeInfoParsed.conclusion,
560
+ });
561
+ return {
562
+ meta,
563
+ infoMd,
564
+ logMd,
565
+ problemMd,
566
+ nodeHash,
567
+ };
568
+ }
569
+ /**
570
+ * 获取节点树
571
+ */
572
+ async list(params) {
573
+ const { workspaceId, rootId, depth } = params;
574
+ // 获取工作区信息(包括归档状态和目录名)
575
+ const { projectRoot, wsDirName, isArchived } = await this.resolveWorkspaceInfo(workspaceId);
576
+ const graph = await this.json.readGraph(projectRoot, wsDirName, isArchived);
577
+ const config = await this.json.readWorkspaceConfig(projectRoot, wsDirName, isArchived);
578
+ // 确定根节点
579
+ const startId = rootId || config.rootNodeId;
580
+ if (!graph.nodes[startId]) {
581
+ throw new ZeroError("NODE_NOT_FOUND", `节点 "${startId}" 不存在`);
582
+ }
583
+ // 构建树
584
+ const tree = await this.buildNodeTree(projectRoot, wsDirName, graph, startId, 0, depth, isArchived);
585
+ return { tree };
586
+ }
587
+ /**
588
+ * 递归构建节点树
589
+ */
590
+ async buildNodeTree(projectRoot, wsDirName, graph, nodeId, currentDepth, maxDepth, isArchived = false) {
591
+ const node = graph.nodes[nodeId];
592
+ const nodeDirName = node.dirName || nodeId; // 向后兼容
593
+ const nodeInfo = await this.md.readNodeInfo(projectRoot, wsDirName, nodeDirName, isArchived);
594
+ const item = {
595
+ id: nodeId,
596
+ type: node.type,
597
+ title: nodeInfo.title,
598
+ status: node.status || nodeInfo.status, // 兼容旧数据:优先用 graph.json,fallback 到 Info.md
599
+ role: node.role,
600
+ dispatch: node.dispatch,
601
+ children: [],
602
+ };
603
+ // 检查深度限制
604
+ if (maxDepth !== undefined && currentDepth >= maxDepth) {
605
+ return item;
606
+ }
607
+ // 递归处理子节点
608
+ for (const childId of node.children) {
609
+ const childTree = await this.buildNodeTree(projectRoot, wsDirName, graph, childId, currentDepth + 1, maxDepth, isArchived);
610
+ item.children.push(childTree);
611
+ }
612
+ return item;
613
+ }
614
+ /**
615
+ * 删除节点及子树
616
+ */
617
+ async delete(params) {
618
+ const { workspaceId, nodeId } = params;
619
+ // 1. 获取 projectRoot 和 wsDirName
620
+ const { projectRoot, wsDirName } = await this.resolveProjectRoot(workspaceId);
621
+ // 2. 验证节点存在
622
+ const graph = await this.json.readGraph(projectRoot, wsDirName);
623
+ const config = await this.json.readWorkspaceConfig(projectRoot, wsDirName);
624
+ if (!graph.nodes[nodeId]) {
625
+ throw new ZeroError("NODE_NOT_FOUND", `节点 "${nodeId}" 不存在`);
626
+ }
627
+ // 3. 检查是否为根节点
628
+ if (nodeId === config.rootNodeId) {
629
+ throw new ZeroError("CANNOT_DELETE_ROOT", "无法删除根节点");
630
+ }
631
+ // 4. 递归收集所有子节点 ID
632
+ const deletedNodes = this.collectAllChildren(graph, nodeId);
633
+ // 5. 删除所有节点目录(使用节点的 dirName)
634
+ for (const id of deletedNodes) {
635
+ const nodeDirName = graph.nodes[id]?.dirName || id; // 向后兼容
636
+ const nodePath = this.fs.getNodePath(projectRoot, wsDirName, nodeDirName);
637
+ await this.fs.rmdir(nodePath);
638
+ }
639
+ // 6. 更新 graph.json
640
+ const currentTime = now();
641
+ const parentId = graph.nodes[nodeId].parentId;
642
+ // 从父节点的 children 中移除
643
+ if (parentId && graph.nodes[parentId]) {
644
+ graph.nodes[parentId].children = graph.nodes[parentId].children.filter(id => id !== nodeId);
645
+ graph.nodes[parentId].updatedAt = currentTime;
646
+ }
647
+ // 移除所有被删除的节点
648
+ for (const id of deletedNodes) {
649
+ delete graph.nodes[id];
650
+ }
651
+ // 清理其他节点中对被删除节点的引用
652
+ const deletedSet = new Set(deletedNodes);
653
+ for (const otherNodeId of Object.keys(graph.nodes)) {
654
+ const otherNode = graph.nodes[otherNodeId];
655
+ if (otherNode.references.length > 0) {
656
+ const originalLength = otherNode.references.length;
657
+ otherNode.references = otherNode.references.filter(refId => !deletedSet.has(refId));
658
+ if (otherNode.references.length < originalLength) {
659
+ otherNode.updatedAt = currentTime;
660
+ }
661
+ }
662
+ }
663
+ // 如果当前聚焦的节点被删除,重置聚焦
664
+ if (graph.currentFocus && deletedNodes.includes(graph.currentFocus)) {
665
+ graph.currentFocus = config.rootNodeId;
666
+ }
667
+ await this.json.writeGraph(projectRoot, wsDirName, graph);
668
+ // 7. 更新工作区 updatedAt
669
+ config.updatedAt = currentTime;
670
+ await this.json.writeWorkspaceConfig(projectRoot, wsDirName, config);
671
+ // 8. 同步更新索引中的 updatedAt
672
+ const index = await this.json.readIndex();
673
+ const wsEntry = index.workspaces.find(ws => ws.id === workspaceId);
674
+ if (wsEntry) {
675
+ wsEntry.updatedAt = currentTime;
676
+ await this.json.writeIndex(index);
677
+ }
678
+ // 9. 追加日志
679
+ await this.md.appendLog(projectRoot, wsDirName, {
680
+ time: currentTime,
681
+ operator: "system",
682
+ event: `节点 "${nodeId}" 及其 ${deletedNodes.length - 1} 个子节点已删除`,
683
+ });
684
+ // 推送 SSE 事件通知前端
685
+ eventService.emitNodeUpdate(workspaceId, nodeId);
686
+ return {
687
+ success: true,
688
+ deletedNodes,
689
+ };
690
+ }
691
+ /**
692
+ * 递归收集所有子节点 ID(包括自身)
693
+ */
694
+ collectAllChildren(graph, nodeId) {
695
+ const result = [nodeId];
696
+ const node = graph.nodes[nodeId];
697
+ if (node && node.children) {
698
+ for (const childId of node.children) {
699
+ result.push(...this.collectAllChildren(graph, childId));
700
+ }
701
+ }
702
+ return result;
703
+ }
704
+ // ========== Phase 3: 节点更新 ==========
705
+ /**
706
+ * 更新节点
707
+ */
708
+ async update(params) {
709
+ const { workspaceId, nodeId, nodeHash, title, requirement, note, conclusion, field, old_str, new_str, conclusionsHash } = params;
710
+ // 1. 获取 projectRoot 和 wsDirName
711
+ const { projectRoot, wsDirName } = await this.resolveProjectRoot(workspaceId);
712
+ // 3. 验证节点存在
713
+ const graph = await this.json.readGraph(projectRoot, wsDirName);
714
+ if (!graph.nodes[nodeId]) {
715
+ throw new ZeroError("NODE_NOT_FOUND", `节点 "${nodeId}" 不存在`);
716
+ }
717
+ // 4. 如果提供了新标题,验证合法性
718
+ if (title !== undefined) {
719
+ validateNodeTitle(title);
720
+ }
721
+ const currentTime = now();
722
+ let nodeDirName = graph.nodes[nodeId].dirName || nodeId; // 向后兼容
723
+ // 5. 读取现有 Info.md
724
+ const nodeInfo = await this.md.readNodeInfo(projectRoot, wsDirName, nodeDirName);
725
+ // 6. 如果提供了 nodeHash,进行先读后写校验(MCP 调用必须提供,内部调用可跳过)
726
+ if (nodeHash) {
727
+ const currentHash = computeNodeHash({
728
+ title: nodeInfo.title,
729
+ requirement: nodeInfo.requirement,
730
+ note: nodeInfo.notes,
731
+ conclusion: nodeInfo.conclusion,
732
+ });
733
+ if (currentHash !== nodeHash) {
734
+ throw new ZeroError("CONTENT_CHANGED", "内容已变更,请重新 node_get");
735
+ }
736
+ }
737
+ // 6.1 stale 节点更新 conclusion 时要求 conclusionsHash
738
+ const nodeMeta = graph.nodes[nodeId];
739
+ const isUpdatingConclusion = conclusion !== undefined || (field === "conclusion" && old_str !== undefined);
740
+ if (nodeMeta.conclusionStale && isUpdatingConclusion) {
741
+ if (!conclusionsHash) {
742
+ throw new ZeroError("CONCLUSIONS_HASH_REQUIRED", "结论已过期,更新前需要提供 conclusionsHash,请先调用 context_get 获取最新上下文。");
743
+ }
744
+ // 计算当前 conclusionsHash 并验证
745
+ const childConclusions = nodeMeta.children
746
+ .map(cid => {
747
+ const childMeta = graph.nodes[cid];
748
+ return childMeta ? { nodeId: cid, conclusion: childMeta.conclusion || "" } : null;
749
+ })
750
+ .filter((c) => c !== null && !!c.conclusion);
751
+ const currentContextHash = computeConclusionsHash(childConclusions);
752
+ if (conclusionsHash !== currentContextHash) {
753
+ throw new ZeroError("CONCLUSIONS_HASH_MISMATCH", "conclusionsHash 不匹配,子节点结论可能已变化。请重新调用 context_get 获取最新上下文。");
754
+ }
755
+ }
756
+ // 7. 处理精确替换逻辑(field + old_str + new_str)
757
+ const updates = [];
758
+ let titleChanged = false;
759
+ if (field && old_str !== undefined && new_str !== undefined) {
760
+ // 精确替换模式
761
+ const fieldKey = field === "note" ? "notes" : field;
762
+ const targetContent = nodeInfo[fieldKey] || "";
763
+ // 使用正则计算匹配次数(转义特殊字符)
764
+ const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
765
+ const regex = new RegExp(escapeRegExp(old_str), "g");
766
+ const matches = targetContent.match(regex);
767
+ const count = matches ? matches.length : 0;
768
+ if (count === 0) {
769
+ throw new ZeroError("NO_MATCH", "未找到匹配内容");
770
+ }
771
+ if (count > 1) {
772
+ throw new ZeroError("MULTI_MATCH", `找到 ${count} 处匹配,请提供更多上下文`);
773
+ }
774
+ // 执行替换
775
+ const newContent = targetContent.replace(old_str, new_str);
776
+ nodeInfo[fieldKey] = newContent;
777
+ updates.push(`${field} 精确替换`);
778
+ }
779
+ else {
780
+ // 传统整体更新模式
781
+ if (title !== undefined && title !== nodeInfo.title) {
782
+ nodeInfo.title = title;
783
+ updates.push(`标题: "${title}"`);
784
+ titleChanged = true;
785
+ }
786
+ if (requirement !== undefined && requirement !== nodeInfo.requirement) {
787
+ nodeInfo.requirement = requirement;
788
+ updates.push("需求描述");
789
+ }
790
+ if (note !== undefined && note !== nodeInfo.notes) {
791
+ nodeInfo.notes = note;
792
+ updates.push("备注");
793
+ }
794
+ if (conclusion !== undefined && conclusion !== nodeInfo.conclusion) {
795
+ nodeInfo.conclusion = conclusion;
796
+ updates.push("结论");
797
+ }
798
+ }
799
+ // 如果没有任何更新,直接返回
800
+ if (updates.length === 0) {
801
+ return {
802
+ success: true,
803
+ updatedAt: nodeInfo.updatedAt,
804
+ };
805
+ }
806
+ // 6. 如果标题改变,同步更新目录名(非 root 节点)
807
+ if (titleChanged && nodeId !== "root") {
808
+ const newDirName = generateNodeDirName(title, nodeId);
809
+ if (newDirName !== nodeDirName) {
810
+ const nodesDir = this.fs.getNodesDir(projectRoot, wsDirName);
811
+ const oldPath = this.fs.getNodePath(projectRoot, wsDirName, nodeDirName);
812
+ // 安全重命名(处理冲突)
813
+ const actualDirName = await this.fs.safeRenameDir(oldPath, nodesDir, newDirName);
814
+ nodeDirName = actualDirName;
815
+ graph.nodes[nodeId].dirName = actualDirName;
816
+ }
817
+ }
818
+ // 7. 更新时间戳
819
+ nodeInfo.updatedAt = currentTime;
820
+ // 8. 写入 Info.md(使用可能已更新的目录名)
821
+ await this.md.writeNodeInfo(projectRoot, wsDirName, nodeDirName, nodeInfo);
822
+ // 9. 更新 graph.json 的 updatedAt 和 conclusion
823
+ graph.nodes[nodeId].updatedAt = currentTime;
824
+ if (conclusion !== undefined) {
825
+ graph.nodes[nodeId].conclusion = conclusion || null;
826
+ }
827
+ await this.json.writeGraph(projectRoot, wsDirName, graph);
828
+ // 10. 追加日志
829
+ await this.md.appendLog(projectRoot, wsDirName, {
830
+ time: currentTime,
831
+ operator: "AI",
832
+ event: `更新节点: ${updates.join(", ")}`,
833
+ }, nodeDirName);
834
+ // 推送 SSE 事件通知前端
835
+ eventService.emitNodeUpdate(workspaceId, nodeId);
836
+ return {
837
+ success: true,
838
+ updatedAt: currentTime,
839
+ };
840
+ }
841
+ /**
842
+ * 移动节点到新的父节点
843
+ */
844
+ async move(params) {
845
+ const { workspaceId, nodeId, newParentId } = params;
846
+ // 1. 获取 projectRoot 和 wsDirName
847
+ const { projectRoot, wsDirName } = await this.resolveProjectRoot(workspaceId);
848
+ // 2. 读取图结构
849
+ const graph = await this.json.readGraph(projectRoot, wsDirName);
850
+ // 3. 验证节点存在
851
+ const nodeMeta = graph.nodes[nodeId];
852
+ if (!nodeMeta) {
853
+ throw new ZeroError("NODE_NOT_FOUND", `节点 "${nodeId}" 不存在`);
854
+ }
855
+ // 4. 不能移动根节点
856
+ if (nodeId === "root") {
857
+ throw new ZeroError("INVALID_TRANSITION", "根节点无法移动");
858
+ }
859
+ // 5. 验证新父节点存在
860
+ const newParentMeta = graph.nodes[newParentId];
861
+ if (!newParentMeta) {
862
+ throw new ZeroError("PARENT_NOT_FOUND", `目标父节点 "${newParentId}" 不存在`);
863
+ }
864
+ // 5.1 验证新父节点是规划节点(执行节点不能有子节点)
865
+ if (newParentMeta.type === "execution") {
866
+ throw new ZeroError("EXECUTION_CANNOT_HAVE_CHILDREN", "执行节点不能有子节点,无法将节点移动到执行节点下");
867
+ }
868
+ // 6. 防止循环依赖:不能把节点移到自己的子节点下
869
+ const isDescendant = (ancestorId, descendantId) => {
870
+ const ancestor = graph.nodes[ancestorId];
871
+ if (!ancestor)
872
+ return false;
873
+ for (const childId of ancestor.children) {
874
+ if (childId === descendantId)
875
+ return true;
876
+ if (isDescendant(childId, descendantId))
877
+ return true;
878
+ }
879
+ return false;
880
+ };
881
+ if (isDescendant(nodeId, newParentId)) {
882
+ throw new ZeroError("INVALID_TRANSITION", "不能将节点移动到其子节点下");
883
+ }
884
+ // 7. 如果已经在目标父节点下,无需移动
885
+ const previousParentId = nodeMeta.parentId;
886
+ if (previousParentId === newParentId) {
887
+ return {
888
+ success: true,
889
+ previousParentId,
890
+ newParentId,
891
+ };
892
+ }
893
+ const currentTime = now();
894
+ // 8. 从旧父节点的 children 中移除
895
+ if (previousParentId && graph.nodes[previousParentId]) {
896
+ graph.nodes[previousParentId].children = graph.nodes[previousParentId].children.filter((id) => id !== nodeId);
897
+ graph.nodes[previousParentId].updatedAt = currentTime;
898
+ }
899
+ // 9. 添加到新父节点的 children
900
+ newParentMeta.children.push(nodeId);
901
+ newParentMeta.updatedAt = currentTime;
902
+ // 10. 更新节点的 parentId
903
+ nodeMeta.parentId = newParentId;
904
+ nodeMeta.updatedAt = currentTime;
905
+ // 11. 保存图结构
906
+ await this.json.writeGraph(projectRoot, wsDirName, graph);
907
+ // 12. 读取节点 Info.md 获取标题用于日志
908
+ const nodeDirName = nodeMeta.dirName || nodeId; // 向后兼容
909
+ const nodeInfo = await this.md.readNodeInfo(projectRoot, wsDirName, nodeDirName);
910
+ // 13. 记录日志
911
+ await this.md.appendLog(projectRoot, wsDirName, {
912
+ time: currentTime,
913
+ operator: "AI",
914
+ event: `移动节点 "${nodeInfo.title}" 到 ${newParentId === "root" ? "根节点" : newParentId}`,
915
+ }, nodeDirName);
916
+ // 推送 SSE 事件通知前端
917
+ eventService.emitNodeUpdate(workspaceId, nodeId);
918
+ return {
919
+ success: true,
920
+ previousParentId,
921
+ newParentId,
922
+ };
923
+ }
924
+ /**
925
+ * 重新排序节点的子节点
926
+ * @param workspaceId 工作区 ID
927
+ * @param nodeId 父节点 ID
928
+ * @param orderedChildIds 排序后的子节点 ID 数组
929
+ */
930
+ async reorderChildren(params) {
931
+ const { workspaceId, nodeId, orderedChildIds } = params;
932
+ // 1. 获取 projectRoot 和 wsDirName
933
+ const { projectRoot, wsDirName } = await this.resolveProjectRoot(workspaceId);
934
+ // 2. 读取图结构
935
+ const graph = await this.json.readGraph(projectRoot, wsDirName);
936
+ // 3. 验证节点存在
937
+ const nodeMeta = graph.nodes[nodeId];
938
+ if (!nodeMeta) {
939
+ throw new ZeroError("NODE_NOT_FOUND", `节点 "${nodeId}" 不存在`);
940
+ }
941
+ // 4. 验证所有子节点 ID 都在 orderedChildIds 中
942
+ const currentChildren = new Set(nodeMeta.children);
943
+ const newChildren = new Set(orderedChildIds);
944
+ if (currentChildren.size !== newChildren.size) {
945
+ throw new ZeroError("INVALID_TRANSITION", "子节点数量不匹配");
946
+ }
947
+ for (const childId of orderedChildIds) {
948
+ if (!currentChildren.has(childId)) {
949
+ throw new ZeroError("NODE_NOT_FOUND", `子节点 "${childId}" 不存在`);
950
+ }
951
+ }
952
+ // 5. 更新子节点顺序
953
+ nodeMeta.children = orderedChildIds;
954
+ // 6. 保存图结构
955
+ await this.json.writeGraph(projectRoot, wsDirName, graph);
956
+ }
957
+ }
958
+ //# sourceMappingURL=NodeService.js.map