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.
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 +469 -339
  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 +115 -18
  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 +329 -0
  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 +184 -3
  183. package/tests/service/workflow-watchdog.test.ts +0 -91
  184. package/tests/utils/file-lock.test.ts +5 -3
  185. package/tests/utils/session-key.test.ts +52 -0
  186. package/tests/utils/subagent-probe.test.ts +48 -1
  187. package/vitest.config.ts +4 -11
  188. package/.planning/codebase/ARCHITECTURE.md +0 -157
  189. package/.planning/codebase/CONCERNS.md +0 -145
  190. package/.planning/codebase/CONVENTIONS.md +0 -148
  191. package/.planning/codebase/INTEGRATIONS.md +0 -81
  192. package/.planning/codebase/STACK.md +0 -87
  193. package/.planning/codebase/STRUCTURE.md +0 -193
  194. package/.planning/codebase/TESTING.md +0 -243
  195. package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
  196. package/docs/COMMAND_REFERENCE.md +0 -76
  197. package/docs/COMMAND_REFERENCE_EN.md +0 -79
  198. package/scripts/build-web.mjs +0 -46
  199. package/scripts/diagnose-nocturnal.mjs +0 -537
  200. package/scripts/seed-nocturnal-scenarios.mjs +0 -384
  201. package/src/commands/nocturnal-review.ts +0 -322
  202. package/src/commands/nocturnal-rollout.ts +0 -790
  203. package/src/commands/nocturnal-train.ts +0 -986
  204. package/src/commands/pd-reflect.ts +0 -88
  205. package/src/core/adaptive-thresholds.ts +0 -478
  206. package/src/core/diagnostician-task-store.ts +0 -192
  207. package/src/core/nocturnal-arbiter.ts +0 -715
  208. package/src/core/nocturnal-artifact-lineage.ts +0 -116
  209. package/src/core/nocturnal-artificer.ts +0 -257
  210. package/src/core/nocturnal-candidate-scoring.ts +0 -530
  211. package/src/core/nocturnal-compliance.ts +0 -1146
  212. package/src/core/nocturnal-dataset.ts +0 -763
  213. package/src/core/nocturnal-executability.ts +0 -428
  214. package/src/core/nocturnal-export.ts +0 -499
  215. package/src/core/nocturnal-paths.ts +0 -240
  216. package/src/core/nocturnal-reasoning-deriver.ts +0 -343
  217. package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
  218. package/src/core/nocturnal-snapshot-contract.ts +0 -99
  219. package/src/core/nocturnal-trajectory-extractor.ts +0 -512
  220. package/src/core/nocturnal-trinity-types.ts +0 -218
  221. package/src/core/nocturnal-trinity.ts +0 -2680
  222. package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
  223. package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
  224. package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
  225. package/src/http/principles-console-route.ts +0 -709
  226. package/src/service/central-health-service.ts +0 -49
  227. package/src/service/central-overview-service.ts +0 -138
  228. package/src/service/control-ui-query-service.ts +0 -900
  229. package/src/service/cooldown-strategy.ts +0 -97
  230. package/src/service/evolution-pain-context.ts +0 -79
  231. package/src/service/evolution-query-service.ts +0 -407
  232. package/src/service/health-query-service.ts +0 -1038
  233. package/src/service/nocturnal-config.ts +0 -214
  234. package/src/service/nocturnal-runtime.ts +0 -734
  235. package/src/service/nocturnal-service.ts +0 -1605
  236. package/src/service/nocturnal-target-selector.ts +0 -545
  237. package/src/service/sleep-cycle.ts +0 -157
  238. package/src/service/startup-reconciler.ts +0 -112
  239. package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
  240. package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
  241. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
  242. package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
  243. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
  244. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
  245. package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
  246. package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
  247. package/src/tools/write-pain-flag.ts +0 -215
  248. package/tests/commands/nocturnal-review.test.ts +0 -448
  249. package/tests/commands/nocturnal-train.test.ts +0 -97
  250. package/tests/commands/pd-reflect.test.ts +0 -49
  251. package/tests/core/adaptive-thresholds.test.ts +0 -261
  252. package/tests/core/nocturnal-arbiter.test.ts +0 -559
  253. package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
  254. package/tests/core/nocturnal-artificer.test.ts +0 -241
  255. package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
  256. package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
  257. package/tests/core/nocturnal-compliance.test.ts +0 -646
  258. package/tests/core/nocturnal-dataset.test.ts +0 -892
  259. package/tests/core/nocturnal-e2e.test.ts +0 -234
  260. package/tests/core/nocturnal-executability.test.ts +0 -357
  261. package/tests/core/nocturnal-export.test.ts +0 -517
  262. package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
  263. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
  264. package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
  265. package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
  266. package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
  267. package/tests/core/nocturnal-trinity.test.ts +0 -2053
  268. package/tests/core/pain-auto-repair.test.ts +0 -96
  269. package/tests/core/pain-integration.test.ts +0 -510
  270. package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
  271. package/tests/http/principles-console-route.test.ts +0 -162
  272. package/tests/integration/chaos-resilience.test.ts +0 -348
  273. package/tests/integration/empathy-workflow-integration.test.ts +0 -626
  274. package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
  275. package/tests/service/control-ui-query-service.test.ts +0 -121
  276. package/tests/service/cooldown-strategy.test.ts +0 -164
  277. package/tests/service/data-endpoints-regression.test.ts +0 -834
  278. package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
  279. package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
  280. package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
  281. package/tests/service/nocturnal-runtime.test.ts +0 -473
  282. package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
  283. package/tests/service/nocturnal-target-selector.test.ts +0 -615
  284. package/tests/service/startup-reconciler.test.ts +0 -148
  285. package/tests/tools/write-pain-flag.test.ts +0 -358
  286. package/ui/src/App.tsx +0 -45
  287. package/ui/src/api.ts +0 -220
  288. package/ui/src/charts.tsx +0 -955
  289. package/ui/src/components/ErrorState.tsx +0 -6
  290. package/ui/src/components/Loading.tsx +0 -13
  291. package/ui/src/components/ProtectedRoute.tsx +0 -12
  292. package/ui/src/components/Shell.tsx +0 -91
  293. package/ui/src/components/WorkspaceConfig.tsx +0 -178
  294. package/ui/src/components/index.ts +0 -5
  295. package/ui/src/context/auth.tsx +0 -80
  296. package/ui/src/context/theme.tsx +0 -66
  297. package/ui/src/hooks/useAutoRefresh.ts +0 -39
  298. package/ui/src/i18n/ui.ts +0 -473
  299. package/ui/src/main.tsx +0 -16
  300. package/ui/src/pages/EvolutionPage.tsx +0 -333
  301. package/ui/src/pages/FeedbackPage.tsx +0 -138
  302. package/ui/src/pages/GateMonitorPage.tsx +0 -136
  303. package/ui/src/pages/LoginPage.tsx +0 -89
  304. package/ui/src/pages/OverviewPage.tsx +0 -599
  305. package/ui/src/pages/SamplesPage.tsx +0 -174
  306. package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
  307. package/ui/src/styles.css +0 -2020
  308. package/ui/src/types.ts +0 -384
  309. package/ui/src/utils/format.ts +0 -15
@@ -1,101 +1,43 @@
1
- /**
2
- * Empathy Keyword Matcher
3
- *
4
- * Fast keyword-based empathy detection that replaces the previous
5
- * LLM subagent-per-turn approach.
6
- *
7
- * Flow:
8
- * User message → keyword matching → weighted score → GFI penalty
9
- *
10
- * The keyword store is periodically optimized by a subagent that analyzes
11
- * recent conversations and updates keyword weights, discovers new terms,
12
- * and removes false positives.
13
- */
14
-
15
1
  import * as fs from 'fs';
16
2
  import * as path from 'path';
17
3
  import { atomicWriteFileSync } from '../utils/io.js';
18
- import type {
4
+ import { createDefaultKeywordStore } from '@principles/core/prompt-builder';
5
+ import type { EmpathyKeywordStore } from '@principles/core/prompt-builder';
6
+
7
+ export {
8
+ matchEmpathyKeywords,
9
+ createDefaultKeywordStore,
10
+ applyKeywordUpdates,
11
+ shouldTriggerOptimization,
12
+ getKeywordStoreSummary,
13
+ } from '@principles/core/prompt-builder';
14
+
15
+ export type {
19
16
  EmpathyKeywordStore,
20
17
  EmpathyKeywordEntry,
21
18
  EmpathyKeywordStats,
22
19
  EmpathyMatchResult,
23
- EmpathyKeywordConfig} from './empathy-types.js';
24
- import {
25
- EMPATHY_SEED_KEYWORDS,
26
- DEFAULT_EMPATHY_KEYWORD_CONFIG,
27
- scoreToSeverity,
28
- } from './empathy-types.js';
20
+ EmpathyKeywordConfig,
21
+ } from '@principles/core/prompt-builder';
29
22
 
30
23
  const KEYWORD_STORE_FILE = 'empathy_keywords.json';
31
24
 
32
- // =========================================================================
33
- // Store Management
34
- // =========================================================================
35
-
36
- /**
37
- * Creates a default keyword store from seed keywords.
38
- * Supports both Chinese and English keywords.
39
- */
40
- export function createDefaultKeywordStore(language: 'zh' | 'en' = 'zh'): EmpathyKeywordStore {
41
- const now = new Date().toISOString();
42
- const terms: Record<string, EmpathyKeywordEntry> = {};
43
-
44
- // Include all seed keywords (both zh and en)
45
- for (const seed of EMPATHY_SEED_KEYWORDS) {
46
- // For Chinese language, include all keywords
47
- // For English language, include only English keywords
48
- const isChinese = /[\u4e00-\u9fa5]/.test(seed.term);
49
- if (language === 'zh' || !isChinese) {
50
- terms[seed.term] = {
51
- weight: seed.weight,
52
- source: 'seed',
53
- hitCount: 0,
54
- falsePositiveRate: seed.initialFalsePositiveRate ?? 0.15, // Differentiated FPR (Finding #6)
55
- };
56
- }
57
- }
58
-
59
- const stats: EmpathyKeywordStats = {
60
- totalHits: 0,
61
- totalFalsePositives: 0,
62
- optimizationCount: 0,
63
- };
64
-
65
- return {
66
- version: 1,
67
- lastUpdated: now,
68
- lastOptimizedAt: now,
69
- terms,
70
- stats,
71
- };
72
- }
73
-
74
- /**
75
- * Loads the keyword store from disk, or creates a default one if not found.
76
- * Respects the configured language setting.
77
- */
78
25
  export function loadKeywordStore(stateDir: string, language?: 'zh' | 'en'): EmpathyKeywordStore {
79
26
  const filePath = path.join(stateDir, KEYWORD_STORE_FILE);
80
-
27
+
81
28
  try {
82
29
  if (!fs.existsSync(filePath)) {
83
30
  const store = createDefaultKeywordStore(language);
84
-
85
-
86
31
  saveKeywordStore(stateDir, store);
87
32
  return store;
88
33
  }
89
34
 
90
35
  const raw = fs.readFileSync(filePath, 'utf8');
91
36
  const parsed = JSON.parse(raw);
92
-
93
- // Validate structure
37
+
94
38
  if (!parsed.terms || !parsed.stats || !parsed.version) {
95
39
  console.warn('[PD:Empathy] Invalid keyword store format, creating default');
96
40
  const store = createDefaultKeywordStore(language);
97
-
98
-
99
41
  saveKeywordStore(stateDir, store);
100
42
  return store;
101
43
  }
@@ -104,21 +46,15 @@ export function loadKeywordStore(stateDir: string, language?: 'zh' | 'en'): Empa
104
46
  } catch (e) {
105
47
  console.warn(`[PD:Empathy] Failed to load keyword store: ${e}`);
106
48
  const store = createDefaultKeywordStore(language);
107
-
108
-
109
49
  saveKeywordStore(stateDir, store);
110
50
  return store;
111
51
  }
112
52
  }
113
53
 
114
- /**
115
- * Saves the keyword store to disk.
116
- */
117
-
118
54
  export function saveKeywordStore(stateDir: string, store: EmpathyKeywordStore): void {
119
55
  const filePath = path.join(stateDir, KEYWORD_STORE_FILE);
120
56
  const dir = path.dirname(filePath);
121
-
57
+
122
58
  if (!fs.existsSync(dir)) {
123
59
  fs.mkdirSync(dir, { recursive: true });
124
60
  }
@@ -126,211 +62,3 @@ export function saveKeywordStore(stateDir: string, store: EmpathyKeywordStore):
126
62
  store.lastUpdated = new Date().toISOString();
127
63
  atomicWriteFileSync(filePath, JSON.stringify(store, null, 2));
128
64
  }
129
-
130
- // =========================================================================
131
- // Keyword Matching
132
- // =========================================================================
133
-
134
- /**
135
- * Matches text against the keyword store and returns a structured result.
136
- *
137
- * This is the core function that replaces the previous LLM-based empathy detection.
138
- * It runs in < 1ms for typical keyword stores (50-200 terms).
139
- */
140
- export function matchEmpathyKeywords(
141
- text: string,
142
- store: EmpathyKeywordStore,
143
- config: EmpathyKeywordConfig = DEFAULT_EMPATHY_KEYWORD_CONFIG,
144
- ): EmpathyMatchResult {
145
- if (!text || typeof text !== 'string' || text.trim().length === 0) {
146
- return {
147
- matched: false,
148
- score: 0,
149
- matchedTerms: [],
150
- severity: 'mild',
151
- confidence: 0,
152
- };
153
- }
154
-
155
- const lowerText = text.toLowerCase();
156
- let totalScore = 0;
157
- const matchedTerms: string[] = [];
158
-
159
- for (const [term, entry] of Object.entries(store.terms)) {
160
- if (lowerText.includes(term.toLowerCase())) {
161
- // Weight adjusted by false positive rate
162
- const adjustedWeight = entry.weight * (1 - entry.falsePositiveRate);
163
- totalScore += adjustedWeight;
164
- matchedTerms.push(term);
165
-
166
- // Update hit stats
167
- entry.hitCount++;
168
- entry.lastHitAt = new Date().toISOString();
169
- }
170
- }
171
-
172
- // Cap score at 1.0
173
- const cappedScore = Math.min(1, totalScore);
174
-
175
- // Only consider matched if score exceeds threshold
176
- const isMatched = cappedScore >= config.matchThreshold && matchedTerms.length > 0;
177
-
178
- // Limit matched terms for performance
179
- const limitedTerms = matchedTerms.slice(0, config.maxTermsPerMessage);
180
-
181
- // Calculate confidence based on:
182
- // - Number of matched terms (more terms = higher confidence)
183
- // - Score relative to threshold (higher score = higher confidence)
184
- const termConfidence = Math.min(1, limitedTerms.length / 3);
185
- const scoreConfidence = Math.min(1, cappedScore / 0.8);
186
- const confidence = Math.max(termConfidence, scoreConfidence);
187
-
188
- const result: EmpathyMatchResult = {
189
- matched: isMatched,
190
- score: cappedScore,
191
- matchedTerms: limitedTerms,
192
- severity: scoreToSeverity(cappedScore),
193
- confidence,
194
- };
195
-
196
- // Update store stats
197
- if (isMatched) {
198
- store.stats.totalHits += limitedTerms.length;
199
- }
200
-
201
- return result;
202
- }
203
-
204
- // =========================================================================
205
- // Keyword Store Updates
206
- // =========================================================================
207
-
208
- /**
209
- * Applies keyword updates from subagent optimization.
210
- *
211
- * This is called when the empathy optimizer subagent completes its analysis
212
- * and returns suggested updates to the keyword store.
213
- */
214
-
215
- export function applyKeywordUpdates(
216
- store: EmpathyKeywordStore,
217
- updates: Record<string, {
218
- action: 'add' | 'update' | 'remove';
219
- weight?: number;
220
- falsePositiveRate?: number;
221
- examples?: string[];
222
- reasoning?: string;
223
- }>,
224
- ): { added: number; updated: number; removed: number } {
225
- let added = 0;
226
- let updated = 0;
227
- let removed = 0;
228
- const now = new Date().toISOString();
229
-
230
- for (const [term, update] of Object.entries(updates)) {
231
- switch (update.action) {
232
- case 'add':
233
- if (!store.terms[term]) {
234
- store.terms[term] = {
235
- weight: update.weight ?? 0.5,
236
- source: 'llm_discovered',
237
- hitCount: 0,
238
- falsePositiveRate: update.falsePositiveRate ?? 0.2,
239
- examples: update.examples,
240
- discoveredAt: now,
241
- };
242
- added++;
243
- }
244
- break;
245
-
246
- case 'update':
247
- if (store.terms[term]) {
248
- if (update.weight !== undefined) {
249
- store.terms[term].weight = update.weight;
250
- }
251
- if (update.falsePositiveRate !== undefined) {
252
- store.terms[term].falsePositiveRate = update.falsePositiveRate;
253
- }
254
- if (update.examples) {
255
- store.terms[term].examples = update.examples;
256
- }
257
- updated++;
258
- }
259
- break;
260
-
261
- case 'remove':
262
- if (store.terms[term]) {
263
- delete store.terms[term];
264
- removed++;
265
- }
266
- break;
267
- }
268
- }
269
-
270
- store.lastOptimizedAt = now;
271
- store.stats.optimizationCount++;
272
-
273
- return { added, updated, removed };
274
- }
275
-
276
- // =========================================================================
277
- // Optimization Trigger
278
- // =========================================================================
279
-
280
- /**
281
- * Checks if it's time to trigger subagent optimization.
282
- *
283
- * Returns true if either:
284
- * - The number of turns since last optimization exceeds the interval
285
- * - The time since last optimization exceeds the interval
286
- */
287
- export function shouldTriggerOptimization(
288
- store: EmpathyKeywordStore,
289
- turnsSinceLastOptimization: number,
290
- config: EmpathyKeywordConfig = DEFAULT_EMPATHY_KEYWORD_CONFIG,
291
- ): boolean {
292
- const turnsExceeded = turnsSinceLastOptimization >= config.optimizationIntervalTurns;
293
-
294
- const lastOpt = new Date(store.lastOptimizedAt).getTime();
295
- const now = Date.now();
296
- const timeExceeded = (now - lastOpt) >= config.optimizationIntervalMs;
297
-
298
- return turnsExceeded || timeExceeded;
299
- }
300
-
301
- // =========================================================================
302
- // Store Inspection
303
- // =========================================================================
304
-
305
- /**
306
- * Returns a summary of the keyword store for debugging/monitoring.
307
- */
308
- export function getKeywordStoreSummary(store: EmpathyKeywordStore): {
309
- totalTerms: number;
310
- seedTerms: number;
311
- discoveredTerms: number;
312
- topHitTerms: { term: string; hitCount: number; weight: number }[];
313
- highFalsePositiveTerms: { term: string; falsePositiveRate: number }[];
314
- } {
315
- const terms = Object.entries(store.terms);
316
- const seedTerms = terms.filter(([, e]) => e.source === 'seed');
317
- const discoveredTerms = terms.filter(([, e]) => e.source === 'llm_discovered');
318
-
319
- const topHitTerms = terms
320
- .map(([term, entry]) => ({ term, hitCount: entry.hitCount, weight: entry.weight }))
321
- .sort((a, b) => b.hitCount - a.hitCount)
322
- .slice(0, 10);
323
-
324
- const highFalsePositiveTerms = terms
325
- .filter(([, e]) => e.falsePositiveRate > 0.3)
326
- .map(([term, entry]) => ({ term, falsePositiveRate: entry.falsePositiveRate }))
327
- .sort((a, b) => b.falsePositiveRate - a.falsePositiveRate);
328
-
329
- return {
330
- totalTerms: terms.length,
331
- seedTerms: seedTerms.length,
332
- discoveredTerms: discoveredTerms.length,
333
- topHitTerms,
334
- highFalsePositiveTerms,
335
- };
336
- }
@@ -1,229 +1,18 @@
1
- /**
2
- * Empathy Keyword Matching Types
3
- *
4
- * Types for the dynamic keyword-based empathy detection system.
5
- * Replaces the previous LLM subagent-per-turn approach with fast keyword
6
- * matching + periodic subagent optimization of the keyword store.
7
- */
8
-
9
- // =========================================================================
10
- // Keyword Store
11
- // =========================================================================
12
-
13
- export interface EmpathyKeywordStore {
14
- version: number;
15
- lastUpdated: string;
16
- lastOptimizedAt: string;
17
- terms: Record<string, EmpathyKeywordEntry>;
18
- stats: EmpathyKeywordStats;
19
- }
20
-
21
- export interface EmpathyKeywordEntry {
22
- /** 0-1, contribution to GFI when matched */
23
- weight: number;
24
-
25
- /** How this keyword was discovered */
26
- source: 'seed' | 'llm_discovered' | 'user_reported';
27
-
28
- /** Total times this keyword has matched */
29
- hitCount: number;
30
-
31
- /** Last time this keyword matched */
32
- lastHitAt?: string;
33
-
34
- /** 0-1, false positive rate calculated from subagent validation */
35
- falsePositiveRate: number;
36
-
37
- /** Example contexts where this keyword appeared */
38
- examples?: string[];
39
-
40
- /** When LLM first discovered this keyword */
41
- discoveredAt?: string;
42
- }
43
-
44
- export interface EmpathyKeywordStats {
45
- totalHits: number;
46
- totalFalsePositives: number;
47
- optimizationCount: number;
48
- }
49
-
50
- // =========================================================================
51
- // Match Result
52
- // =========================================================================
53
-
54
- export interface EmpathyMatchResult {
55
- /** Whether any keywords matched */
56
- matched: boolean;
57
-
58
- /** Weighted total score (0-1) */
59
- score: number;
60
-
61
- /** List of matched keyword terms */
62
- matchedTerms: string[];
63
-
64
- /** Derived severity level */
65
- severity: 'mild' | 'moderate' | 'severe';
66
-
67
- /** Confidence in the result (0-1) */
68
- confidence: number;
69
- }
70
-
71
- // =========================================================================
72
- // Keyword Update (from subagent optimization)
73
- // =========================================================================
74
-
75
- export interface EmpathyKeywordUpdate {
76
- action: 'add' | 'update' | 'remove';
77
- weight?: number;
78
- falsePositiveRate?: number;
79
- examples?: string[];
80
- reasoning?: string;
81
- }
82
-
83
- export interface EmpathyOptimizationResult {
84
- updates: Record<string, EmpathyKeywordUpdate>;
85
- reasoning: string;
86
- analyzedTurns: number;
87
- newPatternsDiscovered: number;
88
- }
89
-
90
- // =========================================================================
91
- // Seed Keywords (preset list)
92
- // =========================================================================
93
-
94
- export interface SeedKeywordEntry {
95
- term: string;
96
- weight: number;
97
- category: 'negation' | 'anger' | 'disappointment' | 'escalation';
98
- /** Initial false positive rate — higher for generic words, lower for specific anger signals */
99
- initialFalsePositiveRate?: number;
100
- }
101
-
102
- /**
103
- * Preset seed keywords for empathy detection.
104
- * These are the initial keywords before the LLM starts discovering new ones.
105
- */
106
- export const EMPATHY_SEED_KEYWORDS: SeedKeywordEntry[] = [
107
- // 否定词 (Negation) — generic, higher FPR
108
- { term: '不对', weight: 0.5, category: 'negation', initialFalsePositiveRate: 0.3 },
109
- { term: '错了', weight: 0.5, category: 'negation', initialFalsePositiveRate: 0.3 },
110
- { term: '搞错了', weight: 0.5, category: 'negation', initialFalsePositiveRate: 0.25 },
111
- { term: '不行', weight: 0.4, category: 'negation', initialFalsePositiveRate: 0.35 },
112
- { term: '没用', weight: 0.4, category: 'negation', initialFalsePositiveRate: 0.3 },
113
- { term: '重做', weight: 0.6, category: 'negation', initialFalsePositiveRate: 0.15 },
114
- { term: '重写', weight: 0.6, category: 'negation', initialFalsePositiveRate: 0.15 },
115
- { term: 'not right', weight: 0.5, category: 'negation', initialFalsePositiveRate: 0.3 },
116
- { term: 'wrong', weight: 0.5, category: 'negation', initialFalsePositiveRate: 0.3 },
117
- { term: 'redo', weight: 0.6, category: 'negation', initialFalsePositiveRate: 0.15 },
118
- { term: 'start over', weight: 0.6, category: 'negation', initialFalsePositiveRate: 0.15 },
119
-
120
- // 愤怒表达 (Anger) — specific, lower FPR
121
- { term: '垃圾', weight: 0.9, category: 'anger', initialFalsePositiveRate: 0.05 },
122
- { term: '蠢', weight: 0.8, category: 'anger', initialFalsePositiveRate: 0.1 },
123
- { term: '废物', weight: 0.9, category: 'anger', initialFalsePositiveRate: 0.05 },
124
- { term: '白做', weight: 0.7, category: 'anger', initialFalsePositiveRate: 0.15 },
125
- { term: '浪费时间', weight: 0.8, category: 'anger', initialFalsePositiveRate: 0.1 },
126
- { term: 'garbage', weight: 0.9, category: 'anger', initialFalsePositiveRate: 0.05 },
127
- { term: 'stupid', weight: 0.8, category: 'anger', initialFalsePositiveRate: 0.1 },
128
- { term: 'useless', weight: 0.7, category: 'anger', initialFalsePositiveRate: 0.15 },
129
- { term: 'waste of time', weight: 0.8, category: 'anger', initialFalsePositiveRate: 0.1 },
130
-
131
- // 失望信号 (Disappointment) — moderate FPR
132
- { term: '不行啊', weight: 0.5, category: 'disappointment', initialFalsePositiveRate: 0.25 },
133
- { term: '还是不对', weight: 0.6, category: 'disappointment', initialFalsePositiveRate: 0.2 },
134
- { term: '没解决', weight: 0.5, category: 'disappointment', initialFalsePositiveRate: 0.25 },
135
- { term: '没用上', weight: 0.5, category: 'disappointment', initialFalsePositiveRate: 0.25 },
136
- { term: '不能用', weight: 0.5, category: 'disappointment', initialFalsePositiveRate: 0.25 },
137
- { term: 'still not working', weight: 0.6, category: 'disappointment', initialFalsePositiveRate: 0.2 },
138
- { term: "doesn't help", weight: 0.5, category: 'disappointment', initialFalsePositiveRate: 0.25 },
139
- { term: 'not useful', weight: 0.5, category: 'disappointment', initialFalsePositiveRate: 0.25 },
140
-
141
- // 升级信号 (Escalation) — specific context, lower FPR
142
- { term: '你自己看', weight: 0.8, category: 'escalation', initialFalsePositiveRate: 0.1 },
143
- { term: '你确定吗', weight: 0.7, category: 'escalation', initialFalsePositiveRate: 0.15 },
144
- { term: '你是不是没理解', weight: 0.8, category: 'escalation', initialFalsePositiveRate: 0.1 },
145
- { term: '你到底在干什么', weight: 0.9, category: 'escalation', initialFalsePositiveRate: 0.05 },
146
- { term: 'are you sure', weight: 0.7, category: 'escalation', initialFalsePositiveRate: 0.15 },
147
- { term: 'did you even read', weight: 0.8, category: 'escalation', initialFalsePositiveRate: 0.1 },
148
- { term: 'what are you doing', weight: 0.8, category: 'escalation', initialFalsePositiveRate: 0.1 },
149
- ];
150
-
151
- // =========================================================================
152
- // Configuration
153
- // =========================================================================
154
-
155
- export interface EmpathyKeywordConfig {
156
- /** Minimum score to consider a match (0-1) */
157
- matchThreshold: number;
158
-
159
- /** Maximum number of terms to match per message */
160
- maxTermsPerMessage: number;
161
-
162
- /** How often to trigger subagent optimization (number of turns) */
163
- optimizationIntervalTurns: number;
164
-
165
- /** How often to trigger subagent optimization (time-based, ms) */
166
- optimizationIntervalMs: number;
167
-
168
- /** GFI penalty for mild severity */
169
- penaltyMild: number;
170
-
171
- /** GFI penalty for moderate severity */
172
- penaltyModerate: number;
173
-
174
- /** GFI penalty for severe severity */
175
- penaltySevere: number;
176
- }
177
-
178
- export const DEFAULT_EMPATHY_KEYWORD_CONFIG: EmpathyKeywordConfig = {
179
- matchThreshold: 0.3,
180
- maxTermsPerMessage: 5,
181
- optimizationIntervalTurns: 50,
182
- optimizationIntervalMs: 6 * 60 * 60 * 1000, // 6 hours
183
- penaltyMild: 10,
184
- penaltyModerate: 25,
185
- penaltySevere: 40,
186
- };
187
-
188
- // =========================================================================
189
- // Severity Mapping
190
- // =========================================================================
191
-
192
- /**
193
- * Maps a weighted score to a severity level.
194
- *
195
- * Score ranges:
196
- * - 0.0 - 0.3: mild (single low-weight keyword)
197
- * - 0.3 - 0.6: moderate (multiple keywords or high-weight keyword)
198
- * - 0.6 - 1.0: severe (multiple high-weight keywords)
199
- */
200
- export function scoreToSeverity(score: number): 'mild' | 'moderate' | 'severe' {
201
- if (score >= 0.6) return 'severe';
202
- if (score >= 0.3) return 'moderate';
203
- return 'mild';
204
- }
205
-
206
- /**
207
- * Maps severity to GFI penalty value.
208
- */
209
- export function severityToPenalty(
210
- severity: 'mild' | 'moderate' | 'severe',
211
- config: EmpathyKeywordConfig = DEFAULT_EMPATHY_KEYWORD_CONFIG
212
- ): number {
213
- switch (severity) {
214
- case 'mild': return config.penaltyMild;
215
- case 'moderate': return config.penaltyModerate;
216
- case 'severe': return config.penaltySevere;
217
- }
218
- }
219
-
220
- /**
221
- * Normalizes various severity string inputs to the canonical empathy severity type.
222
- * Handles common aliases: 'high' → 'severe', 'medium' → 'moderate'.
223
- */
224
- export function normalizeSeverity(input?: string): 'mild' | 'moderate' | 'severe' {
225
- const normalized = (input || '').toLowerCase();
226
- if (normalized === 'severe' || normalized === 'high') return 'severe';
227
- if (normalized === 'moderate' || normalized === 'medium') return 'moderate';
228
- return 'mild';
229
- }
1
+ export type {
2
+ EmpathyKeywordStore,
3
+ EmpathyKeywordEntry,
4
+ EmpathyKeywordStats,
5
+ EmpathyMatchResult,
6
+ EmpathyKeywordUpdate,
7
+ EmpathyOptimizationResult,
8
+ SeedKeywordEntry,
9
+ EmpathyKeywordConfig,
10
+ } from '@principles/core/prompt-builder';
11
+
12
+ export {
13
+ EMPATHY_SEED_KEYWORDS,
14
+ DEFAULT_EMPATHY_KEYWORD_CONFIG,
15
+ scoreToSeverity,
16
+ severityToPenalty,
17
+ normalizeSeverity,
18
+ } from '@principles/core/prompt-builder';