principles-disciple 1.72.0 → 1.74.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/INSTALL.md +1 -3
- 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/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 +29 -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/migration.ts +0 -1
- 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/path-resolver.ts +0 -1
- package/src/core/paths.ts +0 -1
- 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 +71 -64
- package/src/hooks/gate.ts +183 -31
- 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 +400 -440
- 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 +59 -16
- 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 +38 -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 +8 -20
- 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 +7 -7
- package/templates/langs/en/core/BOOT.md +1 -1
- package/templates/langs/en/core/HEARTBEAT.md +2 -2
- package/templates/langs/en/principles/THINKING_OS.md +3 -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/scripts/run.mjs +51 -15
- package/templates/langs/en/skills/evolve-task/SKILL.md +3 -3
- 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 +2 -3
- 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 +7 -7
- package/templates/langs/zh/core/BOOT.md +1 -1
- package/templates/langs/zh/core/HEARTBEAT.md +2 -2
- package/templates/langs/zh/principles/THINKING_OS.md +3 -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/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 +4 -4
- 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 +2 -3
- 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/migration.test.ts +7 -7
- 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/path-resolver.test.ts +1 -1
- package/tests/core/paths-refactor.test.ts +0 -22
- 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-context.test.ts +2 -2
- package/tests/core/workspace-dir-validation.test.ts +8 -1
- package/tests/core-anti-growth.test.ts +191 -0
- package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
- package/tests/hooks/confirm-first-removal.test.ts +188 -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-no-path-write-tool.test.ts +172 -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 +32 -17
- 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 +3 -1
- 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/templates/langs/en/skills/plan-script/SKILL.md +0 -32
- package/templates/langs/zh/skills/plan-script/SKILL.md +0 -32
- 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
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import { autoCompressFocus, cleanupStaleInfo } from '../focus-history.js';
|
|
6
|
+
|
|
7
|
+
function makeFocusContent(artifactRows: string[], extraLines = 0): string {
|
|
8
|
+
const lines: string[] = [
|
|
9
|
+
'# CURRENT_FOCUS',
|
|
10
|
+
'',
|
|
11
|
+
'**版本**: v1',
|
|
12
|
+
'**更新**: 2026-05-09',
|
|
13
|
+
'',
|
|
14
|
+
'## 📍 状态快照',
|
|
15
|
+
'',
|
|
16
|
+
'当前聚焦于核心功能开发。',
|
|
17
|
+
'',
|
|
18
|
+
'## 🔄 当前任务',
|
|
19
|
+
'',
|
|
20
|
+
'- [ ] 实现新功能',
|
|
21
|
+
'- [ ] 修复已知问题',
|
|
22
|
+
'- [ ] 完善测试覆盖',
|
|
23
|
+
'',
|
|
24
|
+
'## ➡️ 下一步',
|
|
25
|
+
'',
|
|
26
|
+
'1. 完成 PRI-82 E2E 测试',
|
|
27
|
+
'2. 提交代码审查',
|
|
28
|
+
'',
|
|
29
|
+
'## 🧠 Working Memory',
|
|
30
|
+
'',
|
|
31
|
+
'> Last updated: 2026-05-09T12:00:00Z',
|
|
32
|
+
'',
|
|
33
|
+
'### 📁 文件输出记录',
|
|
34
|
+
'',
|
|
35
|
+
'| 文件路径 | 操作 | 描述 |',
|
|
36
|
+
'|----------|------|------|',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
for (const row of artifactRows) {
|
|
40
|
+
lines.push(row);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
lines.push('');
|
|
44
|
+
lines.push('### ⚠️ 活动问题');
|
|
45
|
+
lines.push('- 测试覆盖不足 → 增加E2E测试');
|
|
46
|
+
lines.push('');
|
|
47
|
+
lines.push('### ➡️ 下一步行动');
|
|
48
|
+
lines.push('1. 完成回归测试');
|
|
49
|
+
lines.push('2. 提交PR');
|
|
50
|
+
lines.push('');
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < extraLines; i++) {
|
|
53
|
+
lines.push(`<!-- padding line ${i} -->`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return lines.join('\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('autoCompressFocus E2E regression (PRI-82)', () => {
|
|
60
|
+
let tmpDir: string;
|
|
61
|
+
let workspaceDir: string;
|
|
62
|
+
let stateDir: string;
|
|
63
|
+
let focusPath: string;
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-focus-e2e-'));
|
|
67
|
+
workspaceDir = tmpDir;
|
|
68
|
+
stateDir = path.join(tmpDir, '.state');
|
|
69
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
70
|
+
|
|
71
|
+
const focusDir = path.join(tmpDir, 'okr');
|
|
72
|
+
fs.mkdirSync(focusDir, { recursive: true });
|
|
73
|
+
focusPath = path.join(focusDir, 'CURRENT_FOCUS.md');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('removes missing-file artifact rows via filesystem filtering before core compression', () => {
|
|
81
|
+
const existingFile = path.join(workspaceDir, 'src', 'existing-module.ts');
|
|
82
|
+
|
|
83
|
+
fs.mkdirSync(path.join(workspaceDir, 'src'), { recursive: true });
|
|
84
|
+
fs.writeFileSync(existingFile, 'export const x = 1;');
|
|
85
|
+
|
|
86
|
+
const existingRow = '| `src/existing-module.ts` | modified | 核心模块 |';
|
|
87
|
+
const missingRow = '| `src/deleted-module.ts` | modified | 已删除模块 |';
|
|
88
|
+
|
|
89
|
+
const content = makeFocusContent([existingRow, missingRow], 90);
|
|
90
|
+
|
|
91
|
+
fs.writeFileSync(focusPath, content);
|
|
92
|
+
|
|
93
|
+
const result = autoCompressFocus(focusPath, workspaceDir, stateDir);
|
|
94
|
+
|
|
95
|
+
expect(result.compressed).toBe(true);
|
|
96
|
+
expect(result.newContent).toBeDefined();
|
|
97
|
+
|
|
98
|
+
expect(result.newContent!).not.toContain('deleted-module.ts');
|
|
99
|
+
|
|
100
|
+
expect(result.newLines).toBeLessThan(result.oldLines);
|
|
101
|
+
|
|
102
|
+
expect(result.reason).toContain('Auto-compressed');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('goes through core compressFocusContent path when threshold is exceeded', () => {
|
|
106
|
+
const content = makeFocusContent([
|
|
107
|
+
'| `src/utils.ts` | created | 工具函数 |',
|
|
108
|
+
], 90);
|
|
109
|
+
|
|
110
|
+
fs.writeFileSync(focusPath, content);
|
|
111
|
+
|
|
112
|
+
const result = autoCompressFocus(focusPath, workspaceDir, stateDir);
|
|
113
|
+
|
|
114
|
+
expect(result.compressed).toBe(true);
|
|
115
|
+
|
|
116
|
+
expect(result.reason).toContain('Auto-compressed');
|
|
117
|
+
|
|
118
|
+
const writtenContent = fs.readFileSync(focusPath, 'utf-8');
|
|
119
|
+
expect(writtenContent).not.toBe(content);
|
|
120
|
+
expect(writtenContent.split('\n').length).toBeLessThan(content.split('\n').length);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('does not compress when below threshold', () => {
|
|
124
|
+
const content = makeFocusContent([
|
|
125
|
+
'| `src/utils.ts` | created | 工具函数 |',
|
|
126
|
+
], 0);
|
|
127
|
+
|
|
128
|
+
fs.writeFileSync(focusPath, content);
|
|
129
|
+
|
|
130
|
+
const result = autoCompressFocus(focusPath, workspaceDir, stateDir);
|
|
131
|
+
|
|
132
|
+
expect(result.compressed).toBe(false);
|
|
133
|
+
expect(result.reason).toBe('Below threshold');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('creates backup in history when compressing', () => {
|
|
137
|
+
const content = makeFocusContent([
|
|
138
|
+
'| `src/utils.ts` | created | 工具函数 |',
|
|
139
|
+
], 90);
|
|
140
|
+
|
|
141
|
+
fs.writeFileSync(focusPath, content);
|
|
142
|
+
|
|
143
|
+
const result = autoCompressFocus(focusPath, workspaceDir, stateDir);
|
|
144
|
+
|
|
145
|
+
expect(result.compressed).toBe(true);
|
|
146
|
+
expect(result.backupPath).not.toBeNull();
|
|
147
|
+
|
|
148
|
+
const historyDir = path.join(path.dirname(focusPath), '.history');
|
|
149
|
+
expect(fs.existsSync(historyDir)).toBe(true);
|
|
150
|
+
|
|
151
|
+
const historyFiles = fs.readdirSync(historyDir).filter(f => f.startsWith('CURRENT_FOCUS.v'));
|
|
152
|
+
expect(historyFiles.length).toBeGreaterThanOrEqual(1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('handles workspace without existing files gracefully', () => {
|
|
156
|
+
const content = makeFocusContent([
|
|
157
|
+
'| `src/ghost-file.ts` | modified | 不存在的文件 |',
|
|
158
|
+
], 90);
|
|
159
|
+
|
|
160
|
+
fs.writeFileSync(focusPath, content);
|
|
161
|
+
|
|
162
|
+
const result = autoCompressFocus(focusPath, workspaceDir, stateDir);
|
|
163
|
+
|
|
164
|
+
expect(result.compressed).toBe(true);
|
|
165
|
+
|
|
166
|
+
expect(result.newContent!).not.toContain('ghost-file.ts');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('cleanupStaleInfo filesystem filtering (PRI-82)', () => {
|
|
171
|
+
let tmpDir: string;
|
|
172
|
+
let workspaceDir: string;
|
|
173
|
+
|
|
174
|
+
beforeEach(() => {
|
|
175
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-cleanup-'));
|
|
176
|
+
workspaceDir = tmpDir;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
afterEach(() => {
|
|
180
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('preserves artifact row for existing file and removes row for missing file', () => {
|
|
184
|
+
const existingFile = path.join(workspaceDir, 'src', 'real.ts');
|
|
185
|
+
fs.mkdirSync(path.join(workspaceDir, 'src'), { recursive: true });
|
|
186
|
+
fs.writeFileSync(existingFile, 'export const a = 1;');
|
|
187
|
+
|
|
188
|
+
const content = makeFocusContent([
|
|
189
|
+
'| `src/real.ts` | modified | 真实文件 |',
|
|
190
|
+
'| `src/phantom.ts` | modified | 幽灵文件 |',
|
|
191
|
+
], 0);
|
|
192
|
+
|
|
193
|
+
const result = cleanupStaleInfo(content, workspaceDir);
|
|
194
|
+
|
|
195
|
+
expect(result).toContain('real.ts');
|
|
196
|
+
expect(result).not.toContain('phantom.ts');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('preserves all artifact rows when no workspaceDir provided', () => {
|
|
200
|
+
const content = makeFocusContent([
|
|
201
|
+
'| `src/real.ts` | modified | 真实文件 |',
|
|
202
|
+
'| `src/phantom.ts` | modified | 幽灵文件 |',
|
|
203
|
+
], 0);
|
|
204
|
+
|
|
205
|
+
const result = cleanupStaleInfo(content);
|
|
206
|
+
|
|
207
|
+
expect(result).toContain('real.ts');
|
|
208
|
+
expect(result).toContain('phantom.ts');
|
|
209
|
+
});
|
|
210
|
+
});
|
package/src/core/config.ts
CHANGED
|
@@ -123,7 +123,7 @@ export const DEFAULT_SETTINGS: PainSettings = {
|
|
|
123
123
|
promotion_similarity_threshold: 0.8
|
|
124
124
|
},
|
|
125
125
|
scores: {
|
|
126
|
-
paralysis:
|
|
126
|
+
paralysis: 45, // Must be >= pain_trigger (40) so llm_paralysis can trigger diagnosis (PRI-274)
|
|
127
127
|
default_confusion: 30,
|
|
128
128
|
default_loop: 40,
|
|
129
129
|
tool_failure_friction: 15, // Reduced from 30. A failing tool shouldn't instantly cripple the AI
|
|
@@ -21,44 +21,19 @@ import {
|
|
|
21
21
|
CORRECTION_SEED_KEYWORDS,
|
|
22
22
|
MAX_CORRECTION_KEYWORDS,
|
|
23
23
|
} from './correction-types.js';
|
|
24
|
-
import { checkKeywordOptCooldown, recordKeywordOptRun } from '../service/nocturnal-runtime.js';
|
|
25
24
|
import { atomicWriteFileSync } from '../utils/io.js';
|
|
26
25
|
|
|
27
26
|
const KEYWORD_STORE_FILE = 'correction_keywords.json';
|
|
28
27
|
|
|
29
|
-
// CORR-08: Daily optimization throttle (uses checkCooldown in nocturnal-runtime.ts)
|
|
30
|
-
// Note: throttle state is stored in nocturnal-runtime.json, not a separate file.
|
|
31
|
-
|
|
32
|
-
// Weight bounds for correction keywords (D-39-03, D-39-15)
|
|
33
28
|
const MIN_KEYWORD_WEIGHT = 0.1;
|
|
34
29
|
const MAX_KEYWORD_WEIGHT = 0.9;
|
|
35
30
|
|
|
36
|
-
// =========================================================================
|
|
37
|
-
// Module-level cache (D-04, D-05)
|
|
38
|
-
// =========================================================================
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Invalidated on every successful save so the next load re-reads from disk.
|
|
42
|
-
* Set to null intentionally — never assume disk and memory are in sync after a write.
|
|
43
|
-
*/
|
|
44
31
|
let _correctionCueCache: CorrectionKeywordStore | null = null;
|
|
45
32
|
|
|
46
|
-
/**
|
|
47
|
-
* Resets the module-level cache (for testing only).
|
|
48
|
-
* @internal
|
|
49
|
-
*/
|
|
50
33
|
export function _resetCorrectionCueCache(): void {
|
|
51
34
|
_correctionCueCache = null;
|
|
52
35
|
}
|
|
53
36
|
|
|
54
|
-
// =========================================================================
|
|
55
|
-
// Default store factory
|
|
56
|
-
// =========================================================================
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Creates a fresh store populated with the 16 seed keywords (D-08, D-09).
|
|
60
|
-
* addedAt is stamped with the current ISO timestamp.
|
|
61
|
-
*/
|
|
62
37
|
function createDefaultStore(): CorrectionKeywordStore {
|
|
63
38
|
const now = new Date().toISOString();
|
|
64
39
|
const keywords: CorrectionKeyword[] = CORRECTION_SEED_KEYWORDS.map((k) => ({
|
|
@@ -69,14 +44,6 @@ function createDefaultStore(): CorrectionKeywordStore {
|
|
|
69
44
|
return { keywords, version: 1, lastOptimizedAt: now };
|
|
70
45
|
}
|
|
71
46
|
|
|
72
|
-
// =========================================================================
|
|
73
|
-
// Load / save
|
|
74
|
-
// =========================================================================
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Loads the keyword store from disk.
|
|
78
|
-
* On first run (file absent) or parse failure, creates and persists the default store.
|
|
79
|
-
*/
|
|
80
47
|
export function loadCorrectionKeywordStore(stateDir: string): CorrectionKeywordStore {
|
|
81
48
|
if (_correctionCueCache) return _correctionCueCache;
|
|
82
49
|
|
|
@@ -88,23 +55,16 @@ export function loadCorrectionKeywordStore(stateDir: string): CorrectionKeywordS
|
|
|
88
55
|
_correctionCueCache = JSON.parse(raw) as CorrectionKeywordStore;
|
|
89
56
|
return _correctionCueCache;
|
|
90
57
|
} catch {
|
|
91
|
-
|
|
58
|
+
void 0;
|
|
92
59
|
}
|
|
93
60
|
}
|
|
94
61
|
|
|
95
|
-
// File absent or corrupt: seed the store and persist it (D-01)
|
|
96
62
|
const defaultStore = createDefaultStore();
|
|
97
63
|
saveCorrectionKeywordStore(stateDir, defaultStore);
|
|
98
64
|
_correctionCueCache = defaultStore;
|
|
99
65
|
return _correctionCueCache;
|
|
100
66
|
}
|
|
101
67
|
|
|
102
|
-
/**
|
|
103
|
-
* Atomically saves the keyword store to disk (D-03, T-38-02).
|
|
104
|
-
* Uses temp-file-then-rename to ensure the file is always valid JSON or
|
|
105
|
-
* the previous valid state if a crash occurs mid-write.
|
|
106
|
-
* MUST invalidate the cache after the rename (D-05).
|
|
107
|
-
*/
|
|
108
68
|
export function saveCorrectionKeywordStore(
|
|
109
69
|
stateDir: string,
|
|
110
70
|
store: CorrectionKeywordStore
|
|
@@ -114,29 +74,17 @@ export function saveCorrectionKeywordStore(
|
|
|
114
74
|
fs.mkdirSync(stateDir, { recursive: true });
|
|
115
75
|
atomicWriteFileSync(filePath, JSON.stringify(store, null, 2));
|
|
116
76
|
|
|
117
|
-
// Invalidate cache so the next read re-loads from disk (D-05)
|
|
118
77
|
_correctionCueCache = null;
|
|
119
78
|
}
|
|
120
79
|
|
|
121
|
-
// =========================================================================
|
|
122
|
-
// Throttle helpers (CORR-08)
|
|
123
|
-
// =========================================================================
|
|
124
|
-
// Singleton state
|
|
125
|
-
// =========================================================================
|
|
126
|
-
|
|
127
80
|
let _instance: CorrectionCueLearner | null = null;
|
|
128
81
|
let _lastStateDir: string | null = null;
|
|
129
82
|
|
|
130
|
-
/** Resets singleton state (for testing only). @internal */
|
|
131
83
|
export function _resetCorrectionCueLearnerInstance(): void {
|
|
132
84
|
_instance = null;
|
|
133
85
|
_lastStateDir = null;
|
|
134
86
|
}
|
|
135
87
|
|
|
136
|
-
// =========================================================================
|
|
137
|
-
// CorrectionCueLearner class
|
|
138
|
-
// =========================================================================
|
|
139
|
-
|
|
140
88
|
export class CorrectionCueLearner {
|
|
141
89
|
private readonly store: CorrectionKeywordStore;
|
|
142
90
|
private readonly stateDir: string;
|
|
@@ -146,17 +94,6 @@ export class CorrectionCueLearner {
|
|
|
146
94
|
this.store = loadCorrectionKeywordStore(stateDir);
|
|
147
95
|
}
|
|
148
96
|
|
|
149
|
-
// ── Public API ──────────────────────────────────────────────────────────
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Checks whether text contains a correction cue (D-11).
|
|
153
|
-
* Pure read-only — does NOT modify the store.
|
|
154
|
-
* Normalisation is equivalent to the original detectCorrectionCue():
|
|
155
|
-
* trim → lowercase → strip punctuation
|
|
156
|
-
* Returns weighted score based on keyword accuracy (D-39-03, D-39-04).
|
|
157
|
-
*
|
|
158
|
-
* To record hits/TPs, call recordHit() and recordTruePositive() separately.
|
|
159
|
-
*/
|
|
160
97
|
match(text: string): CorrectionMatchResult {
|
|
161
98
|
const normalized = text
|
|
162
99
|
.trim()
|
|
@@ -168,9 +105,6 @@ export class CorrectionCueLearner {
|
|
|
168
105
|
|
|
169
106
|
for (const keyword of this.store.keywords) {
|
|
170
107
|
if (normalized.includes(keyword.term.toLowerCase())) {
|
|
171
|
-
// D-39-03, D-39-04: Weighted score formula
|
|
172
|
-
// No history (tp=0, fp=0) → accuracy = 1 (trust raw weight)
|
|
173
|
-
// Has history → accuracy = tp / (tp + fp) (proportional to true positive rate)
|
|
174
108
|
const tp = keyword.truePositiveCount ?? 0;
|
|
175
109
|
const fp = keyword.falsePositiveCount ?? 0;
|
|
176
110
|
const accuracy = (tp + fp) > 0 ? tp / (tp + fp) : 1;
|
|
@@ -184,7 +118,6 @@ export class CorrectionCueLearner {
|
|
|
184
118
|
const cappedScore = Math.min(1, totalScore);
|
|
185
119
|
const isMatched = matchedTerms.length > 0;
|
|
186
120
|
|
|
187
|
-
// D-39-04: Confidence derived from multiple signals
|
|
188
121
|
const termConfidence = Math.min(1, matchedTerms.length / 3);
|
|
189
122
|
const scoreConfidence = Math.min(1, cappedScore / 0.8);
|
|
190
123
|
const confidence = Math.max(termConfidence, scoreConfidence);
|
|
@@ -197,12 +130,6 @@ export class CorrectionCueLearner {
|
|
|
197
130
|
};
|
|
198
131
|
}
|
|
199
132
|
|
|
200
|
-
/**
|
|
201
|
-
* Records a keyword hit (for hitCount/FPR tracking).
|
|
202
|
-
* Increments hitCount and updates lastHitAt for all matched terms.
|
|
203
|
-
* Intentionally does NOT flush — hitCount is best-effort analytics,
|
|
204
|
-
* persisted by the next recordTruePositive() or flush() call.
|
|
205
|
-
*/
|
|
206
133
|
recordHits(terms: string[]): void {
|
|
207
134
|
for (const term of terms) {
|
|
208
135
|
const keywordIndex = this.store.keywords.findIndex(k => k.term.toLowerCase() === term.toLowerCase());
|
|
@@ -216,17 +143,12 @@ export class CorrectionCueLearner {
|
|
|
216
143
|
}
|
|
217
144
|
}
|
|
218
145
|
|
|
219
|
-
/**
|
|
220
|
-
* Records a confirmed true positive for the given keyword term.
|
|
221
|
-
* Increments truePositiveCount atomically.
|
|
222
|
-
*/
|
|
223
146
|
recordTruePositive(term: string): void {
|
|
224
147
|
const keyword = this.store.keywords.find(k => k.term.toLowerCase() === term.toLowerCase());
|
|
225
148
|
if (!keyword) return;
|
|
226
149
|
|
|
227
150
|
keyword.truePositiveCount = (keyword.truePositiveCount ?? 0) + 1;
|
|
228
151
|
|
|
229
|
-
// Update in-store reference
|
|
230
152
|
const keywordIndex = this.store.keywords.findIndex(k => k.term.toLowerCase() === term.toLowerCase());
|
|
231
153
|
if (keywordIndex >= 0) {
|
|
232
154
|
this.store.keywords[keywordIndex] = { ...keyword };
|
|
@@ -235,61 +157,23 @@ export class CorrectionCueLearner {
|
|
|
235
157
|
this.flush();
|
|
236
158
|
}
|
|
237
159
|
|
|
238
|
-
/**
|
|
239
|
-
* Records a confirmed false positive for the given keyword term.
|
|
240
|
-
* CORR-10: Decreases keyword weight by 20% (x0.8 multiplicative factor).
|
|
241
|
-
* D-39-17: Keywords at very low weight (<0.1) still match but contribute minimally.
|
|
242
|
-
*/
|
|
243
160
|
recordFalsePositive(term: string): void {
|
|
244
161
|
const keyword = this.store.keywords.find(k => k.term.toLowerCase() === term.toLowerCase());
|
|
245
162
|
if (!keyword) return;
|
|
246
163
|
|
|
247
164
|
keyword.falsePositiveCount = (keyword.falsePositiveCount ?? 0) + 1;
|
|
248
165
|
|
|
249
|
-
// D-39-15: Multiplicative weight decay x0.8 on confirmed FP
|
|
250
166
|
keyword.weight = Math.max(MIN_KEYWORD_WEIGHT, keyword.weight * 0.8);
|
|
251
167
|
keyword.lastHitAt = new Date().toISOString();
|
|
252
168
|
|
|
253
|
-
// Update in-store reference
|
|
254
169
|
const keywordIndex = this.store.keywords.findIndex(k => k.term.toLowerCase() === term.toLowerCase());
|
|
255
170
|
if (keywordIndex >= 0) {
|
|
256
171
|
this.store.keywords[keywordIndex] = { ...keyword };
|
|
257
172
|
}
|
|
258
173
|
|
|
259
|
-
// D-39-16: Apply decay BEFORE flush to disk
|
|
260
174
|
this.flush();
|
|
261
175
|
}
|
|
262
176
|
|
|
263
|
-
/**
|
|
264
|
-
* Returns true if optimization is allowed (within daily throttle limit).
|
|
265
|
-
* CORR-08: Max 4 optimizations per day across all triggers.
|
|
266
|
-
*/
|
|
267
|
-
canRunKeywordOptimization(): boolean {
|
|
268
|
-
// D-39-12, D-39-13: Per-workspace throttle, 4 calls/day
|
|
269
|
-
// Uses dedicated keywordOptRunTimestamps array to avoid pollution from regular nocturnal runs (#321)
|
|
270
|
-
const cooldown = checkKeywordOptCooldown(this.stateDir, {
|
|
271
|
-
maxRunsPerWindow: 4,
|
|
272
|
-
quotaWindowMs: 24 * 60 * 60 * 1000,
|
|
273
|
-
});
|
|
274
|
-
return !cooldown.quotaExhausted;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Records that an optimization was performed.
|
|
279
|
-
* Updates lastOptimizedAt for the store and records the run in the
|
|
280
|
-
* keyword-optimization quota (dedicated from regular nocturnal quota).
|
|
281
|
-
* @throws Error if quota recording fails — caller should propagate
|
|
282
|
-
*/
|
|
283
|
-
async recordOptimizationPerformed(): Promise<void> {
|
|
284
|
-
this.store.lastOptimizedAt = new Date().toISOString();
|
|
285
|
-
this.flush();
|
|
286
|
-
await recordKeywordOptRun(this.stateDir);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Adds a new keyword to the store and immediately flushes (D-06, D-07).
|
|
291
|
-
* Throws if the 200-term limit would be exceeded.
|
|
292
|
-
*/
|
|
293
177
|
add(keyword: Omit<CorrectionKeyword, 'addedAt'>): void {
|
|
294
178
|
if (this.store.keywords.length >= MAX_CORRECTION_KEYWORDS) {
|
|
295
179
|
throw new Error('Correction keyword store limit reached (200 terms)');
|
|
@@ -304,11 +188,6 @@ export class CorrectionCueLearner {
|
|
|
304
188
|
this.flush();
|
|
305
189
|
}
|
|
306
190
|
|
|
307
|
-
/**
|
|
308
|
-
* Updates the weight of an existing keyword.
|
|
309
|
-
* Weight is clamped to 0.1-0.9 range.
|
|
310
|
-
* Throws if keyword not found.
|
|
311
|
-
*/
|
|
312
191
|
updateWeight(term: string, weight: number): void {
|
|
313
192
|
const keyword = this.store.keywords.find(
|
|
314
193
|
k => k.term.toLowerCase() === term.toLowerCase()
|
|
@@ -317,7 +196,7 @@ export class CorrectionCueLearner {
|
|
|
317
196
|
throw new Error(`Keyword not found: ${term}`);
|
|
318
197
|
}
|
|
319
198
|
|
|
320
|
-
keyword.weight = Math.max(MIN_KEYWORD_WEIGHT, Math.min(MAX_KEYWORD_WEIGHT, weight));
|
|
199
|
+
keyword.weight = Math.max(MIN_KEYWORD_WEIGHT, Math.min(MAX_KEYWORD_WEIGHT, weight));
|
|
321
200
|
const idx = this.store.keywords.findIndex(
|
|
322
201
|
k => k.term.toLowerCase() === term.toLowerCase()
|
|
323
202
|
);
|
|
@@ -327,10 +206,6 @@ export class CorrectionCueLearner {
|
|
|
327
206
|
this.flush();
|
|
328
207
|
}
|
|
329
208
|
|
|
330
|
-
/**
|
|
331
|
-
* Removes a keyword from the store by term.
|
|
332
|
-
* Throws if keyword not found.
|
|
333
|
-
*/
|
|
334
209
|
remove(term: string): void {
|
|
335
210
|
const idx = this.store.keywords.findIndex(
|
|
336
211
|
k => k.term.toLowerCase() === term.toLowerCase()
|
|
@@ -342,27 +217,18 @@ export class CorrectionCueLearner {
|
|
|
342
217
|
this.flush();
|
|
343
218
|
}
|
|
344
219
|
|
|
345
|
-
/** Returns a reference to the in-memory store. */
|
|
346
220
|
getStore(): CorrectionKeywordStore {
|
|
347
221
|
return this.store;
|
|
348
222
|
}
|
|
349
223
|
|
|
350
|
-
/** Returns the lastOptimizedAt timestamp. */
|
|
351
224
|
getLastOptimizedAt(): string {
|
|
352
225
|
return this.store.lastOptimizedAt;
|
|
353
226
|
}
|
|
354
227
|
|
|
355
|
-
/** Persists the current in-memory store to disk atomically. */
|
|
356
228
|
flush(): void {
|
|
357
229
|
saveCorrectionKeywordStore(this.stateDir, this.store);
|
|
358
230
|
}
|
|
359
231
|
|
|
360
|
-
// ── Singleton factory ───────────────────────────────────────────────────
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Returns the shared CorrectionCueLearner instance for a given stateDir.
|
|
364
|
-
* Re-creates the instance if stateDir changes (e.g. workspace switch).
|
|
365
|
-
*/
|
|
366
232
|
static get(stateDir: string): CorrectionCueLearner {
|
|
367
233
|
if (!_instance || _lastStateDir !== stateDir) {
|
|
368
234
|
_instance = new CorrectionCueLearner(stateDir);
|
|
@@ -1,88 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
weight: number;
|
|
18
|
-
/** How this keyword was introduced */
|
|
19
|
-
source: 'seed' | 'llm' | 'user';
|
|
20
|
-
/** ISO 8601 timestamp of when this keyword was added */
|
|
21
|
-
addedAt: string;
|
|
22
|
-
/** Total times this keyword has matched (default: 0) */
|
|
23
|
-
hitCount?: number;
|
|
24
|
-
/** Confirmed correct matches (default: 0) */
|
|
25
|
-
truePositiveCount?: number;
|
|
26
|
-
/** Confirmed incorrect matches (default: 0) */
|
|
27
|
-
falsePositiveCount?: number;
|
|
28
|
-
/** Last time this keyword matched (ISO timestamp) */
|
|
29
|
-
lastHitAt?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface CorrectionKeywordStore {
|
|
33
|
-
/** All correction keywords */
|
|
34
|
-
keywords: CorrectionKeyword[];
|
|
35
|
-
/** Schema version */
|
|
36
|
-
version: number;
|
|
37
|
-
/** Last time keyword optimization was performed (ISO timestamp) */
|
|
38
|
-
lastOptimizedAt: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// =========================================================================
|
|
42
|
-
// Match Result
|
|
43
|
-
// =========================================================================
|
|
44
|
-
|
|
45
|
-
export interface CorrectionMatchResult {
|
|
46
|
-
/** Whether any keyword matched */
|
|
47
|
-
matched: boolean;
|
|
48
|
-
/** Matched terms (empty array when no match; may be truncated to first N items) */
|
|
49
|
-
matchedTerms: string[];
|
|
50
|
-
/** Weighted score (0-1) based on keyword weight and accuracy */
|
|
51
|
-
score: number;
|
|
52
|
-
/** Confidence in the match result (0-1) */
|
|
53
|
-
confidence: number;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// =========================================================================
|
|
57
|
-
// Seed Keywords (16 terms — sourced from detectCorrectionCue)
|
|
58
|
-
// =========================================================================
|
|
59
|
-
|
|
60
|
-
/** Maximum number of keywords the store may hold (D-06). */
|
|
61
|
-
export const MAX_CORRECTION_KEYWORDS = 200;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Preset seed keywords for correction cue detection.
|
|
65
|
-
* Mirrors the hardcoded list in detectCorrectionCue() exactly (D-08).
|
|
66
|
-
* addedAt is intentionally empty here — it is filled in at runtime by
|
|
67
|
-
* createDefaultStore() when the store is first persisted to disk.
|
|
68
|
-
*/
|
|
69
|
-
export const CORRECTION_SEED_KEYWORDS: CorrectionKeyword[] = [
|
|
70
|
-
// Chinese (8)
|
|
71
|
-
{ term: '不是这个', weight: 0.6, source: 'seed', addedAt: '' },
|
|
72
|
-
{ term: '不对', weight: 0.5, source: 'seed', addedAt: '' },
|
|
73
|
-
{ term: '错了', weight: 0.5, source: 'seed', addedAt: '' },
|
|
74
|
-
{ term: '搞错了', weight: 0.7, source: 'seed', addedAt: '' },
|
|
75
|
-
{ term: '理解错了', weight: 0.7, source: 'seed', addedAt: '' },
|
|
76
|
-
{ term: '你理解错了', weight: 0.8, source: 'seed', addedAt: '' },
|
|
77
|
-
{ term: '重新来', weight: 0.6, source: 'seed', addedAt: '' },
|
|
78
|
-
{ term: '再试一次', weight: 0.4, source: 'seed', addedAt: '' },
|
|
79
|
-
// English (8)
|
|
80
|
-
{ term: 'you are wrong', weight: 0.7, source: 'seed', addedAt: '' },
|
|
81
|
-
{ term: 'wrong file', weight: 0.6, source: 'seed', addedAt: '' },
|
|
82
|
-
{ term: 'not this', weight: 0.4, source: 'seed', addedAt: '' },
|
|
83
|
-
{ term: 'redo', weight: 0.6, source: 'seed', addedAt: '' },
|
|
84
|
-
{ term: 'try again', weight: 0.4, source: 'seed', addedAt: '' },
|
|
85
|
-
{ term: 'again', weight: 0.3, source: 'seed', addedAt: '' },
|
|
86
|
-
{ term: 'please redo', weight: 0.6, source: 'seed', addedAt: '' },
|
|
87
|
-
{ term: 'please try again', weight: 0.5, source: 'seed', addedAt: '' },
|
|
88
|
-
];
|
|
1
|
+
import type {
|
|
2
|
+
CorrectionKeyword,
|
|
3
|
+
CorrectionKeywordStore,
|
|
4
|
+
CorrectionMatchResult,
|
|
5
|
+
} from '@principles/core/runtime-v2';
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
CorrectionKeyword,
|
|
9
|
+
CorrectionKeywordStore,
|
|
10
|
+
CorrectionMatchResult,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
MAX_CORRECTION_KEYWORDS,
|
|
15
|
+
CORRECTION_SEED_KEYWORDS,
|
|
16
|
+
} from '@principles/core/runtime-v2';
|
package/src/core/dictionary.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
-
import {
|
|
4
|
+
import { JsonFileStore } from './file-store.js';
|
|
5
5
|
|
|
6
6
|
export type RuleType = 'regex' | 'exact_match';
|
|
7
7
|
|
|
@@ -64,25 +64,31 @@ const DEFAULT_RULES: Record<string, PainRule> = {
|
|
|
64
64
|
|
|
65
65
|
export class PainDictionary {
|
|
66
66
|
private data: PainDictionaryData = { rules: {} };
|
|
67
|
-
private readonly
|
|
67
|
+
private readonly store: JsonFileStore<PainDictionaryData>;
|
|
68
68
|
private readonly compiledRegex: Map<string, RegExp> = new Map();
|
|
69
69
|
|
|
70
70
|
constructor(private readonly stateDir: string) {
|
|
71
|
-
|
|
71
|
+
const filePath = path.join(stateDir, 'pain_dictionary.json');
|
|
72
|
+
this.store = new JsonFileStore<PainDictionaryData>(filePath, () => ({ rules: { ...DEFAULT_RULES } }));
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
load(): void {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
const filePath = path.join(this.stateDir, 'pain_dictionary.json');
|
|
77
|
+
const fileExisted = fs.existsSync(filePath);
|
|
78
|
+
const loaded = this.store.load();
|
|
79
|
+
// Check if we got real data (has at least one rule from file) or just defaults
|
|
80
|
+
const hasRules = loaded.rules && Object.keys(loaded.rules).length > 0;
|
|
81
|
+
if (hasRules) {
|
|
82
|
+
this.data = loaded;
|
|
82
83
|
} else {
|
|
83
84
|
this.data = { rules: { ...DEFAULT_RULES } };
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
// Only overwrite if file didn't previously exist — preserve corrupt files
|
|
86
|
+
if (!fileExisted) {
|
|
87
|
+
console.log(`[PD:Dictionary] Dictionary not found, creating with default rules`);
|
|
88
|
+
this.flush();
|
|
89
|
+
} else {
|
|
90
|
+
console.warn(`[PD:Dictionary] Dictionary corrupt or empty, preserving file and using defaults`);
|
|
91
|
+
}
|
|
86
92
|
}
|
|
87
93
|
this.compile();
|
|
88
94
|
}
|
|
@@ -152,14 +158,7 @@ export class PainDictionary {
|
|
|
152
158
|
}
|
|
153
159
|
|
|
154
160
|
flush(): void {
|
|
155
|
-
|
|
156
|
-
if (!fs.existsSync(this.stateDir)) {
|
|
157
|
-
fs.mkdirSync(this.stateDir, { recursive: true });
|
|
158
|
-
}
|
|
159
|
-
atomicWriteFileSync(this.filePath, JSON.stringify(this.data, null, 2));
|
|
160
|
-
} catch (e) {
|
|
161
|
-
console.error('[PD] Failed to flush pain_dictionary.json:', e);
|
|
162
|
-
}
|
|
161
|
+
this.store.save(this.data);
|
|
163
162
|
}
|
|
164
163
|
|
|
165
164
|
getStats(): { totalRules: number; totalHits: number } {
|