principles-disciple 1.71.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.
- package/openclaw.plugin.json +10 -5
- package/package.json +17 -19
- package/scripts/acceptance-test.mjs +16 -73
- package/scripts/sync-plugin.mjs +382 -77
- package/src/commands/archive-impl.ts +2 -1
- package/src/commands/capabilities.ts +2 -2
- package/src/commands/context.ts +2 -2
- package/src/commands/disable-impl.ts +2 -1
- package/src/commands/evolution-status.ts +16 -16
- package/src/commands/export.ts +12 -67
- package/src/commands/pain.ts +91 -1
- package/src/commands/principle-rollback.ts +2 -1
- package/src/commands/promote-impl.ts +7 -43
- package/src/commands/rollback-impl.ts +2 -1
- package/src/commands/rollback.ts +2 -1
- package/src/commands/samples.ts +2 -1
- package/src/commands/thinking-os.ts +2 -1
- package/src/config/errors.ts +18 -2
- package/src/constants/diagnostician.ts +2 -2
- package/src/constants/tools.ts +2 -1
- package/src/core/__tests__/focus-history.test.ts +210 -0
- package/src/core/config.ts +1 -1
- package/src/core/confirm-first-gate.ts +255 -0
- package/src/core/correction-cue-learner.ts +2 -136
- package/src/core/correction-types.ts +16 -88
- package/src/core/dictionary.ts +19 -20
- package/src/core/empathy-keyword-matcher.ts +17 -289
- package/src/core/empathy-types.ts +18 -229
- package/src/core/event-log.ts +38 -132
- package/src/core/evolution-reducer.ts +21 -2
- package/src/core/evolution-types.ts +76 -464
- package/src/core/file-store.ts +80 -0
- package/src/core/focus-history.ts +228 -955
- package/src/core/local-worker-routing.ts +34 -314
- package/src/core/merge-gate-audit.ts +0 -195
- package/src/core/pain-diagnostic-gate.ts +154 -0
- package/src/core/pain-signal.ts +21 -138
- package/src/core/pain.ts +15 -88
- package/src/core/pd-task-reconciler.ts +26 -115
- package/src/core/pd-task-service.ts +9 -9
- package/src/core/pd-task-types.ts +23 -127
- package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
- package/src/core/principle-compiler/code-validator.ts +15 -42
- package/src/core/principle-compiler/compiler.ts +100 -15
- package/src/core/principle-compiler/index.ts +5 -2
- package/src/core/principle-compiler/template-generator.ts +4 -104
- package/src/core/principle-injection.ts +10 -202
- package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
- package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
- package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
- package/src/core/principle-tree-ledger-adapter.ts +145 -0
- package/src/core/principle-tree-ledger.ts +8 -6
- package/src/core/reflection/reflection-context.ts +14 -109
- package/src/core/replay-engine.ts +8 -500
- package/src/core/rule-host-helpers.ts +5 -35
- package/src/core/rule-host-types.ts +10 -82
- package/src/core/rule-host.ts +6 -63
- package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
- package/src/core/session-tracker.ts +87 -101
- package/src/core/shadow-observation-registry.ts +19 -48
- package/src/core/trajectory.ts +3 -1
- package/src/core/workflow-funnel-loader.ts +62 -68
- package/src/core/workspace-context.ts +46 -0
- package/src/core/workspace-dir-service.ts +1 -1
- package/src/core/workspace-dir-validation.ts +18 -9
- package/src/hooks/AGENTS.md +1 -1
- package/src/hooks/gate-block-helper.ts +46 -44
- package/src/hooks/gate.ts +207 -7
- package/src/hooks/lifecycle.ts +30 -32
- package/src/hooks/llm.ts +60 -32
- package/src/hooks/pain.ts +297 -103
- package/src/hooks/prompt.ts +469 -339
- package/src/hooks/subagent.ts +2 -29
- package/src/i18n/commands.ts +2 -10
- package/src/index.ts +95 -85
- package/src/openclaw-sdk.ts +311 -0
- package/src/service/central-database.ts +8 -4
- package/src/service/evolution-queue-migration.ts +2 -1
- package/src/service/evolution-worker.ts +163 -1786
- package/src/service/internalization-trigger-adapter.ts +302 -0
- package/src/service/keyword-optimization-service.ts +4 -4
- package/src/service/monitoring-query-service.ts +1 -215
- package/src/service/queue-io.ts +60 -331
- package/src/service/runtime-summary-service.ts +115 -18
- package/src/service/subagent-workflow/index.ts +0 -41
- package/src/service/subagent-workflow/types.ts +9 -120
- package/src/service/subagent-workflow/workflow-store.ts +2 -119
- package/src/service/workflow-watchdog.ts +0 -43
- package/src/types/event-payload.ts +16 -74
- package/src/types/event-types.ts +39 -547
- package/src/types/hygiene-types.ts +7 -30
- package/src/types/principle-tree-schema.ts +20 -222
- package/src/types/queue.ts +15 -70
- package/src/types/runtime-summary.ts +5 -49
- package/src/utils/io.ts +10 -0
- package/src/utils/retry.ts +1 -1
- package/src/utils/shadow-fingerprint.ts +2 -2
- package/src/utils/workspace-resolver.ts +50 -0
- package/templates/langs/en/core/AGENTS.md +2 -2
- package/templates/langs/en/core/BOOT.md +1 -1
- package/templates/langs/en/core/HEARTBEAT.md +2 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/en/skills/evolve-task/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-mentor/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
- package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
- package/templates/langs/zh/core/AGENTS.md +2 -2
- package/templates/langs/zh/core/BOOT.md +1 -1
- package/templates/langs/zh/core/HEARTBEAT.md +2 -2
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
- package/templates/langs/zh/skills/evolve-task/SKILL.md +2 -2
- package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
- package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
- package/tests/build-artifacts.test.ts +1 -3
- package/tests/commands/evolution-status.test.ts +0 -118
- package/tests/core/bootstrap-rules.test.ts +1 -1
- package/tests/core/config.test.ts +1 -1
- package/tests/core/event-log.test.ts +35 -0
- package/tests/core/evolution-engine.test.ts +610 -0
- package/tests/core/file-store.test.ts +102 -0
- package/tests/core/focus-history.test.ts +203 -11
- package/tests/core/merge-gate-audit.test.ts +2 -169
- package/tests/core/model-deployment-registry.test.ts +7 -1
- package/tests/core/model-training-registry.test.ts +19 -0
- package/tests/core/observability.test.ts +0 -1
- package/tests/core/pain-diagnostic-gate.test.ts +498 -0
- package/tests/core/pain.test.ts +0 -1
- package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
- package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
- package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
- package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
- package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
- package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
- package/tests/core/reflection-context.test.ts +0 -14
- package/tests/core/replay-engine.test.ts +127 -215
- package/tests/core/rule-host-helpers.test.ts +2 -2
- package/tests/core/rule-implementation-runtime.test.ts +0 -27
- package/tests/core/workflow-funnel-loader.test.ts +162 -0
- package/tests/core/workspace-dir-validation.test.ts +8 -1
- package/tests/core-anti-growth.test.ts +192 -0
- package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
- package/tests/hooks/confirm-first-gate.test.ts +333 -0
- package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
- package/tests/hooks/gate-auto-correct.test.ts +665 -0
- package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
- package/tests/hooks/pain.test.ts +269 -12
- package/tests/hooks/prompt-characterization.test.ts +500 -0
- package/tests/hooks/prompt-size-guard.test.ts +329 -0
- package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
- package/tests/index.test.ts +94 -1
- package/tests/integration/auto-entry-gate.test.ts +248 -0
- package/tests/integration/internalization-trigger-guard.test.ts +69 -0
- package/tests/integration/m8-legacy-paths.test.ts +63 -0
- package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
- package/tests/plugin-config-resolution-cutover.test.ts +359 -0
- package/tests/runtime-v2-discovery-guard.test.ts +154 -0
- package/tests/service/central-database.test.ts +457 -0
- package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
- package/tests/service/evolution-worker.timeout.test.ts +11 -129
- package/tests/service/internalization-trigger-adapter.test.ts +251 -0
- package/tests/service/monitoring-query-service.test.ts +1 -47
- package/tests/service/queue-io.test.ts +1 -62
- package/tests/service/runtime-summary-service.test.ts +184 -3
- package/tests/service/workflow-watchdog.test.ts +0 -91
- package/tests/utils/file-lock.test.ts +5 -3
- package/tests/utils/session-key.test.ts +52 -0
- package/tests/utils/subagent-probe.test.ts +48 -1
- package/vitest.config.ts +4 -11
- package/.planning/codebase/ARCHITECTURE.md +0 -157
- package/.planning/codebase/CONCERNS.md +0 -145
- package/.planning/codebase/CONVENTIONS.md +0 -148
- package/.planning/codebase/INTEGRATIONS.md +0 -81
- package/.planning/codebase/STACK.md +0 -87
- package/.planning/codebase/STRUCTURE.md +0 -193
- package/.planning/codebase/TESTING.md +0 -243
- package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
- package/docs/COMMAND_REFERENCE.md +0 -76
- package/docs/COMMAND_REFERENCE_EN.md +0 -79
- package/scripts/build-web.mjs +0 -46
- package/scripts/diagnose-nocturnal.mjs +0 -537
- package/scripts/seed-nocturnal-scenarios.mjs +0 -384
- package/src/commands/nocturnal-review.ts +0 -322
- package/src/commands/nocturnal-rollout.ts +0 -790
- package/src/commands/nocturnal-train.ts +0 -986
- package/src/commands/pd-reflect.ts +0 -88
- package/src/core/adaptive-thresholds.ts +0 -478
- package/src/core/diagnostician-task-store.ts +0 -192
- package/src/core/nocturnal-arbiter.ts +0 -715
- package/src/core/nocturnal-artifact-lineage.ts +0 -116
- package/src/core/nocturnal-artificer.ts +0 -257
- package/src/core/nocturnal-candidate-scoring.ts +0 -530
- package/src/core/nocturnal-compliance.ts +0 -1146
- package/src/core/nocturnal-dataset.ts +0 -763
- package/src/core/nocturnal-executability.ts +0 -428
- package/src/core/nocturnal-export.ts +0 -499
- package/src/core/nocturnal-paths.ts +0 -240
- package/src/core/nocturnal-reasoning-deriver.ts +0 -343
- package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
- package/src/core/nocturnal-snapshot-contract.ts +0 -99
- package/src/core/nocturnal-trajectory-extractor.ts +0 -512
- package/src/core/nocturnal-trinity-types.ts +0 -218
- package/src/core/nocturnal-trinity.ts +0 -2680
- package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
- package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
- package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
- package/src/http/principles-console-route.ts +0 -709
- package/src/service/central-health-service.ts +0 -49
- package/src/service/central-overview-service.ts +0 -138
- package/src/service/control-ui-query-service.ts +0 -900
- package/src/service/cooldown-strategy.ts +0 -97
- package/src/service/evolution-pain-context.ts +0 -79
- package/src/service/evolution-query-service.ts +0 -407
- package/src/service/health-query-service.ts +0 -1038
- package/src/service/nocturnal-config.ts +0 -214
- package/src/service/nocturnal-runtime.ts +0 -734
- package/src/service/nocturnal-service.ts +0 -1605
- package/src/service/nocturnal-target-selector.ts +0 -545
- package/src/service/sleep-cycle.ts +0 -157
- package/src/service/startup-reconciler.ts +0 -112
- package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
- package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
- package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
- package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
- package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
- package/src/tools/write-pain-flag.ts +0 -215
- package/tests/commands/nocturnal-review.test.ts +0 -448
- package/tests/commands/nocturnal-train.test.ts +0 -97
- package/tests/commands/pd-reflect.test.ts +0 -49
- package/tests/core/adaptive-thresholds.test.ts +0 -261
- package/tests/core/nocturnal-arbiter.test.ts +0 -559
- package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
- package/tests/core/nocturnal-artificer.test.ts +0 -241
- package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
- package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
- package/tests/core/nocturnal-compliance.test.ts +0 -646
- package/tests/core/nocturnal-dataset.test.ts +0 -892
- package/tests/core/nocturnal-e2e.test.ts +0 -234
- package/tests/core/nocturnal-executability.test.ts +0 -357
- package/tests/core/nocturnal-export.test.ts +0 -517
- package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
- package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
- package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
- package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
- package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
- package/tests/core/nocturnal-trinity.test.ts +0 -2053
- package/tests/core/pain-auto-repair.test.ts +0 -96
- package/tests/core/pain-integration.test.ts +0 -510
- package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
- package/tests/http/principles-console-route.test.ts +0 -162
- package/tests/integration/chaos-resilience.test.ts +0 -348
- package/tests/integration/empathy-workflow-integration.test.ts +0 -626
- package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
- package/tests/service/control-ui-query-service.test.ts +0 -121
- package/tests/service/cooldown-strategy.test.ts +0 -164
- package/tests/service/data-endpoints-regression.test.ts +0 -834
- package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
- package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
- package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
- package/tests/service/nocturnal-runtime.test.ts +0 -473
- package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
- package/tests/service/nocturnal-target-selector.test.ts +0 -615
- package/tests/service/startup-reconciler.test.ts +0 -148
- package/tests/tools/write-pain-flag.test.ts +0 -358
- package/ui/src/App.tsx +0 -45
- package/ui/src/api.ts +0 -220
- package/ui/src/charts.tsx +0 -955
- package/ui/src/components/ErrorState.tsx +0 -6
- package/ui/src/components/Loading.tsx +0 -13
- package/ui/src/components/ProtectedRoute.tsx +0 -12
- package/ui/src/components/Shell.tsx +0 -91
- package/ui/src/components/WorkspaceConfig.tsx +0 -178
- package/ui/src/components/index.ts +0 -5
- package/ui/src/context/auth.tsx +0 -80
- package/ui/src/context/theme.tsx +0 -66
- package/ui/src/hooks/useAutoRefresh.ts +0 -39
- package/ui/src/i18n/ui.ts +0 -473
- package/ui/src/main.tsx +0 -16
- package/ui/src/pages/EvolutionPage.tsx +0 -333
- package/ui/src/pages/FeedbackPage.tsx +0 -138
- package/ui/src/pages/GateMonitorPage.tsx +0 -136
- package/ui/src/pages/LoginPage.tsx +0 -89
- package/ui/src/pages/OverviewPage.tsx +0 -599
- package/ui/src/pages/SamplesPage.tsx +0 -174
- package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
- package/ui/src/styles.css +0 -2020
- package/ui/src/types.ts +0 -384
- 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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
190
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
.map(f =>
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
-
|
|
493
|
-
|
|
494
|
-
filePath
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
filePath
|
|
539
|
-
filePath
|
|
540
|
-
filePath
|
|
541
|
-
filePath
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
-
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
1300
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
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
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
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
|
-
|
|
1343
|
-
|
|
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,
|