principles-disciple 1.72.0 → 1.73.0

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