principles-disciple 1.8.1 → 1.8.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 (470) hide show
  1. package/ADVANCED_CONFIG_ZH.md +97 -0
  2. package/AGENT_INSTALL.md +173 -0
  3. package/AGENT_INSTALL_EN.md +173 -0
  4. package/INSTALL.md +256 -0
  5. package/SKILL.md +63 -0
  6. package/docs/COMMAND_REFERENCE.md +76 -0
  7. package/docs/COMMAND_REFERENCE_EN.md +79 -0
  8. package/esbuild.config.js +75 -0
  9. package/openclaw.plugin.json +4 -4
  10. package/package.json +11 -13
  11. package/scripts/build-web.mjs +46 -0
  12. package/scripts/install-dependencies.cjs +47 -0
  13. package/scripts/sync-plugin.mjs +802 -0
  14. package/scripts/verify-build.mjs +109 -0
  15. package/src/agents/nocturnal-dreamer.md +152 -0
  16. package/src/agents/nocturnal-philosopher.md +138 -0
  17. package/src/agents/nocturnal-reflector.md +126 -0
  18. package/src/agents/nocturnal-scribe.md +164 -0
  19. package/src/commands/capabilities.ts +85 -0
  20. package/{dist/commands/context.js → src/commands/context.ts} +78 -38
  21. package/src/commands/evolution-status.ts +146 -0
  22. package/src/commands/export.ts +111 -0
  23. package/src/commands/focus.ts +533 -0
  24. package/src/commands/nocturnal-review.ts +311 -0
  25. package/src/commands/nocturnal-rollout.ts +763 -0
  26. package/src/commands/nocturnal-train.ts +1002 -0
  27. package/{dist/commands/pain.js → src/commands/pain.ts} +68 -49
  28. package/src/commands/principle-rollback.ts +27 -0
  29. package/{dist/commands/rollback.js → src/commands/rollback.ts} +44 -12
  30. package/src/commands/samples.ts +60 -0
  31. package/src/commands/strategy.ts +38 -0
  32. package/{dist/commands/thinking-os.js → src/commands/thinking-os.ts} +59 -36
  33. package/src/commands/workflow-debug.ts +128 -0
  34. package/{dist/config/defaults/runtime.js → src/config/defaults/runtime.ts} +12 -5
  35. package/src/config/errors.ts +163 -0
  36. package/{dist/config/index.d.ts → src/config/index.ts} +2 -1
  37. package/src/constants/diagnostician.ts +66 -0
  38. package/src/constants/tools.ts +62 -0
  39. package/src/core/adaptive-thresholds.ts +476 -0
  40. package/{dist/core/config-service.js → src/core/config-service.ts} +7 -4
  41. package/{dist/core/config.js → src/core/config.ts} +158 -46
  42. package/src/core/control-ui-db.ts +435 -0
  43. package/{dist/core/detection-funnel.js → src/core/detection-funnel.ts} +36 -21
  44. package/{dist/core/detection-service.js → src/core/detection-service.ts} +7 -4
  45. package/{dist/core/dictionary-service.js → src/core/dictionary-service.ts} +7 -4
  46. package/{dist/core/dictionary.js → src/core/dictionary.ts} +57 -34
  47. package/src/core/empathy-keyword-matcher.ts +327 -0
  48. package/src/core/empathy-types.ts +218 -0
  49. package/src/core/event-log.ts +544 -0
  50. package/src/core/evolution-engine.ts +612 -0
  51. package/src/core/evolution-logger.ts +353 -0
  52. package/src/core/evolution-migration.ts +77 -0
  53. package/src/core/evolution-reducer.ts +731 -0
  54. package/src/core/evolution-types.ts +456 -0
  55. package/src/core/external-training-contract.ts +527 -0
  56. package/src/core/focus-history.ts +1458 -0
  57. package/src/core/hygiene/tracker.ts +117 -0
  58. package/{dist/core/init.js → src/core/init.ts} +39 -26
  59. package/src/core/local-worker-routing.ts +617 -0
  60. package/{dist/core/migration.js → src/core/migration.ts} +18 -11
  61. package/src/core/model-deployment-registry.ts +722 -0
  62. package/src/core/model-training-registry.ts +813 -0
  63. package/src/core/nocturnal-arbiter.ts +706 -0
  64. package/src/core/nocturnal-candidate-scoring.ts +392 -0
  65. package/src/core/nocturnal-compliance.ts +1075 -0
  66. package/src/core/nocturnal-dataset.ts +668 -0
  67. package/src/core/nocturnal-executability.ts +428 -0
  68. package/src/core/nocturnal-export.ts +390 -0
  69. package/{dist/core/nocturnal-paths.js → src/core/nocturnal-paths.ts} +49 -23
  70. package/src/core/nocturnal-trajectory-extractor.ts +484 -0
  71. package/src/core/nocturnal-trinity.ts +1384 -0
  72. package/src/core/pain.ts +122 -0
  73. package/{dist/core/path-resolver.js → src/core/path-resolver.ts} +157 -36
  74. package/{dist/core/paths.js → src/core/paths.ts} +13 -4
  75. package/src/core/principle-training-state.ts +450 -0
  76. package/src/core/profile.ts +226 -0
  77. package/src/core/promotion-gate.ts +822 -0
  78. package/{dist/core/risk-calculator.js → src/core/risk-calculator.ts} +42 -16
  79. package/{dist/core/session-tracker.js → src/core/session-tracker.ts} +175 -62
  80. package/src/core/shadow-observation-registry.ts +534 -0
  81. package/{dist/core/system-logger.js → src/core/system-logger.ts} +9 -5
  82. package/src/core/thinking-models.ts +217 -0
  83. package/src/core/training-program.ts +630 -0
  84. package/src/core/trajectory-types.ts +243 -0
  85. package/src/core/trajectory.ts +1673 -0
  86. package/{dist/core/workspace-context.js → src/core/workspace-context.ts} +57 -32
  87. package/src/hooks/bash-risk.ts +171 -0
  88. package/src/hooks/edit-verification.ts +295 -0
  89. package/src/hooks/gate-block-helper.ts +160 -0
  90. package/src/hooks/gate.ts +210 -0
  91. package/src/hooks/gfi-gate.ts +177 -0
  92. package/src/hooks/lifecycle.ts +326 -0
  93. package/{dist/hooks/llm.js → src/hooks/llm.ts} +160 -80
  94. package/src/hooks/message-sanitize.ts +45 -0
  95. package/src/hooks/pain.ts +384 -0
  96. package/src/hooks/progressive-trust-gate.ts +174 -0
  97. package/src/hooks/prompt.ts +920 -0
  98. package/src/hooks/subagent.ts +207 -0
  99. package/src/hooks/thinking-checkpoint.ts +73 -0
  100. package/src/hooks/trajectory-collector.ts +290 -0
  101. package/src/http/principles-console-route.ts +716 -0
  102. package/src/i18n/commands.ts +117 -0
  103. package/src/index.ts +694 -0
  104. package/src/service/central-database.ts +831 -0
  105. package/src/service/control-ui-query-service.ts +888 -0
  106. package/src/service/evolution-query-service.ts +405 -0
  107. package/src/service/evolution-worker.ts +1646 -0
  108. package/src/service/health-query-service.ts +836 -0
  109. package/{dist/service/nocturnal-runtime.js → src/service/nocturnal-runtime.ts} +235 -79
  110. package/src/service/nocturnal-service.ts +1015 -0
  111. package/src/service/nocturnal-target-selector.ts +532 -0
  112. package/src/service/phase3-input-filter.ts +237 -0
  113. package/src/service/runtime-summary-service.ts +757 -0
  114. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +513 -0
  115. package/{dist/service/subagent-workflow/empathy-observer-workflow-manager.js → src/service/subagent-workflow/empathy-observer-workflow-manager.ts} +240 -117
  116. package/src/service/subagent-workflow/index.ts +51 -0
  117. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +856 -0
  118. package/src/service/subagent-workflow/runtime-direct-driver.ts +166 -0
  119. package/{dist/service/subagent-workflow/types.d.ts → src/service/subagent-workflow/types.ts} +137 -18
  120. package/src/service/subagent-workflow/workflow-store.ts +328 -0
  121. package/src/service/trajectory-service.ts +15 -0
  122. package/{dist/tools/critique-prompt.js → src/tools/critique-prompt.ts} +25 -8
  123. package/src/tools/deep-reflect.ts +349 -0
  124. package/{dist/tools/model-index.js → src/tools/model-index.ts} +33 -17
  125. package/src/types/event-types.ts +453 -0
  126. package/src/types/hygiene-types.ts +31 -0
  127. package/src/types/principle-tree-schema.ts +244 -0
  128. package/src/types/runtime-summary.ts +49 -0
  129. package/src/types.ts +74 -0
  130. package/src/utils/file-lock.ts +391 -0
  131. package/{dist/utils/glob-match.js → src/utils/glob-match.ts} +21 -20
  132. package/{dist/utils/hashing.js → src/utils/hashing.ts} +6 -4
  133. package/src/utils/io.ts +110 -0
  134. package/{dist/utils/nlp.js → src/utils/nlp.ts} +19 -12
  135. package/{dist/utils/plugin-logger.js → src/utils/plugin-logger.ts} +33 -8
  136. package/src/utils/subagent-probe.ts +94 -0
  137. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +70 -1
  138. package/templates/pain_settings.json +2 -1
  139. package/tests/README.md +120 -0
  140. package/tests/build-artifacts.test.ts +111 -0
  141. package/tests/commands/evolution-status.test.ts +222 -0
  142. package/tests/commands/evolver.test.ts +22 -0
  143. package/tests/commands/export.test.ts +78 -0
  144. package/tests/commands/nocturnal-review.test.ts +448 -0
  145. package/tests/commands/nocturnal-train.test.ts +97 -0
  146. package/tests/commands/pain.test.ts +108 -0
  147. package/tests/commands/samples.test.ts +65 -0
  148. package/tests/commands/strategy.test.ts +34 -0
  149. package/tests/commands/thinking-os.test.ts +88 -0
  150. package/tests/core/adaptive-thresholds.test.ts +261 -0
  151. package/tests/core/config-service.test.ts +89 -0
  152. package/tests/core/config.test.ts +90 -0
  153. package/tests/core/control-ui-db.test.ts +75 -0
  154. package/tests/core/core-template-guidance.test.ts +21 -0
  155. package/tests/core/detection-funnel.test.ts +63 -0
  156. package/tests/core/detection-service.test.ts +50 -0
  157. package/tests/core/dictionary-service.test.ts +116 -0
  158. package/tests/core/dictionary.test.ts +168 -0
  159. package/tests/core/empathy-keyword-matcher.test.ts +209 -0
  160. package/tests/core/event-log.test.ts +181 -0
  161. package/tests/core/evolution-e2e.test.ts +58 -0
  162. package/tests/core/evolution-engine-gate-integration.test.ts +543 -0
  163. package/tests/core/evolution-engine.test.ts +562 -0
  164. package/tests/core/evolution-logger.test.ts +148 -0
  165. package/tests/core/evolution-migration.test.ts +50 -0
  166. package/tests/core/evolution-paths.test.ts +21 -0
  167. package/tests/core/evolution-reducer.detector-metadata.test.ts +602 -0
  168. package/tests/core/evolution-reducer.test.ts +180 -0
  169. package/tests/core/evolution-types-loop.test.ts +48 -0
  170. package/tests/core/evolution-user-stories.e2e.test.ts +249 -0
  171. package/tests/core/external-training-contract.test.ts +463 -0
  172. package/tests/core/focus-history.test.ts +682 -0
  173. package/tests/core/init-flatten.test.ts +69 -0
  174. package/tests/core/init-refactor.test.ts +87 -0
  175. package/tests/core/init-v1.3.test.ts +46 -0
  176. package/tests/core/init.test.ts +190 -0
  177. package/tests/core/local-worker-routing.test.ts +757 -0
  178. package/tests/core/migration.test.ts +84 -0
  179. package/tests/core/model-deployment-registry.test.ts +845 -0
  180. package/tests/core/model-training-registry.test.ts +889 -0
  181. package/tests/core/nocturnal-arbiter.test.ts +494 -0
  182. package/tests/core/nocturnal-candidate-scoring.test.ts +400 -0
  183. package/tests/core/nocturnal-compliance.test.ts +646 -0
  184. package/tests/core/nocturnal-dataset.test.ts +892 -0
  185. package/tests/core/nocturnal-executability.test.ts +357 -0
  186. package/tests/core/nocturnal-export.test.ts +462 -0
  187. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +428 -0
  188. package/tests/core/nocturnal-trajectory-extractor.test.ts +634 -0
  189. package/tests/core/nocturnal-trinity.test.ts +953 -0
  190. package/tests/core/pain.test.ts +33 -0
  191. package/tests/core/path-resolver.test.ts +57 -0
  192. package/tests/core/paths-refactor.test.ts +42 -0
  193. package/tests/core/phase7-rollout-integration.test.ts +477 -0
  194. package/tests/core/principle-training-state.test.ts +712 -0
  195. package/tests/core/profile.test.ts +56 -0
  196. package/tests/core/promotion-gate.test.ts +556 -0
  197. package/tests/core/risk-calculator.test.ts +168 -0
  198. package/tests/core/session-tracker.test.ts +191 -0
  199. package/tests/core/training-program.test.ts +472 -0
  200. package/tests/core/trajectory.test.ts +265 -0
  201. package/tests/core/workspace-context-factory.test.ts +18 -0
  202. package/tests/core/workspace-context.test.ts +134 -0
  203. package/tests/fixtures/nocturnal-reviewed-subset.json +183 -0
  204. package/tests/fixtures/production-compatibility.test.ts +147 -0
  205. package/tests/fixtures/production-mock-generator.ts +282 -0
  206. package/tests/hooks/bash-risk-integration.test.ts +137 -0
  207. package/tests/hooks/bash-risk.test.ts +81 -0
  208. package/tests/hooks/edit-verification.test.ts +678 -0
  209. package/tests/hooks/gate-edit-verification-p1.test.ts +632 -0
  210. package/tests/hooks/gate-edit-verification.test.ts +435 -0
  211. package/tests/hooks/gate-pipeline-integration.test.ts +404 -0
  212. package/tests/hooks/gate.test.ts +271 -0
  213. package/tests/hooks/gfi-gate-unit.test.ts +422 -0
  214. package/tests/hooks/gfi-gate.test.ts +669 -0
  215. package/tests/hooks/lifecycle.test.ts +248 -0
  216. package/tests/hooks/llm.test.ts +308 -0
  217. package/tests/hooks/message-sanitize.test.ts +36 -0
  218. package/tests/hooks/pain.test.ts +141 -0
  219. package/tests/hooks/progressive-trust-gate.test.ts +277 -0
  220. package/tests/hooks/prompt.test.ts +1411 -0
  221. package/tests/hooks/subagent.test.ts +467 -0
  222. package/tests/hooks/thinking-gate.test.ts +313 -0
  223. package/tests/http/principles-console-route.test.ts +140 -0
  224. package/tests/hygiene-tracker.test.ts +77 -0
  225. package/tests/index.integration.test.ts +179 -0
  226. package/tests/index.shadow-routing.integration.test.ts +140 -0
  227. package/tests/index.test.ts +9 -0
  228. package/tests/integration/empathy-workflow-integration.test.ts +627 -0
  229. package/tests/service/control-ui-query-service.test.ts +121 -0
  230. package/tests/service/empathy-observer-workflow-manager.test.ts +176 -0
  231. package/tests/service/evolution-worker.test.ts +585 -0
  232. package/tests/service/nocturnal-runtime.test.ts +470 -0
  233. package/tests/service/nocturnal-service.test.ts +577 -0
  234. package/tests/service/nocturnal-target-selector.test.ts +615 -0
  235. package/tests/service/nocturnal-workflow-manager.test.ts +439 -0
  236. package/tests/service/phase3-input-filter.test.ts +289 -0
  237. package/tests/service/runtime-summary-service.test.ts +919 -0
  238. package/tests/task-compliance.test.ts +166 -0
  239. package/tests/test-utils.ts +48 -0
  240. package/tests/tools/critique-prompt.test.ts +260 -0
  241. package/tests/tools/deep-reflect.test.ts +232 -0
  242. package/tests/tools/model-index.test.ts +246 -0
  243. package/tests/ui/app.test.tsx +114 -0
  244. package/tests/utils/file-lock.test.ts +407 -0
  245. package/tests/utils/hashing.test.ts +32 -0
  246. package/tests/utils/io.test.ts +39 -0
  247. package/tests/utils/nlp.test.ts +53 -0
  248. package/tests/utils/plugin-logger.test.ts +156 -0
  249. package/tsconfig.json +16 -0
  250. package/tsconfig.tsbuildinfo +1 -0
  251. package/ui/src/App.tsx +45 -0
  252. package/ui/src/api.ts +216 -0
  253. package/ui/src/charts.tsx +586 -0
  254. package/ui/src/components/ErrorState.tsx +6 -0
  255. package/ui/src/components/Loading.tsx +13 -0
  256. package/ui/src/components/ProtectedRoute.tsx +12 -0
  257. package/ui/src/components/Shell.tsx +91 -0
  258. package/ui/src/components/WorkspaceConfig.tsx +146 -0
  259. package/ui/src/components/index.ts +5 -0
  260. package/ui/src/context/auth.tsx +80 -0
  261. package/ui/src/context/theme.tsx +66 -0
  262. package/ui/src/hooks/useAutoRefresh.ts +39 -0
  263. package/ui/src/i18n/ui.ts +363 -0
  264. package/ui/src/main.tsx +16 -0
  265. package/ui/src/pages/EvolutionPage.tsx +352 -0
  266. package/ui/src/pages/FeedbackPage.tsx +140 -0
  267. package/ui/src/pages/GateMonitorPage.tsx +136 -0
  268. package/ui/src/pages/LoginPage.tsx +88 -0
  269. package/ui/src/pages/OverviewPage.tsx +238 -0
  270. package/ui/src/pages/SamplesPage.tsx +174 -0
  271. package/ui/src/pages/ThinkingModelsPage.tsx +127 -0
  272. package/ui/src/styles.css +1661 -0
  273. package/ui/src/types.ts +368 -0
  274. package/ui/src/utils/format.ts +15 -0
  275. package/vitest.config.ts +23 -0
  276. package/dist/commands/capabilities.d.ts +0 -3
  277. package/dist/commands/capabilities.js +0 -73
  278. package/dist/commands/context.d.ts +0 -5
  279. package/dist/commands/evolution-status.d.ts +0 -4
  280. package/dist/commands/evolution-status.js +0 -117
  281. package/dist/commands/evolver.d.ts +0 -9
  282. package/dist/commands/evolver.js +0 -26
  283. package/dist/commands/export.d.ts +0 -2
  284. package/dist/commands/export.js +0 -98
  285. package/dist/commands/focus.d.ts +0 -14
  286. package/dist/commands/focus.js +0 -457
  287. package/dist/commands/nocturnal-review.d.ts +0 -24
  288. package/dist/commands/nocturnal-review.js +0 -265
  289. package/dist/commands/nocturnal-rollout.d.ts +0 -27
  290. package/dist/commands/nocturnal-rollout.js +0 -671
  291. package/dist/commands/nocturnal-train.d.ts +0 -25
  292. package/dist/commands/nocturnal-train.js +0 -919
  293. package/dist/commands/pain.d.ts +0 -5
  294. package/dist/commands/principle-rollback.d.ts +0 -4
  295. package/dist/commands/principle-rollback.js +0 -22
  296. package/dist/commands/rollback.d.ts +0 -19
  297. package/dist/commands/samples.d.ts +0 -2
  298. package/dist/commands/samples.js +0 -55
  299. package/dist/commands/strategy.d.ts +0 -3
  300. package/dist/commands/strategy.js +0 -29
  301. package/dist/commands/thinking-os.d.ts +0 -2
  302. package/dist/config/defaults/runtime.d.ts +0 -40
  303. package/dist/config/errors.d.ts +0 -84
  304. package/dist/config/errors.js +0 -94
  305. package/dist/config/index.js +0 -7
  306. package/dist/constants/diagnostician.d.ts +0 -12
  307. package/dist/constants/diagnostician.js +0 -56
  308. package/dist/constants/tools.d.ts +0 -17
  309. package/dist/constants/tools.js +0 -54
  310. package/dist/core/adaptive-thresholds.d.ts +0 -186
  311. package/dist/core/adaptive-thresholds.js +0 -300
  312. package/dist/core/config-service.d.ts +0 -15
  313. package/dist/core/config.d.ts +0 -129
  314. package/dist/core/control-ui-db.d.ts +0 -95
  315. package/dist/core/control-ui-db.js +0 -292
  316. package/dist/core/detection-funnel.d.ts +0 -33
  317. package/dist/core/detection-service.d.ts +0 -15
  318. package/dist/core/dictionary-service.d.ts +0 -15
  319. package/dist/core/dictionary.d.ts +0 -38
  320. package/dist/core/event-log.d.ts +0 -82
  321. package/dist/core/event-log.js +0 -463
  322. package/dist/core/evolution-engine.d.ts +0 -118
  323. package/dist/core/evolution-engine.js +0 -464
  324. package/dist/core/evolution-logger.d.ts +0 -137
  325. package/dist/core/evolution-logger.js +0 -256
  326. package/dist/core/evolution-migration.d.ts +0 -5
  327. package/dist/core/evolution-migration.js +0 -65
  328. package/dist/core/evolution-reducer.d.ts +0 -98
  329. package/dist/core/evolution-reducer.js +0 -465
  330. package/dist/core/evolution-types.d.ts +0 -287
  331. package/dist/core/evolution-types.js +0 -78
  332. package/dist/core/external-training-contract.d.ts +0 -276
  333. package/dist/core/external-training-contract.js +0 -269
  334. package/dist/core/focus-history.d.ts +0 -210
  335. package/dist/core/focus-history.js +0 -1185
  336. package/dist/core/hygiene/tracker.d.ts +0 -22
  337. package/dist/core/hygiene/tracker.js +0 -106
  338. package/dist/core/init.d.ts +0 -12
  339. package/dist/core/local-worker-routing.d.ts +0 -175
  340. package/dist/core/local-worker-routing.js +0 -525
  341. package/dist/core/migration.d.ts +0 -6
  342. package/dist/core/model-deployment-registry.d.ts +0 -218
  343. package/dist/core/model-deployment-registry.js +0 -503
  344. package/dist/core/model-training-registry.d.ts +0 -295
  345. package/dist/core/model-training-registry.js +0 -475
  346. package/dist/core/nocturnal-arbiter.d.ts +0 -159
  347. package/dist/core/nocturnal-arbiter.js +0 -534
  348. package/dist/core/nocturnal-candidate-scoring.d.ts +0 -137
  349. package/dist/core/nocturnal-candidate-scoring.js +0 -266
  350. package/dist/core/nocturnal-compliance.d.ts +0 -175
  351. package/dist/core/nocturnal-compliance.js +0 -824
  352. package/dist/core/nocturnal-dataset.d.ts +0 -224
  353. package/dist/core/nocturnal-dataset.js +0 -443
  354. package/dist/core/nocturnal-executability.d.ts +0 -85
  355. package/dist/core/nocturnal-executability.js +0 -331
  356. package/dist/core/nocturnal-export.d.ts +0 -124
  357. package/dist/core/nocturnal-export.js +0 -275
  358. package/dist/core/nocturnal-paths.d.ts +0 -124
  359. package/dist/core/nocturnal-trajectory-extractor.d.ts +0 -242
  360. package/dist/core/nocturnal-trajectory-extractor.js +0 -307
  361. package/dist/core/nocturnal-trinity.d.ts +0 -311
  362. package/dist/core/nocturnal-trinity.js +0 -880
  363. package/dist/core/pain.d.ts +0 -4
  364. package/dist/core/pain.js +0 -70
  365. package/dist/core/path-resolver.d.ts +0 -46
  366. package/dist/core/paths.d.ts +0 -65
  367. package/dist/core/principle-training-state.d.ts +0 -121
  368. package/dist/core/principle-training-state.js +0 -321
  369. package/dist/core/profile.d.ts +0 -62
  370. package/dist/core/profile.js +0 -210
  371. package/dist/core/promotion-gate.d.ts +0 -238
  372. package/dist/core/promotion-gate.js +0 -529
  373. package/dist/core/risk-calculator.d.ts +0 -22
  374. package/dist/core/session-tracker.d.ts +0 -101
  375. package/dist/core/shadow-observation-registry.d.ts +0 -217
  376. package/dist/core/shadow-observation-registry.js +0 -308
  377. package/dist/core/system-logger.d.ts +0 -8
  378. package/dist/core/thinking-models.d.ts +0 -38
  379. package/dist/core/thinking-models.js +0 -170
  380. package/dist/core/training-program.d.ts +0 -233
  381. package/dist/core/training-program.js +0 -433
  382. package/dist/core/trajectory.d.ts +0 -411
  383. package/dist/core/trajectory.js +0 -1307
  384. package/dist/core/workspace-context.d.ts +0 -71
  385. package/dist/hooks/bash-risk.d.ts +0 -57
  386. package/dist/hooks/bash-risk.js +0 -137
  387. package/dist/hooks/edit-verification.d.ts +0 -62
  388. package/dist/hooks/edit-verification.js +0 -256
  389. package/dist/hooks/gate-block-helper.d.ts +0 -44
  390. package/dist/hooks/gate-block-helper.js +0 -119
  391. package/dist/hooks/gate.d.ts +0 -24
  392. package/dist/hooks/gate.js +0 -173
  393. package/dist/hooks/gfi-gate.d.ts +0 -40
  394. package/dist/hooks/gfi-gate.js +0 -113
  395. package/dist/hooks/lifecycle.d.ts +0 -5
  396. package/dist/hooks/lifecycle.js +0 -284
  397. package/dist/hooks/llm.d.ts +0 -13
  398. package/dist/hooks/message-sanitize.d.ts +0 -3
  399. package/dist/hooks/message-sanitize.js +0 -37
  400. package/dist/hooks/pain.d.ts +0 -5
  401. package/dist/hooks/pain.js +0 -301
  402. package/dist/hooks/progressive-trust-gate.d.ts +0 -52
  403. package/dist/hooks/progressive-trust-gate.js +0 -134
  404. package/dist/hooks/prompt.d.ts +0 -49
  405. package/dist/hooks/prompt.js +0 -905
  406. package/dist/hooks/subagent.d.ts +0 -10
  407. package/dist/hooks/subagent.js +0 -387
  408. package/dist/hooks/thinking-checkpoint.d.ts +0 -37
  409. package/dist/hooks/thinking-checkpoint.js +0 -51
  410. package/dist/hooks/trajectory-collector.d.ts +0 -32
  411. package/dist/hooks/trajectory-collector.js +0 -256
  412. package/dist/http/principles-console-route.d.ts +0 -9
  413. package/dist/http/principles-console-route.js +0 -681
  414. package/dist/i18n/commands.d.ts +0 -26
  415. package/dist/i18n/commands.js +0 -116
  416. package/dist/index.d.ts +0 -7
  417. package/dist/index.js +0 -581
  418. package/dist/service/central-database.d.ts +0 -104
  419. package/dist/service/central-database.js +0 -649
  420. package/dist/service/control-ui-query-service.d.ts +0 -221
  421. package/dist/service/control-ui-query-service.js +0 -543
  422. package/dist/service/empathy-observer-manager.d.ts +0 -88
  423. package/dist/service/empathy-observer-manager.js +0 -414
  424. package/dist/service/evolution-query-service.d.ts +0 -155
  425. package/dist/service/evolution-query-service.js +0 -258
  426. package/dist/service/evolution-worker.d.ts +0 -101
  427. package/dist/service/evolution-worker.js +0 -975
  428. package/dist/service/health-query-service.d.ts +0 -170
  429. package/dist/service/health-query-service.js +0 -662
  430. package/dist/service/nocturnal-runtime.d.ts +0 -183
  431. package/dist/service/nocturnal-service.d.ts +0 -163
  432. package/dist/service/nocturnal-service.js +0 -787
  433. package/dist/service/nocturnal-target-selector.d.ts +0 -145
  434. package/dist/service/nocturnal-target-selector.js +0 -315
  435. package/dist/service/phase3-input-filter.d.ts +0 -73
  436. package/dist/service/phase3-input-filter.js +0 -172
  437. package/dist/service/runtime-summary-service.d.ts +0 -122
  438. package/dist/service/runtime-summary-service.js +0 -485
  439. package/dist/service/subagent-workflow/empathy-observer-workflow-manager.d.ts +0 -48
  440. package/dist/service/subagent-workflow/index.d.ts +0 -4
  441. package/dist/service/subagent-workflow/index.js +0 -3
  442. package/dist/service/subagent-workflow/runtime-direct-driver.d.ts +0 -77
  443. package/dist/service/subagent-workflow/runtime-direct-driver.js +0 -75
  444. package/dist/service/subagent-workflow/types.js +0 -11
  445. package/dist/service/subagent-workflow/workflow-store.d.ts +0 -26
  446. package/dist/service/subagent-workflow/workflow-store.js +0 -165
  447. package/dist/service/trajectory-service.d.ts +0 -2
  448. package/dist/service/trajectory-service.js +0 -15
  449. package/dist/tools/critique-prompt.d.ts +0 -14
  450. package/dist/tools/deep-reflect.d.ts +0 -39
  451. package/dist/tools/deep-reflect.js +0 -350
  452. package/dist/tools/model-index.d.ts +0 -9
  453. package/dist/types/event-types.d.ts +0 -306
  454. package/dist/types/event-types.js +0 -106
  455. package/dist/types/hygiene-types.d.ts +0 -20
  456. package/dist/types/hygiene-types.js +0 -12
  457. package/dist/types/runtime-summary.d.ts +0 -47
  458. package/dist/types/runtime-summary.js +0 -1
  459. package/dist/types.d.ts +0 -50
  460. package/dist/types.js +0 -22
  461. package/dist/utils/file-lock.d.ts +0 -71
  462. package/dist/utils/file-lock.js +0 -309
  463. package/dist/utils/glob-match.d.ts +0 -28
  464. package/dist/utils/hashing.d.ts +0 -9
  465. package/dist/utils/io.d.ts +0 -6
  466. package/dist/utils/io.js +0 -106
  467. package/dist/utils/nlp.d.ts +0 -9
  468. package/dist/utils/plugin-logger.d.ts +0 -39
  469. package/dist/utils/subagent-probe.d.ts +0 -34
  470. package/dist/utils/subagent-probe.js +0 -81
@@ -0,0 +1,1458 @@
1
+ /**
2
+ * CURRENT_FOCUS 历史版本管理
3
+ *
4
+ * 功能:
5
+ * - 压缩时备份当前版本到历史目录
6
+ * - 清理过期历史版本
7
+ * - 读取历史版本(用于 full 模式)
8
+ * - 工作记忆提取与合并(压缩后恢复上下文)
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+
14
+ // ============================================================================
15
+ // 工作记忆数据结构
16
+ // ============================================================================
17
+
18
+ /**
19
+ * 文件输出记录
20
+ */
21
+ export interface FileArtifact {
22
+ path: string; // 完整文件路径
23
+ action: 'created' | 'modified' | 'deleted';
24
+ description: string; // 简短描述
25
+ }
26
+
27
+ /**
28
+ * 工作记忆快照
29
+ */
30
+ export interface WorkingMemorySnapshot {
31
+ lastUpdated: string;
32
+
33
+ // 文件输出记录(核心)
34
+ artifacts: FileArtifact[];
35
+
36
+ // 当前任务
37
+ currentTask?: {
38
+ description: string;
39
+ status: 'in_progress' | 'blocked' | 'reviewing' | 'completed';
40
+ progress: number;
41
+ };
42
+
43
+ // 活动问题
44
+ activeProblems: Array<{
45
+ problem: string;
46
+ approach?: string;
47
+ }>;
48
+
49
+ // 下一步行动
50
+ nextActions: string[];
51
+ }
52
+
53
+ /**
54
+ * 简单的日志记录器
55
+ */
56
+ function logError(message: string, error?: unknown): void {
57
+ const timestamp = new Date().toISOString();
58
+ const errorStr = error instanceof Error ? error.message : String(error);
59
+ // eslint-disable-next-line no-console
60
+ console.error(`[focus-history] ${timestamp} ERROR: ${message}${errorStr ? ' - ' + errorStr : ''}`);
61
+ }
62
+
63
+ /** 历史版本保留数量 */
64
+ const MAX_HISTORY_FILES = 10;
65
+
66
+ /** full 模式读取的历史版本数 */
67
+ const FULL_MODE_HISTORY_COUNT = 3;
68
+
69
+ /**
70
+ * 获取历史目录路径
71
+ */
72
+ export function getHistoryDir(focusPath: string): string {
73
+ return path.join(path.dirname(focusPath), '.history');
74
+ }
75
+
76
+ /**
77
+ * 从 CURRENT_FOCUS.md 提取版本号
78
+ * 支持整数和小数版本(如 v1, v1.1, v1.2)
79
+ */
80
+ export function extractVersion(content: string): string {
81
+ const match = content.match(/\*\*版本\*\*:\s*v([\d.]+)/i);
82
+ return match ? match[1] : '1';
83
+ }
84
+
85
+ /**
86
+ * 从 CURRENT_FOCUS.md 提取更新日期
87
+ */
88
+ export function extractDate(content: string): string {
89
+ const match = content.match(/\*\*更新\*\*:\s*(\d{4}-\d{2}-\d{2})/);
90
+ return match ? match[1] : new Date().toISOString().split('T')[0];
91
+ }
92
+
93
+ /**
94
+ * 备份当前版本到历史目录
95
+ *
96
+ * @param focusPath CURRENT_FOCUS.md 的完整路径
97
+ * @param content 当前内容
98
+ * @returns 备份文件路径,失败返回 null
99
+ */
100
+ export function backupToHistory(focusPath: string, content: string): string | null {
101
+ try {
102
+ const historyDir = getHistoryDir(focusPath);
103
+
104
+ // 确保历史目录存在
105
+ if (!fs.existsSync(historyDir)) {
106
+ try {
107
+ fs.mkdirSync(historyDir, { recursive: true });
108
+ } catch (error) {
109
+ logError(`Failed to create history directory: ${historyDir}`, error);
110
+ return null;
111
+ }
112
+ }
113
+
114
+ const version = extractVersion(content);
115
+ const date = extractDate(content);
116
+ // 使用时间戳作为唯一标识,避免同名冲突
117
+ const timestamp = Date.now();
118
+ const backupName = `CURRENT_FOCUS.v${version}.${date}.${timestamp}.md`;
119
+ const backupPath = path.join(historyDir, backupName);
120
+
121
+ // 如果备份已存在,跳过
122
+ if (fs.existsSync(backupPath)) {
123
+ return null;
124
+ }
125
+
126
+ try {
127
+ fs.writeFileSync(backupPath, content, 'utf-8');
128
+ return backupPath;
129
+ } catch (error) {
130
+ logError(`Failed to write backup file: ${backupPath}`, error);
131
+ return null;
132
+ }
133
+ } catch (error) {
134
+ logError('Unexpected error in backupToHistory', error);
135
+ return null;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * 清理过期历史版本
141
+ *
142
+ * @param focusPath CURRENT_FOCUS.md 的完整路径
143
+ * @param maxFiles 最大保留数量
144
+ */
145
+ export function cleanupHistory(focusPath: string, maxFiles: number = MAX_HISTORY_FILES): void {
146
+ try {
147
+ const historyDir = getHistoryDir(focusPath);
148
+
149
+ if (!fs.existsSync(historyDir)) {
150
+ return;
151
+ }
152
+
153
+ // 获取所有历史文件并按修改时间排序(最新的在前)
154
+ const files = fs.readdirSync(historyDir)
155
+ .filter(f => f.startsWith('CURRENT_FOCUS.v') && f.endsWith('.md'))
156
+ .map(f => ({
157
+ name: f,
158
+ path: path.join(historyDir, f),
159
+ mtime: fs.statSync(path.join(historyDir, f)).mtime.getTime()
160
+ }))
161
+ .sort((a, b) => b.mtime - a.mtime);
162
+
163
+ // 删除超出数量的文件
164
+ const toDelete = files.slice(maxFiles);
165
+ for (const file of toDelete) {
166
+ try {
167
+ fs.unlinkSync(file.path);
168
+ } catch (error) {
169
+ // 单个文件删除失败不应中断整个清理过程
170
+ logError(`Failed to delete history file: ${file.path}`, error);
171
+ }
172
+ }
173
+ } catch (error) {
174
+ logError('Unexpected error in cleanupHistory', error);
175
+ }
176
+ }
177
+
178
+ /**
179
+ * 获取历史版本列表
180
+ *
181
+ * @param focusPath CURRENT_FOCUS.md 的完整路径
182
+ * @param count 获取数量
183
+ * @returns 历史版本内容数组(按时间倒序)
184
+ */
185
+ export function getHistoryVersions(focusPath: string, count: number = FULL_MODE_HISTORY_COUNT): string[] {
186
+ const historyDir = getHistoryDir(focusPath);
187
+
188
+ if (!fs.existsSync(historyDir)) {
189
+ return [];
190
+ }
191
+
192
+ // 获取所有历史文件并按修改时间排序(最新的在前)
193
+ const files = fs.readdirSync(historyDir)
194
+ .filter(f => f.startsWith('CURRENT_FOCUS.v') && f.endsWith('.md'))
195
+ .map(f => ({
196
+ path: path.join(historyDir, f),
197
+ mtime: fs.statSync(path.join(historyDir, f)).mtime.getTime()
198
+ }))
199
+ .sort((a, b) => b.mtime - a.mtime)
200
+ .slice(0, count);
201
+
202
+ return files.map(f => fs.readFileSync(f.path, 'utf-8'));
203
+ }
204
+
205
+ /**
206
+ * 压缩 CURRENT_FOCUS.md
207
+ *
208
+ * @param focusPath CURRENT_FOCUS.md 的完整路径
209
+ * @param newContent 新内容
210
+ * @returns 压缩后的信息
211
+ */
212
+ export function compressFocus(focusPath: string, newContent: string): {
213
+ backupPath: string | null;
214
+ cleanedCount: number;
215
+ } {
216
+ // 读取当前内容
217
+ let oldContent = '';
218
+ if (fs.existsSync(focusPath)) {
219
+ oldContent = fs.readFileSync(focusPath, 'utf-8');
220
+ }
221
+
222
+ // 备份当前版本
223
+ const backupPath = oldContent ? backupToHistory(focusPath, oldContent) : null;
224
+
225
+ // 递增版本号(支持小数版本)
226
+ const oldVersion = extractVersion(oldContent);
227
+ // 解析版本号并递增
228
+ const versionParts = oldVersion.split('.');
229
+ const majorVersion = parseInt(versionParts[0], 10) || 1;
230
+ const newVersion = `${majorVersion + 1}`;
231
+ const today = new Date().toISOString().split('T')[0];
232
+
233
+ // 更新版本号和日期
234
+ const updatedContent = newContent
235
+ .replace(/\*\*版本\*\*:\s*v[\d.]+/i, `**版本**: v${newVersion}`)
236
+ .replace(/\*\*更新\*\*:\s*\d{4}-\d{2}-\d{2}/, `**更新**: ${today}`);
237
+
238
+ // 写入新内容
239
+ fs.writeFileSync(focusPath, updatedContent, 'utf-8');
240
+
241
+ // 清理过期历史
242
+ const historyDir = getHistoryDir(focusPath);
243
+ const beforeCount = fs.existsSync(historyDir)
244
+ ? fs.readdirSync(historyDir).filter(f => f.startsWith('CURRENT_FOCUS.v')).length
245
+ : 0;
246
+
247
+ cleanupHistory(focusPath);
248
+
249
+ const afterCount = fs.existsSync(historyDir)
250
+ ? fs.readdirSync(historyDir).filter(f => f.startsWith('CURRENT_FOCUS.v')).length
251
+ : 0;
252
+
253
+ return {
254
+ backupPath,
255
+ cleanedCount: beforeCount - afterCount
256
+ };
257
+ }
258
+
259
+ /**
260
+ * 智能摘要提取
261
+ *
262
+ * 优先提取关键章节,确保不丢失重要信息
263
+ * 对于非结构化内容,回退到简单的行截取
264
+ *
265
+ * @param content CURRENT_FOCUS.md 内容
266
+ * @param maxLines 最大行数
267
+ */
268
+ export function extractSummary(content: string, maxLines: number = 30): string {
269
+ const lines = content.split('\n');
270
+ const sections: { [key: string]: string[] } = {
271
+ header: [], // 标题和元数据
272
+ snapshot: [], // 状态快照
273
+ current: [], // 当前任务
274
+ nextSteps: [], // 下一步
275
+ reference: [] // 参考
276
+ };
277
+
278
+ let currentSection = 'header';
279
+ let hasStructuredSections = false;
280
+
281
+ for (const line of lines) {
282
+ // 识别章节(使用更宽松的匹配,支持不同格式)
283
+ const trimmedLine = line.trim();
284
+
285
+ // 使用正则匹配,支持 h1-h3 和多种格式
286
+ if (/^#{1,3}\s*.*状态快照|📍/.test(trimmedLine)) {
287
+ currentSection = 'snapshot';
288
+ hasStructuredSections = true;
289
+ } else if (/^#{1,3}\s*.*当前任务|🔄/.test(trimmedLine)) {
290
+ currentSection = 'current';
291
+ hasStructuredSections = true;
292
+ } else if (/^#{1,3}\s*.*下一步|➡️/.test(trimmedLine)) {
293
+ currentSection = 'nextSteps';
294
+ hasStructuredSections = true;
295
+ } else if (/^#{1,3}\s*.*参考|📎/.test(trimmedLine)) {
296
+ currentSection = 'reference';
297
+ hasStructuredSections = true;
298
+ } else if (trimmedLine === '---') {
299
+ continue; // 跳过分隔线
300
+ } else if (line.startsWith('<!--')) {
301
+ continue; // 跳过注释
302
+ }
303
+
304
+ sections[currentSection].push(line);
305
+ }
306
+
307
+ // 如果没有结构化章节,回退到简单的行截取
308
+ if (!hasStructuredSections) {
309
+ const result = lines.slice(0, maxLines);
310
+ if (lines.length > maxLines) {
311
+ result.push('');
312
+ result.push('...[truncated, see CURRENT_FOCUS.md for full context]');
313
+ }
314
+ return result.join('\n');
315
+ }
316
+
317
+ // 按优先级拼接
318
+ const result: string[] = [
319
+ ...sections.header.slice(0, 5), // 标题 + 元数据
320
+ '',
321
+ '---',
322
+ '',
323
+ ...sections.snapshot.slice(0, 10), // 状态快照
324
+ '',
325
+ ...sections.nextSteps.slice(0, 10), // 下一步(优先级高)
326
+ '',
327
+ ...sections.current.slice(0, 15), // 当前任务
328
+ ];
329
+
330
+ // 限制总行数
331
+ const trimmed = result.slice(0, maxLines);
332
+ if (result.length > maxLines) {
333
+ trimmed.push('');
334
+ trimmed.push('...[truncated, see CURRENT_FOCUS.md for full context]');
335
+ }
336
+
337
+ return trimmed.join('\n');
338
+ }
339
+
340
+ // ============================================================================
341
+ // 工作记忆管理
342
+ // ============================================================================
343
+
344
+ /** Working Memory 章节标记 */
345
+ const WORKING_MEMORY_SECTION = '## 🧠 Working Memory';
346
+
347
+ /** 最大保留的文件记录数 */
348
+ const MAX_ARTIFACTS = 20;
349
+
350
+ /** 最大保留的问题数 */
351
+ const MAX_PROBLEMS = 5;
352
+
353
+ /** 最大保留下一步行动数 */
354
+ const MAX_NEXT_ACTIONS = 5;
355
+
356
+ /**
357
+ * 从会话消息中提取工作记忆
358
+ *
359
+ * @param messages 会话消息数组(OpenClaw 格式)
360
+ * @param workspaceDir 工作区目录(用于生成相对路径)
361
+ * @returns 提取的工作记忆快照
362
+ */
363
+ export function extractWorkingMemory(
364
+ messages: Array<{ role?: string; content?: string | unknown[] }>,
365
+ workspaceDir?: string
366
+ ): WorkingMemorySnapshot {
367
+ const snapshot: WorkingMemorySnapshot = {
368
+ lastUpdated: new Date().toISOString(),
369
+ artifacts: [],
370
+ activeProblems: [],
371
+ nextActions: []
372
+ };
373
+
374
+ // 只处理最近的 assistant 消息
375
+ const recentMessages = messages
376
+ .filter(m => m.role === 'assistant')
377
+ .slice(-10);
378
+
379
+ for (const msg of recentMessages) {
380
+ let text = '';
381
+ const toolUses: Array<{ name: string; input: Record<string, unknown> }> = [];
382
+
383
+ if (typeof msg.content === 'string') {
384
+ text = msg.content;
385
+ } else if (Array.isArray(msg.content)) {
386
+ const textParts: string[] = [];
387
+
388
+ for (const c of msg.content) {
389
+ if (!c || typeof c !== 'object') continue;
390
+ const obj = c as Record<string, unknown>;
391
+
392
+ // 提取文本内容
393
+ if (obj.type === 'text' && typeof obj.text === 'string') {
394
+ textParts.push(obj.text);
395
+ }
396
+
397
+ // 提取工具调用(关键:文件操作在这里!)
398
+ if (obj.type === 'tool_use' && typeof obj.name === 'string' && typeof obj.input === 'object') {
399
+ toolUses.push({
400
+ name: obj.name,
401
+ input: obj.input as Record<string, unknown>
402
+ });
403
+ }
404
+ }
405
+
406
+ text = textParts.join('\n');
407
+ }
408
+
409
+ // 从工具调用中提取文件路径(这是最可靠的方式)
410
+ for (const toolUse of toolUses) {
411
+ if (['write_file', 'replace', 'create_file'].includes(toolUse.name)) {
412
+ const filePath = toolUse.input.file_path || toolUse.input.absolute_path || toolUse.input.path;
413
+ if (typeof filePath === 'string' && filePath.trim()) {
414
+ // 跳过不需要的文件
415
+ if (filePath.includes('node_modules') ||
416
+ filePath.endsWith('.d.ts') ||
417
+ filePath.includes('.config.')) {
418
+ continue;
419
+ }
420
+
421
+ // 生成相对路径
422
+ const displayPath = workspaceDir && filePath.startsWith(workspaceDir)
423
+ ? path.relative(workspaceDir, filePath)
424
+ : filePath;
425
+
426
+ // 判断操作类型
427
+ const action: 'created' | 'modified' | 'deleted' =
428
+ toolUse.name === 'write_file' || toolUse.name === 'create_file' ? 'created' : 'modified';
429
+
430
+ // 尝试从文本中提取描述
431
+ const description = extractDescription(text, filePath);
432
+
433
+ snapshot.artifacts.push({
434
+ path: displayPath,
435
+ action,
436
+ description
437
+ });
438
+ }
439
+ }
440
+ }
441
+
442
+ if (!text) continue;
443
+
444
+ // 从文本中提取文件操作(备用方式)
445
+ extractFileArtifacts(text, snapshot.artifacts, workspaceDir);
446
+
447
+ // 提取问题
448
+ extractProblems(text, snapshot.activeProblems);
449
+
450
+ // 提取下一步
451
+ extractNextActions(text, snapshot.nextActions);
452
+ }
453
+
454
+ // 去重和限制数量
455
+ snapshot.artifacts = deduplicateArtifacts(snapshot.artifacts).slice(-MAX_ARTIFACTS);
456
+ snapshot.activeProblems = snapshot.activeProblems.slice(-MAX_PROBLEMS);
457
+ snapshot.nextActions = snapshot.nextActions.slice(-MAX_NEXT_ACTIONS);
458
+
459
+ return snapshot;
460
+ }
461
+
462
+ /**
463
+ * 从文本中提取文件操作记录
464
+ */
465
+ function extractFileArtifacts(
466
+ text: string,
467
+ artifacts: FileArtifact[],
468
+ workspaceDir?: string
469
+ ): void {
470
+ // 匹配 write_file, replace 工具调用
471
+ // 格式: file_path: "/path/to/file" 或 absolute_path: "/path/to/file"
472
+ const filePathRegex = /(?:file_path|absolute_path)["']?\s*[:=]\s*["']([^"']+\.(ts|js|json|md|yaml|yml|py|sh|mjs|cjs))["']/gi;
473
+
474
+ let match;
475
+ while ((match = filePathRegex.exec(text)) !== null) {
476
+ const filePath = match[1];
477
+
478
+ // 跳过 node_modules 和配置文件
479
+ if (filePath.includes('node_modules') ||
480
+ filePath.endsWith('.d.ts') ||
481
+ filePath.includes('.config.')) {
482
+ continue;
483
+ }
484
+
485
+ // 生成相对路径(如果有 workspaceDir)
486
+ const displayPath = workspaceDir && filePath.startsWith(workspaceDir)
487
+ ? path.relative(workspaceDir, filePath)
488
+ : filePath;
489
+
490
+ // 判断操作类型 - 根据上下文关键词
491
+ let action: 'created' | 'modified' | 'deleted' = 'modified';
492
+ const contextBefore = text.substring(Math.max(0, match.index - 200), match.index);
493
+ if (contextBefore.toLowerCase().includes('created') ||
494
+ contextBefore.includes('新建') ||
495
+ contextBefore.includes('创建')) {
496
+ action = 'created';
497
+ } else if (contextBefore.toLowerCase().includes('deleted') ||
498
+ contextBefore.includes('删除')) {
499
+ action = 'deleted';
500
+ }
501
+
502
+ // 尝试提取描述(从附近的文本)
503
+ const description = extractDescription(text, filePath);
504
+
505
+ artifacts.push({
506
+ path: displayPath,
507
+ action,
508
+ description
509
+ });
510
+ }
511
+
512
+ // 匹配更通用的文件路径格式(如代码块中的路径)
513
+ // 格式: `path/to/file.ts` 或 "path/to/file.ts"
514
+ // 只匹配明确的代码相关路径
515
+ const genericPathRegex = /[`"']([a-zA-Z0-9_\-\/]+\.(ts|js|mjs|cjs|py))[`"']/g;
516
+
517
+ while ((match = genericPathRegex.exec(text)) !== null) {
518
+ const filePath = match[1];
519
+
520
+ // 跳过太短、node_modules、配置文件
521
+ if (filePath.length < 10 ||
522
+ filePath.includes('node_modules') ||
523
+ filePath.includes('.config.') ||
524
+ filePath.endsWith('.d.ts') ||
525
+ filePath.endsWith('.test.ts') ||
526
+ filePath.endsWith('.spec.ts')) {
527
+ continue;
528
+ }
529
+
530
+ // 检查是否已经存在(避免重复)
531
+ if (artifacts.some(a => a.path === filePath || a.path.endsWith(filePath) || filePath.endsWith(a.path))) {
532
+ continue;
533
+ }
534
+
535
+ const description = extractDescription(text, filePath);
536
+
537
+ artifacts.push({
538
+ path: filePath,
539
+ action: 'modified',
540
+ description
541
+ });
542
+ }
543
+ }
544
+
545
+ /**
546
+ * 尝试从文本中提取文件描述
547
+ */
548
+ function extractDescription(text: string, filePath: string): string {
549
+ // 在文件路径附近查找描述性文字
550
+ const pathIndex = text.indexOf(filePath);
551
+ if (pathIndex === -1) return '';
552
+
553
+ // 向前查找 100 个字符
554
+ const before = text.substring(Math.max(0, pathIndex - 100), pathIndex);
555
+
556
+ // 匹配描述模式
557
+ const descPatterns = [
558
+ /(?:description|说明|描述|功能|purpose)[::]\s*([^\n]{5,50})/i,
559
+ /\/\/\s*(.{5,50})/,
560
+ /\*\s*(.{5,50})\s*$/
561
+ ];
562
+
563
+ for (const pattern of descPatterns) {
564
+ const match = before.match(pattern);
565
+ if (match) {
566
+ return match[1].trim().substring(0, 50);
567
+ }
568
+ }
569
+
570
+ return '';
571
+ }
572
+
573
+ /**
574
+ * 从文本中提取问题
575
+ */
576
+ function extractProblems(
577
+ text: string,
578
+ problems: Array<{ problem: string; approach?: string }>
579
+ ): void {
580
+ // 问题模式(匹配问题描述)
581
+ const problemPattern = /(?:问题|problem|error|错误|失败|failed)[::]\s*([^\n]{5,100})/gi;
582
+ let match;
583
+ while ((match = problemPattern.exec(text)) !== null) {
584
+ const content = match[1].trim();
585
+ if (content.length > 5) {
586
+ problems.push({
587
+ problem: content,
588
+ approach: undefined
589
+ });
590
+ }
591
+ }
592
+
593
+ // 解决方案模式(匹配问题和解决方案)
594
+ const solutionPattern = /(?:解决|solution|方案|修复|fix)[::]\s*([^\n]{5,100})/gi;
595
+ while ((match = solutionPattern.exec(text)) !== null) {
596
+ const content = match[1].trim();
597
+ if (content.length > 5) {
598
+ // 尝试关联到最近的问题
599
+ const lastProblem = problems[problems.length - 1];
600
+ if (lastProblem && !lastProblem.approach) {
601
+ lastProblem.approach = content;
602
+ } else {
603
+ // 作为独立问题记录
604
+ problems.push({
605
+ problem: content,
606
+ approach: content
607
+ });
608
+ }
609
+ }
610
+ }
611
+ }
612
+
613
+ /**
614
+ * 从文本中提取下一步行动
615
+ */
616
+ function extractNextActions(text: string, actions: string[]): void {
617
+ // 匹配下一步模式
618
+ const patterns = [
619
+ /(?:下一步|next|接下来|todo|待办)[::]?\s*\n?\s*[-\d]+\s*[.)]?\s*([^\n]{5,80})/gi,
620
+ /[-\d]+\s*[.)]\s*([^\n]{5,80})/g
621
+ ];
622
+
623
+ for (const pattern of patterns) {
624
+ let match;
625
+ while ((match = pattern.exec(text)) !== null) {
626
+ const action = match[1].trim();
627
+ if (action.length > 5 && !actions.includes(action)) {
628
+ actions.push(action);
629
+ }
630
+ }
631
+ }
632
+ }
633
+
634
+ /**
635
+ * 去重文件记录
636
+ */
637
+ function deduplicateArtifacts(artifacts: FileArtifact[]): FileArtifact[] {
638
+ const seen = new Map<string, FileArtifact>();
639
+
640
+ for (const artifact of artifacts) {
641
+ const key = artifact.path;
642
+ const existing = seen.get(key);
643
+
644
+ if (!existing) {
645
+ seen.set(key, artifact);
646
+ } else {
647
+ // 合并描述(保留更长的)
648
+ if (artifact.description.length > existing.description.length) {
649
+ existing.description = artifact.description;
650
+ }
651
+ }
652
+ }
653
+
654
+ return Array.from(seen.values());
655
+ }
656
+
657
+ /**
658
+ * 解析 CURRENT_FOCUS.md 中的 Working Memory 章节
659
+ */
660
+ export function parseWorkingMemorySection(content: string): WorkingMemorySnapshot | null {
661
+ const wmIndex = content.indexOf(WORKING_MEMORY_SECTION);
662
+ if (wmIndex === -1) return null;
663
+
664
+ const wmContent = content.substring(wmIndex);
665
+
666
+ const snapshot: WorkingMemorySnapshot = {
667
+ lastUpdated: new Date().toISOString(),
668
+ artifacts: [],
669
+ activeProblems: [],
670
+ nextActions: []
671
+ };
672
+
673
+ // 解析 last updated
674
+ const updatedMatch = wmContent.match(/Last updated:\s*([^\n]+)/i);
675
+ if (updatedMatch) {
676
+ snapshot.lastUpdated = updatedMatch[1].trim();
677
+ }
678
+
679
+ // 解析文件记录表格
680
+ // | 文件路径 | 操作 | 描述 |
681
+ const tableRegex = /\|\s*`?([^`|\n]+)`?\s*\|\s*(created|modified|deleted)\s*\|\s*([^|\n]*)\s*\|/gi;
682
+ let match;
683
+ while ((match = tableRegex.exec(wmContent)) !== null) {
684
+ snapshot.artifacts.push({
685
+ path: match[1].trim(),
686
+ action: match[2].toLowerCase() as 'created' | 'modified' | 'deleted',
687
+ description: match[3].trim()
688
+ });
689
+ }
690
+
691
+ // 解析问题列表
692
+ const problemRegex = /[-*]\s*(.+?)\s*(?:→|->)\s*(.+)/g;
693
+ while ((match = problemRegex.exec(wmContent)) !== null) {
694
+ snapshot.activeProblems.push({
695
+ problem: match[1].trim(),
696
+ approach: match[2].trim()
697
+ });
698
+ }
699
+
700
+ // 解析下一步行动
701
+ const actionRegex = /^\s*[\d]+\.\s*(.+)$/gm;
702
+ while ((match = actionRegex.exec(wmContent)) !== null) {
703
+ snapshot.nextActions.push(match[1].trim());
704
+ }
705
+
706
+ return snapshot;
707
+ }
708
+
709
+ /**
710
+ * 将工作记忆合并到 CURRENT_FOCUS.md 内容中
711
+ *
712
+ * @param content 原始内容
713
+ * @param snapshot 工作记忆快照
714
+ * @returns 合并后的内容
715
+ */
716
+ export function mergeWorkingMemory(content: string, snapshot: WorkingMemorySnapshot): string {
717
+ const wmIndex = content.indexOf(WORKING_MEMORY_SECTION);
718
+
719
+ // 生成 Working Memory 章节
720
+ const wmSection = generateWorkingMemorySection(snapshot);
721
+
722
+ if (wmIndex === -1) {
723
+ // 没有 Working Memory 章节,追加到末尾
724
+ return content.trimEnd() + '\n\n' + WORKING_MEMORY_SECTION + '\n' + wmSection;
725
+ } else {
726
+ // 替换现有的 Working Memory 章节
727
+ const beforeWm = content.substring(0, wmIndex);
728
+ // 查找下一个 ## 标题(如果有的话)
729
+ const afterWm = content.substring(wmIndex);
730
+ const nextSectionMatch = afterWm.substring(WORKING_MEMORY_SECTION.length).match(/\n##\s/);
731
+
732
+ if (nextSectionMatch && nextSectionMatch.index !== undefined) {
733
+ const nextSectionStart = WORKING_MEMORY_SECTION.length + nextSectionMatch.index;
734
+ return beforeWm + WORKING_MEMORY_SECTION + '\n' + wmSection + '\n' + afterWm.substring(nextSectionStart);
735
+ } else {
736
+ return beforeWm + WORKING_MEMORY_SECTION + '\n' + wmSection;
737
+ }
738
+ }
739
+ }
740
+
741
+ /**
742
+ * 生成 Working Memory 章节内容
743
+ */
744
+ function generateWorkingMemorySection(snapshot: WorkingMemorySnapshot): string {
745
+ const lines: string[] = [`> Last updated: ${snapshot.lastUpdated}`, ''];
746
+
747
+ // 文件输出记录
748
+ if (snapshot.artifacts.length > 0) {
749
+ lines.push('### 📁 文件输出记录');
750
+ lines.push('');
751
+ lines.push('| 文件路径 | 操作 | 描述 |');
752
+ lines.push('|----------|------|------|');
753
+ for (const artifact of snapshot.artifacts) {
754
+ lines.push(`| \`${artifact.path}\` | ${artifact.action} | ${artifact.description || '-'} |`);
755
+ }
756
+ lines.push('');
757
+ }
758
+
759
+ // 当前任务
760
+ if (snapshot.currentTask) {
761
+ lines.push('### 🎯 当前任务');
762
+ lines.push(`- **描述**: ${snapshot.currentTask.description}`);
763
+ lines.push(`- **状态**: ${snapshot.currentTask.status}`);
764
+ lines.push(`- **进度**: ${snapshot.currentTask.progress}%`);
765
+ lines.push('');
766
+ }
767
+
768
+ // 活动问题
769
+ if (snapshot.activeProblems.length > 0) {
770
+ lines.push('### ⚠️ 活动问题');
771
+ for (const p of snapshot.activeProblems) {
772
+ if (p.approach) {
773
+ lines.push(`- ${p.problem} → ${p.approach}`);
774
+ } else {
775
+ lines.push(`- ${p.problem}`);
776
+ }
777
+ }
778
+ lines.push('');
779
+ }
780
+
781
+ // 下一步行动
782
+ if (snapshot.nextActions.length > 0) {
783
+ lines.push('### ➡️ 下一步行动');
784
+ for (let i = 0; i < snapshot.nextActions.length; i++) {
785
+ lines.push(`${i + 1}. ${snapshot.nextActions[i]}`);
786
+ }
787
+ lines.push('');
788
+ }
789
+
790
+ return lines.join('\n');
791
+ }
792
+
793
+ /**
794
+ * 生成工作记忆注入字符串(用于 prompt 注入)
795
+ */
796
+ export function workingMemoryToInjection(snapshot: WorkingMemorySnapshot | null): string {
797
+ if (!snapshot) return '';
798
+
799
+ if (snapshot.artifacts.length === 0 &&
800
+ snapshot.activeProblems.length === 0 &&
801
+ snapshot.nextActions.length === 0) {
802
+ return '';
803
+ }
804
+
805
+ const lines: string[] = ['<working_memory preserved="true">'];
806
+ lines.push('以下是你压缩前的工作记忆,请继续完成未完成的任务:');
807
+ lines.push('');
808
+
809
+ if (snapshot.artifacts.length > 0) {
810
+ lines.push('### 已输出的文件');
811
+ for (const a of snapshot.artifacts.slice(-10)) {
812
+ lines.push(`- [${a.action.toUpperCase()}] \`${a.path}\`${a.description ? ` - ${a.description}` : ''}`);
813
+ }
814
+ lines.push('');
815
+ }
816
+
817
+ if (snapshot.activeProblems.length > 0) {
818
+ lines.push('### 活动问题');
819
+ for (const p of snapshot.activeProblems) {
820
+ if (p.approach) {
821
+ lines.push(`- ${p.problem} → ${p.approach}`);
822
+ } else {
823
+ lines.push(`- ${p.problem}`);
824
+ }
825
+ }
826
+ lines.push('');
827
+ }
828
+
829
+ if (snapshot.nextActions.length > 0) {
830
+ lines.push('### 下一步行动');
831
+ for (let i = 0; i < snapshot.nextActions.length; i++) {
832
+ lines.push(`${i + 1}. ${snapshot.nextActions[i]}`);
833
+ }
834
+ lines.push('');
835
+ }
836
+
837
+ lines.push('</working_memory>');
838
+
839
+ return lines.join('\n');
840
+ }
841
+
842
+ // ============================================================================
843
+ // 自动压缩与维护
844
+ // ============================================================================
845
+
846
+ /** 默认压缩配置 */
847
+ const DEFAULT_COMPRESSION_CONFIG = {
848
+ lineThreshold: 100,
849
+ sizeThreshold: 15 * 1024, // 15KB
850
+ intervalMs: 24 * 60 * 60 * 1000, // 24 hours
851
+ keepCompletedTasks: 3,
852
+ maxWorkingMemoryArtifacts: 10,
853
+ };
854
+
855
+ /** 压缩时间记录文件名 */
856
+ const LAST_COMPRESS_FILE = '.last_compress';
857
+
858
+ /**
859
+ * 压缩配置接口
860
+ */
861
+ interface CompressionConfig {
862
+ lineThreshold: number;
863
+ sizeThreshold: number;
864
+ intervalMs: number;
865
+ keepCompletedTasks: number;
866
+ maxWorkingMemoryArtifacts: number;
867
+ }
868
+
869
+ /**
870
+ * 从 pain_settings.json 加载压缩配置
871
+ *
872
+ * @param stateDir state 目录路径
873
+ * @returns 压缩配置
874
+ */
875
+ function loadCompressionConfig(stateDir?: string): CompressionConfig {
876
+ if (!stateDir) {
877
+ return DEFAULT_COMPRESSION_CONFIG;
878
+ }
879
+
880
+ try {
881
+ const configPath = path.join(stateDir, 'pain_settings.json');
882
+ if (!fs.existsSync(configPath)) {
883
+ return DEFAULT_COMPRESSION_CONFIG;
884
+ }
885
+
886
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
887
+ const compression = config?.compression || {};
888
+
889
+ return {
890
+ lineThreshold: compression.line_threshold ?? DEFAULT_COMPRESSION_CONFIG.lineThreshold,
891
+ sizeThreshold: (compression.size_threshold_kb ?? 15) * 1024,
892
+ intervalMs: (compression.interval_hours ?? 24) * 60 * 60 * 1000,
893
+ keepCompletedTasks: compression.keep_completed_tasks ?? DEFAULT_COMPRESSION_CONFIG.keepCompletedTasks,
894
+ maxWorkingMemoryArtifacts: compression.max_working_memory_artifacts ?? DEFAULT_COMPRESSION_CONFIG.maxWorkingMemoryArtifacts,
895
+ };
896
+ } catch {
897
+ return DEFAULT_COMPRESSION_CONFIG;
898
+ }
899
+ }
900
+
901
+ /**
902
+ * 检查是否可以进行自动压缩(频率限制)
903
+ *
904
+ * @param stateDir state 目录路径
905
+ * @returns 是否可以进行压缩
906
+ */
907
+ function canAutoCompress(stateDir: string): boolean {
908
+ const lastCompressPath = path.join(stateDir, LAST_COMPRESS_FILE);
909
+
910
+ if (!fs.existsSync(lastCompressPath)) {
911
+ return true;
912
+ }
913
+
914
+ try {
915
+ const config = loadCompressionConfig(stateDir);
916
+ const lastCompressTime = parseInt(fs.readFileSync(lastCompressPath, 'utf-8'), 10);
917
+ const now = Date.now();
918
+ return (now - lastCompressTime) >= config.intervalMs;
919
+ } catch {
920
+ return true;
921
+ }
922
+ }
923
+
924
+ /**
925
+ * 记录压缩时间
926
+ *
927
+ * @param stateDir state 目录路径
928
+ */
929
+ function recordCompressTime(stateDir: string): void {
930
+ try {
931
+ if (!fs.existsSync(stateDir)) {
932
+ fs.mkdirSync(stateDir, { recursive: true });
933
+ }
934
+ fs.writeFileSync(path.join(stateDir, LAST_COMPRESS_FILE), Date.now().toString(), 'utf-8');
935
+ } catch (error) {
936
+ logError('Failed to record compress time', error);
937
+ }
938
+ }
939
+
940
+ /**
941
+ * 提取已完成任务作为里程碑
942
+ */
943
+ export function extractMilestones(content: string): {
944
+ completedTasks: string[];
945
+ fileArtifacts: string[];
946
+ } {
947
+ const completedTasks: string[] = [];
948
+ const fileArtifacts: string[] = [];
949
+ const lines = content.split('\n');
950
+
951
+ let inTaskSection = false;
952
+ let inWorkingMemory = false;
953
+
954
+ for (const line of lines) {
955
+ const trimmed = line.trim();
956
+
957
+ // 识别章节
958
+ if (/^#{1,3}\s*.*当前任务|🔄/.test(trimmed)) {
959
+ inTaskSection = true;
960
+ inWorkingMemory = false;
961
+ } else if (/^#{1,3}\s*.*下一步|➡️/.test(trimmed)) {
962
+ inTaskSection = false;
963
+ inWorkingMemory = false;
964
+ } else if (/^##\s*🧠\s*Working Memory/.test(trimmed)) {
965
+ inWorkingMemory = true;
966
+ inTaskSection = false;
967
+ }
968
+
969
+ // 提取已完成任务
970
+ if (inTaskSection && /^-\s*\[x\]/i.test(trimmed)) {
971
+ completedTasks.push(trimmed.replace(/^-\s*\[x\]\s*/i, ''));
972
+ }
973
+
974
+ // 提取文件引用(从工作记忆)
975
+ if (inWorkingMemory) {
976
+ const fileMatch = trimmed.match(/^\|\s*`?([^`|\n]+)`?\s*\|/);
977
+ if (fileMatch && !fileMatch[1].includes('文件路径')) {
978
+ fileArtifacts.push(fileMatch[1].trim());
979
+ }
980
+ }
981
+ }
982
+
983
+ return {
984
+ completedTasks: completedTasks.slice(-10), // 最多 10 个
985
+ fileArtifacts: fileArtifacts.slice(-10)
986
+ };
987
+ }
988
+
989
+ /**
990
+ * 归档里程碑到 daily memory 文件
991
+ */
992
+ export function archiveMilestonesToDaily(
993
+ workspaceDir: string,
994
+ milestones: { completedTasks: string[]; fileArtifacts: string[] },
995
+ version: string
996
+ ): string | null {
997
+ if (milestones.completedTasks.length === 0 && milestones.fileArtifacts.length === 0) {
998
+ return null;
999
+ }
1000
+
1001
+ const dateStr = new Date().toISOString().split('T')[0];
1002
+ const memoryDir = path.join(workspaceDir, 'memory');
1003
+ const dailyLogPath = path.join(memoryDir, `${dateStr}.md`);
1004
+ const timestamp = new Date().toISOString();
1005
+
1006
+ // 确保目录存在
1007
+ if (!fs.existsSync(memoryDir)) {
1008
+ fs.mkdirSync(memoryDir, { recursive: true });
1009
+ }
1010
+
1011
+ // 构建里程碑内容
1012
+ const lines: string[] = [];
1013
+ lines.push(`\n## 🏆 里程碑 [CURRENT_FOCUS v${version} 压缩]`);
1014
+ lines.push(`> 时间: ${timestamp}`);
1015
+ lines.push('');
1016
+
1017
+ if (milestones.completedTasks.length > 0) {
1018
+ lines.push('### 已完成任务');
1019
+ for (const task of milestones.completedTasks) {
1020
+ lines.push(`- [x] ${task}`);
1021
+ }
1022
+ lines.push('');
1023
+ }
1024
+
1025
+ if (milestones.fileArtifacts.length > 0) {
1026
+ lines.push('### 相关文件');
1027
+ for (const file of milestones.fileArtifacts) {
1028
+ lines.push(`- \`${file}\``);
1029
+ }
1030
+ lines.push('');
1031
+ }
1032
+
1033
+ lines.push('---');
1034
+ lines.push('');
1035
+
1036
+ // 追加到 daily log
1037
+ try {
1038
+ fs.appendFileSync(dailyLogPath, lines.join('\n'), 'utf-8');
1039
+ return dailyLogPath;
1040
+ } catch (error) {
1041
+ logError(`Failed to archive milestones to ${dailyLogPath}`, error);
1042
+ return null;
1043
+ }
1044
+ }
1045
+
1046
+ /**
1047
+ * 清理过期信息和验证文件引用
1048
+ */
1049
+ export function cleanupStaleInfo(
1050
+ content: string,
1051
+ workspaceDir?: string,
1052
+ config?: CompressionConfig
1053
+ ): string {
1054
+ const effectiveConfig = config || DEFAULT_COMPRESSION_CONFIG;
1055
+ const lines = content.split('\n');
1056
+ const result: string[] = [];
1057
+
1058
+ let inWorkingMemory = false;
1059
+ let inFileTable = false;
1060
+ let completedCount = 0;
1061
+ let artifactCount = 0;
1062
+
1063
+ for (let i = 0; i < lines.length; i++) {
1064
+ const line = lines[i];
1065
+ const trimmed = line.trim();
1066
+
1067
+ // 检测 Working Memory 章节
1068
+ if (/^##\s*🧠\s*Working Memory/.test(trimmed)) {
1069
+ inWorkingMemory = true;
1070
+ inFileTable = false;
1071
+ } else if (/^##\s/.test(trimmed) && !trimmed.includes('Working Memory')) {
1072
+ inWorkingMemory = false;
1073
+ inFileTable = false;
1074
+ }
1075
+
1076
+ // 检测文件表格
1077
+ if (inWorkingMemory && /^\|\s*文件路径/.test(trimmed)) {
1078
+ inFileTable = true;
1079
+ result.push(line);
1080
+ continue;
1081
+ }
1082
+
1083
+ if (inFileTable && /^\|[^|]+\|[^|]+\|[^|]+\|/.test(trimmed)) {
1084
+ // 检查是否是表格分隔行
1085
+ if (/^\|[-\s|:]+\|$/.test(trimmed)) {
1086
+ result.push(line);
1087
+ continue;
1088
+ }
1089
+
1090
+ // 提取文件路径
1091
+ const match = trimmed.match(/^\|\s*`?([^`|\n]+)`?\s*\|/);
1092
+ if (match) {
1093
+ const filePath = match[1].trim();
1094
+ artifactCount++;
1095
+
1096
+ // 限制条数
1097
+ if (artifactCount > effectiveConfig.maxWorkingMemoryArtifacts) {
1098
+ continue; // 跳过超出限制的条目
1099
+ }
1100
+
1101
+ // 可选:验证文件是否存在
1102
+ if (workspaceDir) {
1103
+ const fullPath = path.join(workspaceDir, filePath);
1104
+ if (!fs.existsSync(fullPath)) {
1105
+ continue; // 文件不存在,跳过
1106
+ }
1107
+ }
1108
+
1109
+ result.push(line);
1110
+ continue;
1111
+ }
1112
+ }
1113
+
1114
+ // 处理已完成任务
1115
+ if (/^-\s*\[x\]/i.test(trimmed)) {
1116
+ completedCount++;
1117
+ if (completedCount > effectiveConfig.keepCompletedTasks) {
1118
+ continue; // 跳过超出限制的已完成任务
1119
+ }
1120
+ }
1121
+
1122
+ result.push(line);
1123
+ }
1124
+
1125
+ return result.join('\n');
1126
+ }
1127
+
1128
+ /**
1129
+ * 自动压缩 CURRENT_FOCUS.md
1130
+ *
1131
+ * @param focusPath CURRENT_FOCUS.md 的完整路径
1132
+ * @param workspaceDir 工作区目录(可选,用于验证文件引用)
1133
+ * @param stateDir state 目录路径(可选,用于频率限制)
1134
+ * @returns 压缩结果信息,如果不需要压缩则返回 null
1135
+ */
1136
+ export function autoCompressFocus(
1137
+ focusPath: string,
1138
+ workspaceDir?: string,
1139
+ stateDir?: string
1140
+ ): {
1141
+ compressed: boolean;
1142
+ oldLines: number;
1143
+ newLines: number;
1144
+ milestonesArchived: boolean;
1145
+ backupPath: string | null;
1146
+ reason: string;
1147
+ } {
1148
+ // 检查文件是否存在
1149
+ if (!fs.existsSync(focusPath)) {
1150
+ return {
1151
+ compressed: false,
1152
+ oldLines: 0,
1153
+ newLines: 0,
1154
+ milestonesArchived: false,
1155
+ backupPath: null,
1156
+ reason: 'File not found'
1157
+ };
1158
+ }
1159
+
1160
+ // 加载配置
1161
+ const config = loadCompressionConfig(stateDir);
1162
+
1163
+ const oldContent = fs.readFileSync(focusPath, 'utf-8');
1164
+ const oldLines = oldContent.split('\n').length;
1165
+ const oldSize = Buffer.byteLength(oldContent, 'utf-8');
1166
+
1167
+ // 检查是否需要压缩(行数或大小阈值)
1168
+ const needsCompression =
1169
+ oldLines > config.lineThreshold ||
1170
+ oldSize > config.sizeThreshold;
1171
+
1172
+ if (!needsCompression) {
1173
+ return {
1174
+ compressed: false,
1175
+ oldLines,
1176
+ newLines: oldLines,
1177
+ milestonesArchived: false,
1178
+ backupPath: null,
1179
+ reason: 'Below threshold'
1180
+ };
1181
+ }
1182
+
1183
+ // 检查频率限制
1184
+ if (stateDir && !canAutoCompress(stateDir)) {
1185
+ return {
1186
+ compressed: false,
1187
+ oldLines,
1188
+ newLines: oldLines,
1189
+ milestonesArchived: false,
1190
+ backupPath: null,
1191
+ reason: 'Rate limited (24h interval)'
1192
+ };
1193
+ }
1194
+
1195
+ // 1. 提取里程碑
1196
+ const version = extractVersion(oldContent);
1197
+ const milestones = extractMilestones(oldContent);
1198
+
1199
+ // 2. 归档里程碑到 daily memory
1200
+ let milestonesArchived = false;
1201
+ if (workspaceDir) {
1202
+ const archivePath = archiveMilestonesToDaily(workspaceDir, milestones, version);
1203
+ milestonesArchived = archivePath !== null;
1204
+ }
1205
+
1206
+ // 3. 清理过期信息(传入配置)
1207
+ let newContent = cleanupStaleInfo(oldContent, workspaceDir, config);
1208
+
1209
+ // 4. 压缩内容(使用 extractSummary)
1210
+ newContent = extractSummary(newContent, 50);
1211
+
1212
+ // 5. 递增版本号和日期
1213
+ const newVersion = `${parseInt(version, 10) + 1}`;
1214
+ const today = new Date().toISOString().split('T')[0];
1215
+ newContent = newContent
1216
+ .replace(/\*\*版本\*\*:\s*v[\d.]+/i, `**版本**: v${newVersion}`)
1217
+ .replace(/\*\*更新\*\*:\s*\d{4}-\d{2}-\d{2}/, `**更新**: ${today}`);
1218
+
1219
+ // 6. 备份原版本
1220
+ const backupPath = backupToHistory(focusPath, oldContent);
1221
+
1222
+ // 7. 清理过期历史
1223
+ cleanupHistory(focusPath);
1224
+
1225
+ // 8. 写入新内容
1226
+ fs.writeFileSync(focusPath, newContent, 'utf-8');
1227
+
1228
+ // 9. 记录压缩时间
1229
+ if (stateDir) {
1230
+ recordCompressTime(stateDir);
1231
+ }
1232
+
1233
+ const newLines = newContent.split('\n').length;
1234
+
1235
+ return {
1236
+ compressed: true,
1237
+ oldLines,
1238
+ newLines,
1239
+ milestonesArchived,
1240
+ backupPath,
1241
+ reason: `Auto-compressed: ${oldLines} → ${newLines} lines`
1242
+ };
1243
+ }
1244
+
1245
+ /**
1246
+ * 检查是否需要自动压缩
1247
+ */
1248
+ export function needsAutoCompression(focusPath: string, stateDir?: string): boolean {
1249
+ if (!fs.existsSync(focusPath)) {
1250
+ return false;
1251
+ }
1252
+
1253
+ try {
1254
+ const config = stateDir ? loadCompressionConfig(stateDir) : DEFAULT_COMPRESSION_CONFIG;
1255
+ const content = fs.readFileSync(focusPath, 'utf-8');
1256
+ const lines = content.split('\n').length;
1257
+ const size = Buffer.byteLength(content, 'utf-8');
1258
+
1259
+ return lines > config.lineThreshold || size > config.sizeThreshold;
1260
+ } catch {
1261
+ return false;
1262
+ }
1263
+ }
1264
+
1265
+ // ============================================================================
1266
+ // 格式验证与模板恢复
1267
+ // ============================================================================
1268
+
1269
+ /** CURRENT_FOCUS 模板路径(相对于插件根目录) */
1270
+ const CURRENT_FOCUS_TEMPLATE_PATH = 'templates/workspace/okr/CURRENT_FOCUS.md';
1271
+
1272
+ /**
1273
+ * 验证 CURRENT_FOCUS.md 格式
1274
+ *
1275
+ * 仅校验会导致程序崩溃的核心问题,不过度校验
1276
+ *
1277
+ * @param content 文件内容
1278
+ * @returns 验证结果
1279
+ */
1280
+ export function validateCurrentFocus(content: string): {
1281
+ valid: boolean;
1282
+ errors: string[];
1283
+ warnings: string[];
1284
+ } {
1285
+ const errors: string[] = [];
1286
+ const warnings: string[] = [];
1287
+
1288
+ // 仅检查会导致程序崩溃的核心问题
1289
+
1290
+ // 1. 文件为空
1291
+ if (!content || !content.trim()) {
1292
+ errors.push('文件为空');
1293
+ return { valid: false, errors, warnings };
1294
+ }
1295
+
1296
+ // 2. 检查是否是有效的文本(排除二进制乱码)
1297
+ // 如果有超过 50% 的非打印字符,认为是乱码
1298
+ const nonPrintable = content.split('').filter(c => {
1299
+ const code = c.charCodeAt(0);
1300
+ // 允许:换行、制表符、中文、英文、数字、标点
1301
+ return code < 32 && code !== 10 && code !== 13 && code !== 9;
1302
+ }).length;
1303
+
1304
+ if (nonPrintable > content.length * 0.5) {
1305
+ errors.push('文件内容损坏(可能是二进制乱码)');
1306
+ return { valid: false, errors, warnings };
1307
+ }
1308
+
1309
+ // 以下仅作为警告,不触发恢复
1310
+
1311
+ // 提示缺少建议的章节(不影响程序运行)
1312
+ if (!content.includes('下一步') && !content.includes('Next')) {
1313
+ warnings.push('缺少下一步章节(建议保留)');
1314
+ }
1315
+
1316
+ return {
1317
+ valid: true, // 只要不崩溃就认为是 valid
1318
+ errors,
1319
+ warnings
1320
+ };
1321
+ }
1322
+
1323
+ /**
1324
+ * 从模板恢复 CURRENT_FOCUS.md
1325
+ *
1326
+ * @param focusPath CURRENT_FOCUS.md 路径
1327
+ * @param extensionRoot 插件根目录
1328
+ * @returns 恢复是否成功
1329
+ */
1330
+ export function recoverFromTemplate(
1331
+ focusPath: string,
1332
+ extensionRoot: string
1333
+ ): {
1334
+ success: boolean;
1335
+ error?: string;
1336
+ templatePath?: string;
1337
+ } {
1338
+ try {
1339
+ // 查找模板文件
1340
+ const templatePath = path.join(extensionRoot, CURRENT_FOCUS_TEMPLATE_PATH);
1341
+
1342
+ if (!fs.existsSync(templatePath)) {
1343
+ return {
1344
+ success: false,
1345
+ error: `Template not found: ${templatePath}`
1346
+ };
1347
+ }
1348
+
1349
+ // 读取模板
1350
+ let template = fs.readFileSync(templatePath, 'utf-8');
1351
+
1352
+ // 替换日期占位符
1353
+ const today = new Date().toISOString().split('T')[0];
1354
+ template = template.replace(/{YYYY-MM-DD}/g, today);
1355
+
1356
+ // 备份损坏的文件(如果存在)
1357
+ if (fs.existsSync(focusPath)) {
1358
+ const backupPath = `${focusPath}.corrupted.${Date.now()}.md`;
1359
+ fs.copyFileSync(focusPath, backupPath);
1360
+ }
1361
+
1362
+ // 确保目录存在
1363
+ const focusDir = path.dirname(focusPath);
1364
+ if (!fs.existsSync(focusDir)) {
1365
+ fs.mkdirSync(focusDir, { recursive: true });
1366
+ }
1367
+
1368
+ // 写入恢复的内容
1369
+ fs.writeFileSync(focusPath, template, 'utf-8');
1370
+
1371
+ return {
1372
+ success: true,
1373
+ templatePath
1374
+ };
1375
+ } catch (error) {
1376
+ return {
1377
+ success: false,
1378
+ error: String(error)
1379
+ };
1380
+ }
1381
+ }
1382
+
1383
+ /**
1384
+ * 安全读取 CURRENT_FOCUS.md(自动验证 + 恢复)
1385
+ *
1386
+ * 仅在文件为空或损坏时才恢复,其他情况正常使用
1387
+ *
1388
+ * @param focusPath CURRENT_FOCUS.md 路径
1389
+ * @param extensionRoot 插件根目录
1390
+ * @param logger 日志记录器
1391
+ * @returns 文件内容和恢复状态
1392
+ */
1393
+ export function safeReadCurrentFocus(
1394
+ focusPath: string,
1395
+ extensionRoot: string,
1396
+ logger?: { warn?: (msg: string) => void; info?: (msg: string) => void }
1397
+ ): {
1398
+ content: string;
1399
+ recovered: boolean;
1400
+ validationErrors: string[];
1401
+ } {
1402
+ // 文件不存在,从模板创建
1403
+ if (!fs.existsSync(focusPath)) {
1404
+ const result = recoverFromTemplate(focusPath, extensionRoot);
1405
+ if (result.success) {
1406
+ logger?.info?.(`[PD:Focus] Created CURRENT_FOCUS.md from template`);
1407
+ return {
1408
+ content: fs.readFileSync(focusPath, 'utf-8'),
1409
+ recovered: true,
1410
+ validationErrors: []
1411
+ };
1412
+ }
1413
+ return {
1414
+ content: '',
1415
+ recovered: false,
1416
+ validationErrors: [`Failed to create from template: ${result.error}`]
1417
+ };
1418
+ }
1419
+
1420
+ // 读取并验证
1421
+ const content = fs.readFileSync(focusPath, 'utf-8');
1422
+ const validation = validateCurrentFocus(content);
1423
+
1424
+ // 记录警告(不触发恢复)
1425
+ if (validation.warnings.length > 0) {
1426
+ logger?.warn?.(`[PD:Focus] CURRENT_FOCUS.md warnings: ${validation.warnings.join(', ')}`);
1427
+ }
1428
+
1429
+ // 仅在有严重错误时才恢复
1430
+ if (!validation.valid) {
1431
+ logger?.warn?.(`[PD:Focus] CURRENT_FOCUS.md corrupted: ${validation.errors.join(', ')}`);
1432
+
1433
+ const result = recoverFromTemplate(focusPath, extensionRoot);
1434
+ if (result.success) {
1435
+ logger?.info?.(`[PD:Focus] Recovered CURRENT_FOCUS.md from template`);
1436
+ return {
1437
+ content: fs.readFileSync(focusPath, 'utf-8'),
1438
+ recovered: true,
1439
+ validationErrors: validation.errors
1440
+ };
1441
+ }
1442
+
1443
+ // 恢复失败,返回原始内容(让系统继续运行)
1444
+ logger?.warn?.(`[PD:Focus] Failed to recover: ${result.error}`);
1445
+ return {
1446
+ content,
1447
+ recovered: false,
1448
+ validationErrors: validation.errors
1449
+ };
1450
+ }
1451
+
1452
+ // 正常情况:文件有效
1453
+ return {
1454
+ content,
1455
+ recovered: false,
1456
+ validationErrors: []
1457
+ };
1458
+ }