principles-disciple 1.72.0 → 1.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/openclaw.plugin.json +10 -5
  2. package/package.json +17 -19
  3. package/scripts/acceptance-test.mjs +16 -73
  4. package/scripts/sync-plugin.mjs +382 -77
  5. package/src/commands/archive-impl.ts +2 -1
  6. package/src/commands/capabilities.ts +2 -2
  7. package/src/commands/context.ts +2 -2
  8. package/src/commands/disable-impl.ts +2 -1
  9. package/src/commands/evolution-status.ts +16 -16
  10. package/src/commands/export.ts +12 -67
  11. package/src/commands/pain.ts +91 -1
  12. package/src/commands/principle-rollback.ts +2 -1
  13. package/src/commands/promote-impl.ts +7 -43
  14. package/src/commands/rollback-impl.ts +2 -1
  15. package/src/commands/rollback.ts +2 -1
  16. package/src/commands/samples.ts +2 -1
  17. package/src/commands/thinking-os.ts +2 -1
  18. package/src/config/errors.ts +18 -2
  19. package/src/constants/diagnostician.ts +2 -2
  20. package/src/constants/tools.ts +2 -1
  21. package/src/core/__tests__/focus-history.test.ts +210 -0
  22. package/src/core/config.ts +1 -1
  23. package/src/core/confirm-first-gate.ts +255 -0
  24. package/src/core/correction-cue-learner.ts +2 -136
  25. package/src/core/correction-types.ts +16 -88
  26. package/src/core/dictionary.ts +19 -20
  27. package/src/core/empathy-keyword-matcher.ts +17 -289
  28. package/src/core/empathy-types.ts +18 -229
  29. package/src/core/event-log.ts +38 -132
  30. package/src/core/evolution-reducer.ts +21 -2
  31. package/src/core/evolution-types.ts +76 -464
  32. package/src/core/file-store.ts +80 -0
  33. package/src/core/focus-history.ts +228 -955
  34. package/src/core/local-worker-routing.ts +34 -314
  35. package/src/core/merge-gate-audit.ts +0 -195
  36. package/src/core/pain-diagnostic-gate.ts +154 -0
  37. package/src/core/pain-signal.ts +21 -138
  38. package/src/core/pain.ts +15 -88
  39. package/src/core/pd-task-reconciler.ts +26 -115
  40. package/src/core/pd-task-service.ts +9 -9
  41. package/src/core/pd-task-types.ts +23 -127
  42. package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
  43. package/src/core/principle-compiler/code-validator.ts +15 -42
  44. package/src/core/principle-compiler/compiler.ts +100 -15
  45. package/src/core/principle-compiler/index.ts +5 -2
  46. package/src/core/principle-compiler/template-generator.ts +4 -104
  47. package/src/core/principle-injection.ts +10 -202
  48. package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
  49. package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
  50. package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
  51. package/src/core/principle-tree-ledger-adapter.ts +145 -0
  52. package/src/core/principle-tree-ledger.ts +8 -6
  53. package/src/core/reflection/reflection-context.ts +14 -109
  54. package/src/core/replay-engine.ts +8 -500
  55. package/src/core/rule-host-helpers.ts +5 -35
  56. package/src/core/rule-host-types.ts +10 -82
  57. package/src/core/rule-host.ts +6 -63
  58. package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
  59. package/src/core/session-tracker.ts +87 -101
  60. package/src/core/shadow-observation-registry.ts +19 -48
  61. package/src/core/trajectory.ts +3 -1
  62. package/src/core/workflow-funnel-loader.ts +62 -68
  63. package/src/core/workspace-context.ts +46 -0
  64. package/src/core/workspace-dir-service.ts +1 -1
  65. package/src/core/workspace-dir-validation.ts +18 -9
  66. package/src/hooks/AGENTS.md +1 -1
  67. package/src/hooks/gate-block-helper.ts +46 -44
  68. package/src/hooks/gate.ts +207 -7
  69. package/src/hooks/lifecycle.ts +30 -32
  70. package/src/hooks/llm.ts +60 -32
  71. package/src/hooks/pain.ts +297 -103
  72. package/src/hooks/prompt.ts +459 -439
  73. package/src/hooks/subagent.ts +2 -29
  74. package/src/i18n/commands.ts +2 -10
  75. package/src/index.ts +95 -85
  76. package/src/openclaw-sdk.ts +311 -0
  77. package/src/service/central-database.ts +8 -4
  78. package/src/service/evolution-queue-migration.ts +2 -1
  79. package/src/service/evolution-worker.ts +163 -1786
  80. package/src/service/internalization-trigger-adapter.ts +302 -0
  81. package/src/service/keyword-optimization-service.ts +4 -4
  82. package/src/service/monitoring-query-service.ts +1 -215
  83. package/src/service/queue-io.ts +60 -331
  84. package/src/service/runtime-summary-service.ts +59 -16
  85. package/src/service/subagent-workflow/index.ts +0 -41
  86. package/src/service/subagent-workflow/types.ts +9 -120
  87. package/src/service/subagent-workflow/workflow-store.ts +2 -119
  88. package/src/service/workflow-watchdog.ts +0 -43
  89. package/src/types/event-payload.ts +16 -74
  90. package/src/types/event-types.ts +39 -547
  91. package/src/types/hygiene-types.ts +7 -30
  92. package/src/types/principle-tree-schema.ts +20 -222
  93. package/src/types/queue.ts +15 -70
  94. package/src/types/runtime-summary.ts +5 -49
  95. package/src/utils/io.ts +10 -0
  96. package/src/utils/retry.ts +1 -1
  97. package/src/utils/shadow-fingerprint.ts +2 -2
  98. package/src/utils/workspace-resolver.ts +50 -0
  99. package/templates/langs/en/core/AGENTS.md +2 -2
  100. package/templates/langs/en/core/BOOT.md +1 -1
  101. package/templates/langs/en/core/HEARTBEAT.md +2 -2
  102. package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  103. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  104. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  105. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  106. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  107. package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  108. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  109. package/templates/langs/en/skills/evolve-task/SKILL.md +1 -1
  110. package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
  111. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
  112. package/templates/langs/en/skills/pd-mentor/SKILL.md +1 -1
  113. package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
  114. package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
  115. package/templates/langs/zh/core/AGENTS.md +2 -2
  116. package/templates/langs/zh/core/BOOT.md +1 -1
  117. package/templates/langs/zh/core/HEARTBEAT.md +2 -2
  118. package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  119. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  120. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  121. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
  122. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  123. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  124. package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  125. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  126. package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
  127. package/templates/langs/zh/skills/evolve-task/SKILL.md +2 -2
  128. package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
  129. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
  130. package/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -1
  131. package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
  132. package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
  133. package/tests/build-artifacts.test.ts +1 -3
  134. package/tests/commands/evolution-status.test.ts +0 -118
  135. package/tests/core/bootstrap-rules.test.ts +1 -1
  136. package/tests/core/config.test.ts +1 -1
  137. package/tests/core/event-log.test.ts +35 -0
  138. package/tests/core/evolution-engine.test.ts +610 -0
  139. package/tests/core/file-store.test.ts +102 -0
  140. package/tests/core/focus-history.test.ts +203 -11
  141. package/tests/core/merge-gate-audit.test.ts +2 -169
  142. package/tests/core/model-deployment-registry.test.ts +7 -1
  143. package/tests/core/model-training-registry.test.ts +19 -0
  144. package/tests/core/observability.test.ts +0 -1
  145. package/tests/core/pain-diagnostic-gate.test.ts +498 -0
  146. package/tests/core/pain.test.ts +0 -1
  147. package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
  148. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
  149. package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
  150. package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
  151. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
  152. package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
  153. package/tests/core/reflection-context.test.ts +0 -14
  154. package/tests/core/replay-engine.test.ts +127 -215
  155. package/tests/core/rule-host-helpers.test.ts +2 -2
  156. package/tests/core/rule-implementation-runtime.test.ts +0 -27
  157. package/tests/core/workflow-funnel-loader.test.ts +162 -0
  158. package/tests/core/workspace-dir-validation.test.ts +8 -1
  159. package/tests/core-anti-growth.test.ts +192 -0
  160. package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
  161. package/tests/hooks/confirm-first-gate.test.ts +333 -0
  162. package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
  163. package/tests/hooks/gate-auto-correct.test.ts +665 -0
  164. package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
  165. package/tests/hooks/pain.test.ts +269 -12
  166. package/tests/hooks/prompt-characterization.test.ts +500 -0
  167. package/tests/hooks/prompt-size-guard.test.ts +32 -17
  168. package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
  169. package/tests/index.test.ts +94 -1
  170. package/tests/integration/auto-entry-gate.test.ts +248 -0
  171. package/tests/integration/internalization-trigger-guard.test.ts +69 -0
  172. package/tests/integration/m8-legacy-paths.test.ts +63 -0
  173. package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
  174. package/tests/plugin-config-resolution-cutover.test.ts +359 -0
  175. package/tests/runtime-v2-discovery-guard.test.ts +154 -0
  176. package/tests/service/central-database.test.ts +457 -0
  177. package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
  178. package/tests/service/evolution-worker.timeout.test.ts +11 -129
  179. package/tests/service/internalization-trigger-adapter.test.ts +251 -0
  180. package/tests/service/monitoring-query-service.test.ts +1 -47
  181. package/tests/service/queue-io.test.ts +1 -62
  182. package/tests/service/runtime-summary-service.test.ts +3 -1
  183. package/tests/service/workflow-watchdog.test.ts +0 -91
  184. package/tests/utils/file-lock.test.ts +5 -3
  185. package/tests/utils/session-key.test.ts +52 -0
  186. package/tests/utils/subagent-probe.test.ts +48 -1
  187. package/vitest.config.ts +4 -11
  188. package/.planning/codebase/ARCHITECTURE.md +0 -157
  189. package/.planning/codebase/CONCERNS.md +0 -145
  190. package/.planning/codebase/CONVENTIONS.md +0 -148
  191. package/.planning/codebase/INTEGRATIONS.md +0 -81
  192. package/.planning/codebase/STACK.md +0 -87
  193. package/.planning/codebase/STRUCTURE.md +0 -193
  194. package/.planning/codebase/TESTING.md +0 -243
  195. package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
  196. package/docs/COMMAND_REFERENCE.md +0 -76
  197. package/docs/COMMAND_REFERENCE_EN.md +0 -79
  198. package/scripts/build-web.mjs +0 -46
  199. package/scripts/diagnose-nocturnal.mjs +0 -537
  200. package/scripts/seed-nocturnal-scenarios.mjs +0 -384
  201. package/src/commands/nocturnal-review.ts +0 -322
  202. package/src/commands/nocturnal-rollout.ts +0 -790
  203. package/src/commands/nocturnal-train.ts +0 -986
  204. package/src/commands/pd-reflect.ts +0 -88
  205. package/src/core/adaptive-thresholds.ts +0 -478
  206. package/src/core/diagnostician-task-store.ts +0 -192
  207. package/src/core/nocturnal-arbiter.ts +0 -715
  208. package/src/core/nocturnal-artifact-lineage.ts +0 -116
  209. package/src/core/nocturnal-artificer.ts +0 -257
  210. package/src/core/nocturnal-candidate-scoring.ts +0 -530
  211. package/src/core/nocturnal-compliance.ts +0 -1146
  212. package/src/core/nocturnal-dataset.ts +0 -763
  213. package/src/core/nocturnal-executability.ts +0 -428
  214. package/src/core/nocturnal-export.ts +0 -499
  215. package/src/core/nocturnal-paths.ts +0 -240
  216. package/src/core/nocturnal-reasoning-deriver.ts +0 -343
  217. package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
  218. package/src/core/nocturnal-snapshot-contract.ts +0 -99
  219. package/src/core/nocturnal-trajectory-extractor.ts +0 -512
  220. package/src/core/nocturnal-trinity-types.ts +0 -218
  221. package/src/core/nocturnal-trinity.ts +0 -2680
  222. package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
  223. package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
  224. package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
  225. package/src/http/principles-console-route.ts +0 -709
  226. package/src/service/central-health-service.ts +0 -49
  227. package/src/service/central-overview-service.ts +0 -138
  228. package/src/service/control-ui-query-service.ts +0 -900
  229. package/src/service/cooldown-strategy.ts +0 -97
  230. package/src/service/evolution-pain-context.ts +0 -79
  231. package/src/service/evolution-query-service.ts +0 -407
  232. package/src/service/health-query-service.ts +0 -1038
  233. package/src/service/nocturnal-config.ts +0 -214
  234. package/src/service/nocturnal-runtime.ts +0 -734
  235. package/src/service/nocturnal-service.ts +0 -1605
  236. package/src/service/nocturnal-target-selector.ts +0 -545
  237. package/src/service/sleep-cycle.ts +0 -157
  238. package/src/service/startup-reconciler.ts +0 -112
  239. package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
  240. package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
  241. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
  242. package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
  243. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
  244. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
  245. package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
  246. package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
  247. package/src/tools/write-pain-flag.ts +0 -215
  248. package/tests/commands/nocturnal-review.test.ts +0 -448
  249. package/tests/commands/nocturnal-train.test.ts +0 -97
  250. package/tests/commands/pd-reflect.test.ts +0 -49
  251. package/tests/core/adaptive-thresholds.test.ts +0 -261
  252. package/tests/core/nocturnal-arbiter.test.ts +0 -559
  253. package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
  254. package/tests/core/nocturnal-artificer.test.ts +0 -241
  255. package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
  256. package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
  257. package/tests/core/nocturnal-compliance.test.ts +0 -646
  258. package/tests/core/nocturnal-dataset.test.ts +0 -892
  259. package/tests/core/nocturnal-e2e.test.ts +0 -234
  260. package/tests/core/nocturnal-executability.test.ts +0 -357
  261. package/tests/core/nocturnal-export.test.ts +0 -517
  262. package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
  263. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
  264. package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
  265. package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
  266. package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
  267. package/tests/core/nocturnal-trinity.test.ts +0 -2053
  268. package/tests/core/pain-auto-repair.test.ts +0 -96
  269. package/tests/core/pain-integration.test.ts +0 -510
  270. package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
  271. package/tests/http/principles-console-route.test.ts +0 -162
  272. package/tests/integration/chaos-resilience.test.ts +0 -348
  273. package/tests/integration/empathy-workflow-integration.test.ts +0 -626
  274. package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
  275. package/tests/service/control-ui-query-service.test.ts +0 -121
  276. package/tests/service/cooldown-strategy.test.ts +0 -164
  277. package/tests/service/data-endpoints-regression.test.ts +0 -834
  278. package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
  279. package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
  280. package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
  281. package/tests/service/nocturnal-runtime.test.ts +0 -473
  282. package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
  283. package/tests/service/nocturnal-target-selector.test.ts +0 -615
  284. package/tests/service/startup-reconciler.test.ts +0 -148
  285. package/tests/tools/write-pain-flag.test.ts +0 -358
  286. package/ui/src/App.tsx +0 -45
  287. package/ui/src/api.ts +0 -220
  288. package/ui/src/charts.tsx +0 -955
  289. package/ui/src/components/ErrorState.tsx +0 -6
  290. package/ui/src/components/Loading.tsx +0 -13
  291. package/ui/src/components/ProtectedRoute.tsx +0 -12
  292. package/ui/src/components/Shell.tsx +0 -91
  293. package/ui/src/components/WorkspaceConfig.tsx +0 -178
  294. package/ui/src/components/index.ts +0 -5
  295. package/ui/src/context/auth.tsx +0 -80
  296. package/ui/src/context/theme.tsx +0 -66
  297. package/ui/src/hooks/useAutoRefresh.ts +0 -39
  298. package/ui/src/i18n/ui.ts +0 -473
  299. package/ui/src/main.tsx +0 -16
  300. package/ui/src/pages/EvolutionPage.tsx +0 -333
  301. package/ui/src/pages/FeedbackPage.tsx +0 -138
  302. package/ui/src/pages/GateMonitorPage.tsx +0 -136
  303. package/ui/src/pages/LoginPage.tsx +0 -89
  304. package/ui/src/pages/OverviewPage.tsx +0 -599
  305. package/ui/src/pages/SamplesPage.tsx +0 -174
  306. package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
  307. package/ui/src/styles.css +0 -2020
  308. package/ui/src/types.ts +0 -384
  309. package/ui/src/utils/format.ts +0 -15
@@ -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
- // Parse failure — fall through to default
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)); // Clamp to MIN-MAX_KEYWORD_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
- * Correction Cue Keyword Types
3
- *
4
- * Types for the dynamic correction cue detection system.
5
- * Replaces the previous hardcoded cue list in detectCorrectionCue()
6
- * with a persistent, learnable keyword store.
7
- */
8
-
9
- // =========================================================================
10
- // Keyword Store
11
- // =========================================================================
12
-
13
- export interface CorrectionKeyword {
14
- /** The keyword term to match against normalized user text */
15
- term: string;
16
- /** Contribution weight (0-1) */
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';
@@ -1,7 +1,7 @@
1
1
 
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
- import { atomicWriteFileSync } from '../utils/io.js';
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 filePath: string;
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
- this.filePath = path.join(stateDir, 'pain_dictionary.json');
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
- if (fs.existsSync(this.filePath)) {
76
- try {
77
- this.data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
78
- } catch {
79
- console.error('[PD] Failed to parse pain_dictionary.json, using defaults.');
80
- this.data = { rules: { ...DEFAULT_RULES } };
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
- console.log(`[PD:Dictionary] Dictionary not found at ${this.filePath}, creating with default rules`);
85
- this.flush();
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
- try {
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 } {