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.
Files changed (319) hide show
  1. package/INSTALL.md +1 -3
  2. package/openclaw.plugin.json +10 -5
  3. package/package.json +17 -19
  4. package/scripts/acceptance-test.mjs +16 -73
  5. package/scripts/sync-plugin.mjs +382 -77
  6. package/src/commands/archive-impl.ts +2 -1
  7. package/src/commands/capabilities.ts +2 -2
  8. package/src/commands/context.ts +2 -2
  9. package/src/commands/disable-impl.ts +2 -1
  10. package/src/commands/evolution-status.ts +16 -16
  11. package/src/commands/export.ts +12 -67
  12. package/src/commands/pain.ts +91 -1
  13. package/src/commands/principle-rollback.ts +2 -1
  14. package/src/commands/promote-impl.ts +7 -43
  15. package/src/commands/rollback-impl.ts +2 -1
  16. package/src/commands/rollback.ts +2 -1
  17. package/src/commands/samples.ts +2 -1
  18. package/src/commands/thinking-os.ts +2 -1
  19. package/src/config/errors.ts +18 -2
  20. package/src/constants/diagnostician.ts +2 -2
  21. package/src/constants/tools.ts +2 -1
  22. package/src/core/__tests__/focus-history.test.ts +210 -0
  23. package/src/core/config.ts +1 -1
  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 +29 -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/migration.ts +0 -1
  37. package/src/core/pain-diagnostic-gate.ts +154 -0
  38. package/src/core/pain-signal.ts +21 -138
  39. package/src/core/pain.ts +15 -88
  40. package/src/core/path-resolver.ts +0 -1
  41. package/src/core/paths.ts +0 -1
  42. package/src/core/pd-task-reconciler.ts +26 -115
  43. package/src/core/pd-task-service.ts +9 -9
  44. package/src/core/pd-task-types.ts +23 -127
  45. package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
  46. package/src/core/principle-compiler/code-validator.ts +15 -42
  47. package/src/core/principle-compiler/compiler.ts +100 -15
  48. package/src/core/principle-compiler/index.ts +5 -2
  49. package/src/core/principle-compiler/template-generator.ts +4 -104
  50. package/src/core/principle-injection.ts +10 -202
  51. package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
  52. package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
  53. package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
  54. package/src/core/principle-tree-ledger-adapter.ts +145 -0
  55. package/src/core/principle-tree-ledger.ts +8 -6
  56. package/src/core/reflection/reflection-context.ts +14 -109
  57. package/src/core/replay-engine.ts +8 -500
  58. package/src/core/rule-host-helpers.ts +5 -35
  59. package/src/core/rule-host-types.ts +10 -82
  60. package/src/core/rule-host.ts +6 -63
  61. package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
  62. package/src/core/session-tracker.ts +87 -101
  63. package/src/core/shadow-observation-registry.ts +19 -48
  64. package/src/core/trajectory.ts +3 -1
  65. package/src/core/workflow-funnel-loader.ts +62 -68
  66. package/src/core/workspace-context.ts +46 -0
  67. package/src/core/workspace-dir-service.ts +1 -1
  68. package/src/core/workspace-dir-validation.ts +18 -9
  69. package/src/hooks/AGENTS.md +1 -1
  70. package/src/hooks/gate-block-helper.ts +71 -64
  71. package/src/hooks/gate.ts +183 -31
  72. package/src/hooks/lifecycle.ts +30 -32
  73. package/src/hooks/llm.ts +60 -32
  74. package/src/hooks/pain.ts +297 -103
  75. package/src/hooks/prompt.ts +400 -440
  76. package/src/hooks/subagent.ts +2 -29
  77. package/src/i18n/commands.ts +2 -10
  78. package/src/index.ts +95 -85
  79. package/src/openclaw-sdk.ts +311 -0
  80. package/src/service/central-database.ts +8 -4
  81. package/src/service/evolution-queue-migration.ts +2 -1
  82. package/src/service/evolution-worker.ts +163 -1786
  83. package/src/service/internalization-trigger-adapter.ts +302 -0
  84. package/src/service/keyword-optimization-service.ts +4 -4
  85. package/src/service/monitoring-query-service.ts +1 -215
  86. package/src/service/queue-io.ts +60 -331
  87. package/src/service/runtime-summary-service.ts +59 -16
  88. package/src/service/subagent-workflow/index.ts +0 -41
  89. package/src/service/subagent-workflow/types.ts +9 -120
  90. package/src/service/subagent-workflow/workflow-store.ts +2 -119
  91. package/src/service/workflow-watchdog.ts +0 -43
  92. package/src/types/event-payload.ts +16 -74
  93. package/src/types/event-types.ts +38 -547
  94. package/src/types/hygiene-types.ts +7 -30
  95. package/src/types/principle-tree-schema.ts +20 -222
  96. package/src/types/queue.ts +15 -70
  97. package/src/types/runtime-summary.ts +5 -49
  98. package/src/utils/io.ts +8 -20
  99. package/src/utils/retry.ts +1 -1
  100. package/src/utils/shadow-fingerprint.ts +2 -2
  101. package/src/utils/workspace-resolver.ts +50 -0
  102. package/templates/langs/en/core/AGENTS.md +7 -7
  103. package/templates/langs/en/core/BOOT.md +1 -1
  104. package/templates/langs/en/core/HEARTBEAT.md +2 -2
  105. package/templates/langs/en/principles/THINKING_OS.md +3 -2
  106. package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  107. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  108. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  109. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  110. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  111. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  112. package/templates/langs/en/skills/evolve-task/SKILL.md +3 -3
  113. package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
  114. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
  115. package/templates/langs/en/skills/pd-mentor/SKILL.md +2 -3
  116. package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
  117. package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
  118. package/templates/langs/zh/core/AGENTS.md +7 -7
  119. package/templates/langs/zh/core/BOOT.md +1 -1
  120. package/templates/langs/zh/core/HEARTBEAT.md +2 -2
  121. package/templates/langs/zh/principles/THINKING_OS.md +3 -2
  122. package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  123. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  124. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  125. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
  126. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  127. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  128. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  129. package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
  130. package/templates/langs/zh/skills/evolve-task/SKILL.md +4 -4
  131. package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
  132. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
  133. package/templates/langs/zh/skills/pd-mentor/SKILL.md +2 -3
  134. package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
  135. package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
  136. package/tests/build-artifacts.test.ts +1 -3
  137. package/tests/commands/evolution-status.test.ts +0 -118
  138. package/tests/core/bootstrap-rules.test.ts +1 -1
  139. package/tests/core/config.test.ts +1 -1
  140. package/tests/core/event-log.test.ts +35 -0
  141. package/tests/core/evolution-engine.test.ts +610 -0
  142. package/tests/core/file-store.test.ts +102 -0
  143. package/tests/core/focus-history.test.ts +203 -11
  144. package/tests/core/merge-gate-audit.test.ts +2 -169
  145. package/tests/core/migration.test.ts +7 -7
  146. package/tests/core/model-deployment-registry.test.ts +7 -1
  147. package/tests/core/model-training-registry.test.ts +19 -0
  148. package/tests/core/observability.test.ts +0 -1
  149. package/tests/core/pain-diagnostic-gate.test.ts +498 -0
  150. package/tests/core/pain.test.ts +0 -1
  151. package/tests/core/path-resolver.test.ts +1 -1
  152. package/tests/core/paths-refactor.test.ts +0 -22
  153. package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
  154. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
  155. package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
  156. package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
  157. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
  158. package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
  159. package/tests/core/reflection-context.test.ts +0 -14
  160. package/tests/core/replay-engine.test.ts +127 -215
  161. package/tests/core/rule-host-helpers.test.ts +2 -2
  162. package/tests/core/rule-implementation-runtime.test.ts +0 -27
  163. package/tests/core/workflow-funnel-loader.test.ts +162 -0
  164. package/tests/core/workspace-context.test.ts +2 -2
  165. package/tests/core/workspace-dir-validation.test.ts +8 -1
  166. package/tests/core-anti-growth.test.ts +191 -0
  167. package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
  168. package/tests/hooks/confirm-first-removal.test.ts +188 -0
  169. package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
  170. package/tests/hooks/gate-auto-correct.test.ts +665 -0
  171. package/tests/hooks/gate-no-path-write-tool.test.ts +172 -0
  172. package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
  173. package/tests/hooks/pain.test.ts +269 -12
  174. package/tests/hooks/prompt-characterization.test.ts +500 -0
  175. package/tests/hooks/prompt-size-guard.test.ts +32 -17
  176. package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
  177. package/tests/index.test.ts +94 -1
  178. package/tests/integration/auto-entry-gate.test.ts +248 -0
  179. package/tests/integration/internalization-trigger-guard.test.ts +69 -0
  180. package/tests/integration/m8-legacy-paths.test.ts +63 -0
  181. package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
  182. package/tests/plugin-config-resolution-cutover.test.ts +359 -0
  183. package/tests/runtime-v2-discovery-guard.test.ts +154 -0
  184. package/tests/service/central-database.test.ts +457 -0
  185. package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
  186. package/tests/service/evolution-worker.timeout.test.ts +11 -129
  187. package/tests/service/internalization-trigger-adapter.test.ts +251 -0
  188. package/tests/service/monitoring-query-service.test.ts +1 -47
  189. package/tests/service/queue-io.test.ts +1 -62
  190. package/tests/service/runtime-summary-service.test.ts +3 -1
  191. package/tests/service/workflow-watchdog.test.ts +0 -91
  192. package/tests/utils/file-lock.test.ts +5 -3
  193. package/tests/utils/session-key.test.ts +52 -0
  194. package/tests/utils/subagent-probe.test.ts +48 -1
  195. package/vitest.config.ts +4 -11
  196. package/.planning/codebase/ARCHITECTURE.md +0 -157
  197. package/.planning/codebase/CONCERNS.md +0 -145
  198. package/.planning/codebase/CONVENTIONS.md +0 -148
  199. package/.planning/codebase/INTEGRATIONS.md +0 -81
  200. package/.planning/codebase/STACK.md +0 -87
  201. package/.planning/codebase/STRUCTURE.md +0 -193
  202. package/.planning/codebase/TESTING.md +0 -243
  203. package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
  204. package/docs/COMMAND_REFERENCE.md +0 -76
  205. package/docs/COMMAND_REFERENCE_EN.md +0 -79
  206. package/scripts/build-web.mjs +0 -46
  207. package/scripts/diagnose-nocturnal.mjs +0 -537
  208. package/scripts/seed-nocturnal-scenarios.mjs +0 -384
  209. package/src/commands/nocturnal-review.ts +0 -322
  210. package/src/commands/nocturnal-rollout.ts +0 -790
  211. package/src/commands/nocturnal-train.ts +0 -986
  212. package/src/commands/pd-reflect.ts +0 -88
  213. package/src/core/adaptive-thresholds.ts +0 -478
  214. package/src/core/diagnostician-task-store.ts +0 -192
  215. package/src/core/nocturnal-arbiter.ts +0 -715
  216. package/src/core/nocturnal-artifact-lineage.ts +0 -116
  217. package/src/core/nocturnal-artificer.ts +0 -257
  218. package/src/core/nocturnal-candidate-scoring.ts +0 -530
  219. package/src/core/nocturnal-compliance.ts +0 -1146
  220. package/src/core/nocturnal-dataset.ts +0 -763
  221. package/src/core/nocturnal-executability.ts +0 -428
  222. package/src/core/nocturnal-export.ts +0 -499
  223. package/src/core/nocturnal-paths.ts +0 -240
  224. package/src/core/nocturnal-reasoning-deriver.ts +0 -343
  225. package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
  226. package/src/core/nocturnal-snapshot-contract.ts +0 -99
  227. package/src/core/nocturnal-trajectory-extractor.ts +0 -512
  228. package/src/core/nocturnal-trinity-types.ts +0 -218
  229. package/src/core/nocturnal-trinity.ts +0 -2680
  230. package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
  231. package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
  232. package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
  233. package/src/http/principles-console-route.ts +0 -709
  234. package/src/service/central-health-service.ts +0 -49
  235. package/src/service/central-overview-service.ts +0 -138
  236. package/src/service/control-ui-query-service.ts +0 -900
  237. package/src/service/cooldown-strategy.ts +0 -97
  238. package/src/service/evolution-pain-context.ts +0 -79
  239. package/src/service/evolution-query-service.ts +0 -407
  240. package/src/service/health-query-service.ts +0 -1038
  241. package/src/service/nocturnal-config.ts +0 -214
  242. package/src/service/nocturnal-runtime.ts +0 -734
  243. package/src/service/nocturnal-service.ts +0 -1605
  244. package/src/service/nocturnal-target-selector.ts +0 -545
  245. package/src/service/sleep-cycle.ts +0 -157
  246. package/src/service/startup-reconciler.ts +0 -112
  247. package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
  248. package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
  249. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
  250. package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
  251. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
  252. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
  253. package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
  254. package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
  255. package/src/tools/write-pain-flag.ts +0 -215
  256. package/templates/langs/en/skills/plan-script/SKILL.md +0 -32
  257. package/templates/langs/zh/skills/plan-script/SKILL.md +0 -32
  258. package/tests/commands/nocturnal-review.test.ts +0 -448
  259. package/tests/commands/nocturnal-train.test.ts +0 -97
  260. package/tests/commands/pd-reflect.test.ts +0 -49
  261. package/tests/core/adaptive-thresholds.test.ts +0 -261
  262. package/tests/core/nocturnal-arbiter.test.ts +0 -559
  263. package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
  264. package/tests/core/nocturnal-artificer.test.ts +0 -241
  265. package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
  266. package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
  267. package/tests/core/nocturnal-compliance.test.ts +0 -646
  268. package/tests/core/nocturnal-dataset.test.ts +0 -892
  269. package/tests/core/nocturnal-e2e.test.ts +0 -234
  270. package/tests/core/nocturnal-executability.test.ts +0 -357
  271. package/tests/core/nocturnal-export.test.ts +0 -517
  272. package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
  273. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
  274. package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
  275. package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
  276. package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
  277. package/tests/core/nocturnal-trinity.test.ts +0 -2053
  278. package/tests/core/pain-auto-repair.test.ts +0 -96
  279. package/tests/core/pain-integration.test.ts +0 -510
  280. package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
  281. package/tests/http/principles-console-route.test.ts +0 -162
  282. package/tests/integration/chaos-resilience.test.ts +0 -348
  283. package/tests/integration/empathy-workflow-integration.test.ts +0 -626
  284. package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
  285. package/tests/service/control-ui-query-service.test.ts +0 -121
  286. package/tests/service/cooldown-strategy.test.ts +0 -164
  287. package/tests/service/data-endpoints-regression.test.ts +0 -834
  288. package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
  289. package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
  290. package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
  291. package/tests/service/nocturnal-runtime.test.ts +0 -473
  292. package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
  293. package/tests/service/nocturnal-target-selector.test.ts +0 -615
  294. package/tests/service/startup-reconciler.test.ts +0 -148
  295. package/tests/tools/write-pain-flag.test.ts +0 -358
  296. package/ui/src/App.tsx +0 -45
  297. package/ui/src/api.ts +0 -220
  298. package/ui/src/charts.tsx +0 -955
  299. package/ui/src/components/ErrorState.tsx +0 -6
  300. package/ui/src/components/Loading.tsx +0 -13
  301. package/ui/src/components/ProtectedRoute.tsx +0 -12
  302. package/ui/src/components/Shell.tsx +0 -91
  303. package/ui/src/components/WorkspaceConfig.tsx +0 -178
  304. package/ui/src/components/index.ts +0 -5
  305. package/ui/src/context/auth.tsx +0 -80
  306. package/ui/src/context/theme.tsx +0 -66
  307. package/ui/src/hooks/useAutoRefresh.ts +0 -39
  308. package/ui/src/i18n/ui.ts +0 -473
  309. package/ui/src/main.tsx +0 -16
  310. package/ui/src/pages/EvolutionPage.tsx +0 -333
  311. package/ui/src/pages/FeedbackPage.tsx +0 -138
  312. package/ui/src/pages/GateMonitorPage.tsx +0 -136
  313. package/ui/src/pages/LoginPage.tsx +0 -89
  314. package/ui/src/pages/OverviewPage.tsx +0 -599
  315. package/ui/src/pages/SamplesPage.tsx +0 -174
  316. package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
  317. package/ui/src/styles.css +0 -2020
  318. package/ui/src/types.ts +0 -384
  319. package/ui/src/utils/format.ts +0 -15
@@ -25,21 +25,19 @@ import {
25
25
  listImplementationsByLifecycleState,
26
26
  } from './principle-tree-ledger.js';
27
27
  import { loadEntrySource } from './code-implementation-storage.js';
28
- import { createRuleHostHelpers } from './rule-host-helpers.js';
28
+ import { createRuleHostHelpers } from '@principles/core/runtime-v2';
29
+ import { mergeDecisions } from '@principles/core/runtime-v2';
29
30
  import { loadRuleImplementationModule } from './rule-implementation-runtime.js';
30
31
  import type {
31
32
  RuleHostInput,
32
33
  RuleHostResult,
33
34
  RuleHostMeta,
34
35
  LoadedImplementation,
35
- } from './rule-host-types.js';
36
+ } from '@principles/core/runtime-v2';
36
37
  import type { Implementation } from '../types/principle-tree-schema.js';
37
38
 
38
- export interface RuleHostLogger {
39
-
40
- warn?: (_message: string) => void;
41
-
42
- }
39
+ import type { RuleHostLogger } from '@principles/core/runtime-v2';
40
+ export type { RuleHostLogger } from '@principles/core/runtime-v2';
43
41
 
44
42
  export class RuleHost {
45
43
  private readonly stateDir: string;
@@ -62,63 +60,8 @@ export class RuleHost {
62
60
 
63
61
  evaluate(input: RuleHostInput): RuleHostResult | undefined {
64
62
  try {
65
- // Load active code implementations from the ledger
66
63
  const activeImpls = this._loadActiveCodeImplementations();
67
-
68
- if (activeImpls.length === 0) {
69
- return undefined;
70
- }
71
-
72
- // Merge decisions from all active implementations
73
-
74
-
75
- let blocked: RuleHostResult | undefined;
76
- const approvals: RuleHostResult[] = [];
77
-
78
- for (const impl of activeImpls) {
79
- try {
80
- const result = impl.evaluate(input);
81
-
82
- if (!result.matched) {
83
- continue;
84
- }
85
-
86
- if (result.decision === 'block') {
87
- blocked = result;
88
- break; // Short-circuit on block
89
- }
90
-
91
- if (result.decision === 'requireApproval') {
92
- approvals.push(result);
93
- }
94
- // 'allow' is implicit — no action needed
95
- } catch (evalError: unknown) {
96
- // Individual implementation error: log and continue (D-08)
97
- this.logger.warn?.(
98
- `[RuleHost] Implementation ${impl.implId} evaluation failed: ${String(evalError)}`
99
- );
100
- }
101
- }
102
-
103
- if (blocked) {
104
- return blocked;
105
- }
106
-
107
- if (approvals.length > 0) {
108
- // Merge multiple requireApproval results
109
- return {
110
- decision: 'requireApproval',
111
- matched: true,
112
- reason: approvals.map((a) => a.reason).join('; '),
113
- diagnostics: approvals.reduce<Record<string, unknown>>(
114
- (acc, a) => ({ ...acc, ...a.diagnostics }),
115
- {}
116
- ),
117
- };
118
- }
119
-
120
- // All implementations returned allow or matched=false — no opinion
121
- return undefined;
64
+ return mergeDecisions(activeImpls, input, this.logger);
122
65
  } catch (hostError: unknown) {
123
66
  // Conservative degradation: log and return undefined (D-08)
124
67
  this.logger.warn?.(
@@ -0,0 +1,231 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs';
3
+ import * as yaml from 'js-yaml';
4
+ import { SqliteConnection, SqliteActivationStateStore, computeEffectiveFlags, DEFAULT_FEATURE_FLAGS } from '@principles/core/runtime-v2';
5
+ import type { ActivationStatusRecord, EffectiveFeatureFlags } from '@principles/core/runtime-v2';
6
+
7
+ export const RUNTIME_V2_PRINCIPLE_BUDGET = 2000;
8
+
9
+ export interface ActivatedPrinciple {
10
+ principleId: string;
11
+ text: string;
12
+ artifactId: string;
13
+ activationId: string;
14
+ }
15
+
16
+ export interface PromptActivationReaderResult {
17
+ principles: ActivatedPrinciple[];
18
+ warnings: string[];
19
+ source: 'runtime_v2';
20
+ }
21
+
22
+ export interface PromptActivationReaderDeps {
23
+ logger?: { warn?: (msg: string) => void; info?: (msg: string) => void; error?: (msg: string) => void };
24
+ }
25
+
26
+ const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
27
+
28
+ function isRecord(value: unknown): value is Record<string, unknown> {
29
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
30
+ }
31
+
32
+ export class PromptActivationReader {
33
+ private readonly workspaceDir: string;
34
+ private readonly deps: PromptActivationReaderDeps;
35
+
36
+ constructor(workspaceDir: string, deps?: PromptActivationReaderDeps) {
37
+ this.workspaceDir = workspaceDir;
38
+ this.deps = deps ?? {};
39
+ }
40
+
41
+ async readActivatedPrinciples(): Promise<PromptActivationReaderResult> {
42
+ const warnings: string[] = [];
43
+ const principles: ActivatedPrinciple[] = [];
44
+
45
+ const flags = this.loadFeatureFlags();
46
+ const promptFlag = flags.flags['prompt'];
47
+ if (!promptFlag || !promptFlag.enabled) {
48
+ this.deps.logger?.info?.(`[PD:RuntimeV2] Prompt feature flag disabled — skipping Runtime V2 activation read`);
49
+ return { principles, warnings: ['prompt_feature_disabled'], source: 'runtime_v2' };
50
+ }
51
+
52
+ let sqliteConn: SqliteConnection | null = null;
53
+ try {
54
+ sqliteConn = new SqliteConnection(this.workspaceDir);
55
+ const store = new SqliteActivationStateStore(sqliteConn);
56
+
57
+ const activations = await store.listPromptActivations();
58
+
59
+ for (const activation of activations) {
60
+ if (activation.channel !== 'prompt' || activation.action !== 'prompt_activate') {
61
+ continue;
62
+ }
63
+
64
+ const result = this.resolvePrincipleFromActivation(sqliteConn, activation);
65
+ if (result.ok) {
66
+ principles.push(result.principle);
67
+ } else {
68
+ warnings.push(result.warning);
69
+ this.deps.logger?.warn?.(`[PD:RuntimeV2] ${result.warning}`);
70
+ }
71
+ }
72
+ } catch (e) {
73
+ const msg = e instanceof Error ? e.message : String(e);
74
+ const warning = `activation_db_unreadable: ${msg}; nextAction=check_workspace_pd_state_db`;
75
+ warnings.push(warning);
76
+ this.deps.logger?.warn?.(`[PD:RuntimeV2] ${warning}`);
77
+ } finally {
78
+ try {
79
+ sqliteConn?.close();
80
+ } catch {
81
+ // best-effort
82
+ }
83
+ }
84
+
85
+ return { principles, warnings, source: 'runtime_v2' };
86
+ }
87
+
88
+ private loadFeatureFlags(): EffectiveFeatureFlags {
89
+ const configPath = path.join(this.workspaceDir, '.pd', 'feature-flags.yaml');
90
+
91
+ if (!fs.existsSync(configPath)) {
92
+ return computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath);
93
+ }
94
+
95
+ let raw: string;
96
+ try {
97
+ raw = fs.readFileSync(configPath, 'utf8');
98
+ } catch (e) {
99
+ const msg = e instanceof Error ? e.message : String(e);
100
+ this.deps.logger?.warn?.(`[PD:RuntimeV2] Feature flags unreadable: ${msg} — using defaults`);
101
+ return computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath);
102
+ }
103
+
104
+ let parsed: unknown;
105
+ try {
106
+ parsed = yaml.load(raw, { schema: yaml.JSON_SCHEMA });
107
+ } catch {
108
+ this.deps.logger?.warn?.(`[PD:RuntimeV2] Feature flags YAML parse error — using defaults`);
109
+ return {
110
+ ...computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath),
111
+ warnings: ['feature-flags.yaml: YAML parse error, using defaults'],
112
+ };
113
+ }
114
+
115
+ if (!isRecord(parsed)) {
116
+ this.deps.logger?.warn?.(`[PD:RuntimeV2] Feature flags not a mapping — using defaults`);
117
+ return {
118
+ ...computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath),
119
+ warnings: ['feature-flags.yaml: expected a mapping, using defaults'],
120
+ };
121
+ }
122
+
123
+ const parsedRecord: Record<string, unknown> = Object.create(null);
124
+ const yamlWarnings: string[] = [];
125
+ for (const key of Object.keys(parsed)) {
126
+ if (DANGEROUS_KEYS.has(key)) {
127
+ yamlWarnings.push(`feature-flags.yaml: dangerous key '${key}' rejected`);
128
+ continue;
129
+ }
130
+ if (Object.hasOwn(parsed, key)) {
131
+ parsedRecord[key] = parsed[key];
132
+ }
133
+ }
134
+
135
+ const result = computeEffectiveFlags(parsedRecord, DEFAULT_FEATURE_FLAGS, configPath);
136
+ if (yamlWarnings.length > 0) {
137
+ result.warnings = [...yamlWarnings, ...result.warnings];
138
+ for (const w of yamlWarnings) {
139
+ this.deps.logger?.warn?.(`[PD:RuntimeV2] ${w}`);
140
+ }
141
+ }
142
+ return result;
143
+ }
144
+
145
+ private resolvePrincipleFromActivation(
146
+ sqliteConn: SqliteConnection,
147
+ activation: ActivationStatusRecord,
148
+ ): { ok: true; principle: ActivatedPrinciple } | { ok: false; warning: string } {
149
+ const db = sqliteConn.getDb();
150
+
151
+ let contentJson: string;
152
+
153
+ try {
154
+ const row = db.prepare(`
155
+ SELECT artifact_id, artifact_kind, content_json, validation_status
156
+ FROM pi_artifacts
157
+ WHERE artifact_id = ?
158
+ `).get(activation.artifactId);
159
+
160
+ if (!isRecord(row)) {
161
+ return { ok: false, warning: `artifact_query_unexpected: artifactId=${activation.artifactId}; nextAction=check_pi_artifacts_table` };
162
+ }
163
+
164
+ const artifact_id = Object.hasOwn(row, 'artifact_id') && typeof row.artifact_id === 'string' && row.artifact_id.length > 0 ? row.artifact_id : null;
165
+ const artifact_kind = Object.hasOwn(row, 'artifact_kind') && typeof row.artifact_kind === 'string' ? row.artifact_kind : null;
166
+ const raw_content_json = Object.hasOwn(row, 'content_json') && typeof row.content_json === 'string' ? row.content_json : null;
167
+ const validation_status = Object.hasOwn(row, 'validation_status') && typeof row.validation_status === 'string' ? row.validation_status : null;
168
+
169
+ if (!artifact_id) {
170
+ return { ok: false, warning: `artifact_not_found: artifactId=${activation.artifactId} activationId=${activation.activationId}; nextAction=verify_artifact_exists_or_remove_stale_activation` };
171
+ }
172
+
173
+ if (artifact_kind !== 'principle') {
174
+ return { ok: false, warning: `artifact_not_principle: artifactId=${artifact_id} kind=${artifact_kind ?? 'missing'}; nextAction=skip_non_principle_activations` };
175
+ }
176
+
177
+ if (validation_status !== 'validated') {
178
+ return { ok: false, warning: `artifact_not_validated: artifactId=${artifact_id} status=${validation_status ?? 'missing'}; nextAction=skip_unvalidated_artifacts` };
179
+ }
180
+
181
+ if (raw_content_json === null) {
182
+ return { ok: false, warning: `artifact_missing_content_json: artifactId=${artifact_id}; nextAction=ensure_artifact_has_content_json` };
183
+ }
184
+
185
+ contentJson = raw_content_json;
186
+ } catch (e) {
187
+ const msg = e instanceof Error ? e.message : String(e);
188
+ return { ok: false, warning: `artifact_query_failed: artifactId=${activation.artifactId} reason=${msg}; nextAction=check_pi_artifacts_table` };
189
+ }
190
+
191
+ let parsed: unknown;
192
+ try {
193
+ parsed = JSON.parse(contentJson);
194
+ } catch (e) {
195
+ const msg = e instanceof Error ? e.message : String(e);
196
+ return { ok: false, warning: `artifact_content_json_parse_error: artifactId=${activation.artifactId} reason=${msg}; nextAction=fix_artifact_content_json` };
197
+ }
198
+
199
+ if (!isRecord(parsed)) {
200
+ return { ok: false, warning: `artifact_content_malformed: artifactId=${activation.artifactId} reason=parsed_to_non_object; nextAction=fix_artifact_content_json` };
201
+ }
202
+
203
+ const principleId = Object.hasOwn(parsed, 'principleId') && typeof parsed.principleId === 'string' ? parsed.principleId : undefined;
204
+ const text = Object.hasOwn(parsed, 'text') && typeof parsed.text === 'string' ? parsed.text : undefined;
205
+
206
+ const draftObj = Object.hasOwn(parsed, 'principleDraft') && isRecord(parsed.principleDraft) ? parsed.principleDraft : null;
207
+ const draftTitle = draftObj && Object.hasOwn(draftObj, 'title') && typeof draftObj.title === 'string' ? draftObj.title : undefined;
208
+ const draftStatement = draftObj && Object.hasOwn(draftObj, 'statement') && typeof draftObj.statement === 'string' ? draftObj.statement : undefined;
209
+
210
+ const resolvedPrincipleId = principleId && principleId.length > 0 ? principleId : draftTitle;
211
+ const resolvedText = text && text.length > 0 ? text : draftStatement;
212
+
213
+ if (!resolvedPrincipleId || resolvedPrincipleId.length === 0) {
214
+ return { ok: false, warning: `artifact_missing_principle_id: artifactId=${activation.artifactId}; nextAction=ensure_artifact_has_principleId_or_principleDraft_title` };
215
+ }
216
+
217
+ if (!resolvedText || resolvedText.length === 0) {
218
+ return { ok: false, warning: `artifact_missing_text: artifactId=${activation.artifactId} principleId=${resolvedPrincipleId}; nextAction=ensure_artifact_has_text_or_principleDraft_statement` };
219
+ }
220
+
221
+ return {
222
+ ok: true,
223
+ principle: {
224
+ principleId: resolvedPrincipleId,
225
+ text: resolvedText,
226
+ artifactId: activation.artifactId,
227
+ activationId: activation.activationId,
228
+ },
229
+ };
230
+ }
231
+ }
@@ -5,6 +5,14 @@ import { atomicWriteFileSync } from '../utils/io.js';
5
5
  import type { PainConfig } from './config.js';
6
6
  import { SystemLogger } from './system-logger.js';
7
7
  import { TWO_HOURS_MS } from '../config/defaults/runtime.js';
8
+ import {
9
+ applyFriction as coreApplyFriction,
10
+ applyDecay as coreApplyDecay,
11
+ applyRelief as coreApplyRelief,
12
+ classifyGfiStage,
13
+ DEFAULT_GFI_POLICY,
14
+ } from '@principles/core/runtime-v2';
15
+ import type { GfiState, GfiEvent, GfiSource } from '@principles/core/runtime-v2';
8
16
 
9
17
  export interface TokenUsage {
10
18
  input?: number;
@@ -14,6 +22,32 @@ export interface TokenUsage {
14
22
  total?: number;
15
23
  }
16
24
 
25
+ // ── GFI Core Kernel Adapter ──────────────────────────────────────────────────
26
+
27
+ /** Convert SessionState GFI fields to GfiState for core kernel */
28
+ function toGfiState(state: SessionState): GfiState {
29
+ return {
30
+ currentGfi: state.currentGfi ?? 0,
31
+ gfiBySource: (state.gfiBySource ?? {}) as Partial<Record<GfiSource, number>>,
32
+ lastErrorHash: state.lastErrorHash === '' ? undefined : state.lastErrorHash,
33
+ lastErrorSource: state.lastErrorSource || undefined,
34
+ consecutiveErrors: state.consecutiveErrors ?? 0,
35
+ lastGfiDecayAt: state.lastGfiDecayAt,
36
+ dailyGfiPeak: state.dailyGfiPeak,
37
+ };
38
+ }
39
+
40
+ /** Apply GfiState result back onto SessionState */
41
+ function applyGfiResult(state: SessionState, result: GfiState): void {
42
+ state.currentGfi = result.currentGfi;
43
+ state.gfiBySource = { ...result.gfiBySource } as Record<string, number>;
44
+ state.lastErrorHash = result.lastErrorHash ?? '';
45
+ state.lastErrorSource = result.lastErrorSource ?? '';
46
+ state.consecutiveErrors = result.consecutiveErrors;
47
+ state.lastGfiDecayAt = result.lastGfiDecayAt;
48
+ state.dailyGfiPeak = result.dailyGfiPeak ?? state.dailyGfiPeak;
49
+ }
50
+
17
51
  export interface SessionState {
18
52
  sessionId: string;
19
53
  sessionKey?: string; // Structured session key from OpenClaw (e.g., agent:main:cron:job-1:run:xxx)
@@ -233,13 +267,6 @@ function getOrCreateSession(sessionId: string, workspaceDir?: string, sessionKey
233
267
  return state;
234
268
  }
235
269
 
236
- function ensureGfiLedger(state: SessionState): Record<string, number> {
237
- if (!state.gfiBySource || typeof state.gfiBySource !== 'object') {
238
- state.gfiBySource = {};
239
- }
240
- return state.gfiBySource;
241
- }
242
-
243
270
  export function trackToolRead(sessionId: string, filePath: string, workspaceDir?: string): SessionState {
244
271
  const state = getOrCreateSession(sessionId, workspaceDir);
245
272
  const normalizedPath = path.posix.normalize(filePath.replace(/\\/g, '/'));
@@ -287,9 +314,8 @@ export function trackLlmOutput(sessionId: string, usage: TokenUsage | undefined,
287
314
 
288
315
  /**
289
316
  * Tracks physical friction based on tool execution failures.
317
+ * Delegates to core GFI kernel for scoring.
290
318
  */
291
-
292
-
293
319
  export function trackFriction(
294
320
  sessionId: string,
295
321
  deltaF: number,
@@ -298,40 +324,46 @@ export function trackFriction(
298
324
  options?: { source?: string }
299
325
  ): SessionState {
300
326
  const state = getOrCreateSession(sessionId, workspaceDir);
301
- const ledger = ensureGfiLedger(state);
302
-
303
- if (hash && hash === state.lastErrorHash) {
304
- state.consecutiveErrors++;
305
- } else {
306
- state.lastErrorSource = options?.source || (hash ? `unattributed:${hash}` : 'unattributed:unknown');
307
- state.lastErrorHash = hash;
308
- state.consecutiveErrors = 1;
327
+
328
+ const source: GfiSource = (options?.source as GfiSource) ?? 'unknown';
329
+ const event: GfiEvent = {
330
+ source,
331
+ baseScore: deltaF,
332
+ hash: hash || undefined,
333
+ };
334
+
335
+ const coreState = toGfiState(state);
336
+ const nextCore = coreApplyFriction(coreState, event, DEFAULT_GFI_POLICY);
337
+
338
+ // Preserve composite source key for unattributed sources
339
+ if (!options?.source && hash) {
340
+ const val = nextCore.gfiBySource['unknown'];
341
+ if (val !== undefined) {
342
+ const s = nextCore.gfiBySource as Record<string, number>;
343
+ delete s['unknown'];
344
+ const key = `unattributed:${hash}`;
345
+ s[key] = (s[key] ?? 0) + val;
346
+ }
309
347
  }
310
348
 
311
- // GFI formula with multiplier: GFI = GFI + (Delta_F * 1.5^(n-1))
312
- const multiplier = Math.pow(1.5, state.consecutiveErrors - 1);
313
- const addedFriction = deltaF * multiplier;
314
- state.currentGfi = (state.currentGfi || 0) + addedFriction;
315
- const sourceKey = options?.source || (hash ? `unattributed:${hash}` : 'unattributed:unknown');
316
- ledger[sourceKey] = (ledger[sourceKey] || 0) + addedFriction;
349
+ applyGfiResult(state, nextCore);
350
+
317
351
  touchActivity(state, 'control');
318
-
319
- SystemLogger.log(state.workspaceDir, 'GFI_INC', `Friction added: +${addedFriction.toFixed(1)} (Base: ${deltaF}, Mult: ${multiplier.toFixed(2)}). Total GFI: ${state.currentGfi.toFixed(1)}`);
320
-
352
+
321
353
  // Update daily stats
322
354
  state.dailyToolFailures++;
323
355
  state.dailyGfiPeak = Math.max(state.dailyGfiPeak, state.currentGfi);
324
-
356
+
325
357
  // Schedule persistence
326
- // Update decay anchor to prevent retroactive decay of the new friction
327
358
  state.lastGfiDecayAt = Date.now();
328
359
  schedulePersistence(state);
329
-
360
+
330
361
  return state;
331
362
  }
332
363
 
333
364
  /**
334
365
  * Resets the friction index upon successful action.
366
+ * Delegates to core GFI kernel for relief computation.
335
367
  */
336
368
  export function resetFriction(
337
369
  sessionId: string,
@@ -339,50 +371,36 @@ export function resetFriction(
339
371
  options?: { source?: string; amount?: number }
340
372
  ): SessionState {
341
373
  const state = getOrCreateSession(sessionId, workspaceDir);
342
- const ledger = ensureGfiLedger(state);
343
374
 
344
375
  if (options?.source) {
345
- const sourceKey = options.source;
346
- const currentSource = ledger[sourceKey] || 0;
347
- const requestedAmount = Number.isFinite(options.amount) ? Number(options.amount) : currentSource;
348
- const amountToRemove = Math.max(0, Math.min(currentSource, requestedAmount));
349
-
350
- if (amountToRemove > 0) {
351
- ledger[sourceKey] = Math.max(0, currentSource - amountToRemove);
352
- if (ledger[sourceKey] === 0) {
353
- delete ledger[sourceKey];
354
- }
355
- state.currentGfi = Math.max(0, (state.currentGfi || 0) - amountToRemove);
376
+ const coreState = toGfiState(state);
377
+ const nextCore = coreApplyRelief(coreState, { source: options.source, amount: options.amount ?? 0 }, Date.now(), DEFAULT_GFI_POLICY);
378
+ applyGfiResult(state, nextCore);
379
+
380
+ if (state.currentGfi > 0) {
356
381
  SystemLogger.log(
357
382
  state.workspaceDir,
358
383
  'GFI_SLICE_RESET',
359
- `Friction slice reset for ${sourceKey}: -${amountToRemove.toFixed(1)}. Total GFI: ${state.currentGfi.toFixed(1)}`
384
+ `Friction slice reset for ${options.source}: remaining GFI=${state.currentGfi.toFixed(1)}`
360
385
  );
361
-
362
- if (state.lastErrorSource === sourceKey) {
363
- state.consecutiveErrors = 0;
364
- state.lastErrorHash = '';
365
- state.lastErrorSource = '';
366
- }
367
386
  }
368
387
  touchActivity(state, 'control');
369
388
  schedulePersistence(state);
370
389
  return state;
371
390
  }
372
391
 
373
- if (state.currentGfi > 0) {
374
- SystemLogger.log(state.workspaceDir, 'GFI_RESET', `Friction reset to 0 (Was: ${state.currentGfi.toFixed(1)}). Action successful.`);
392
+ // Full reset via core kernel
393
+ const previousGfi = state.currentGfi;
394
+ const coreState = toGfiState(state);
395
+ const nextCore = coreApplyRelief(coreState, { source: 'all', amount: 100 }, Date.now(), DEFAULT_GFI_POLICY);
396
+ applyGfiResult(state, nextCore);
397
+
398
+ if (previousGfi > 0) {
399
+ SystemLogger.log(state.workspaceDir, 'GFI_RESET', `Friction reset to 0 (Was: ${previousGfi.toFixed(1)}). Action successful.`);
375
400
  }
376
- state.currentGfi = 0;
377
- state.gfiBySource = {};
378
- state.lastErrorSource = '';
379
- state.consecutiveErrors = 0;
380
- state.lastErrorHash = '';
381
401
  touchActivity(state, 'control');
382
-
383
- // Schedule persistence
402
+
384
403
  schedulePersistence(state);
385
-
386
404
  return state;
387
405
  }
388
406
 
@@ -538,15 +556,9 @@ export function resetDailyStats(sessionId: string): void {
538
556
  }
539
557
 
540
558
  /**
541
- * Apply time-based decay to GFI using segmented exponential decay.
542
- *
543
- * Decay rates:
544
- * - GFI >= 70 (severe): 3%/min - fast recovery to avoid prolonged blocking
545
- * - GFI 40-70 (moderate): 2%/min - medium decay
546
- * - GFI < 40 (mild): 1%/min - slow decay to retain as warning
547
- *
548
- * Formula: GFI_new = GFI * (1 - λ)^elapsedMinutes
549
- *
559
+ * Apply time-based decay to GFI using stage-aware linear decay.
560
+ * Delegates to core GFI kernel.
561
+ *
550
562
  * @param sessionId - The session to decay
551
563
  * @param elapsedMinutes - Minutes since last decay
552
564
  * @returns Updated session state, or undefined if session not found or GFI is 0
@@ -554,49 +566,23 @@ export function resetDailyStats(sessionId: string): void {
554
566
  export function decayGfi(sessionId: string, elapsedMinutes: number): SessionState | undefined {
555
567
  const state = sessions.get(sessionId);
556
568
  if (!state || state.currentGfi <= 0 || elapsedMinutes <= 0) return undefined;
557
-
558
- // Determine decay rate based on current GFI level (segmented)
559
-
560
- let decayRate: number;
561
- if (state.currentGfi >= 70) {
562
- decayRate = 0.03; // 3%/min for severe friction
563
- } else if (state.currentGfi >= 40) {
564
- decayRate = 0.02; // 2%/min for moderate friction
565
- } else {
566
- decayRate = 0.01; // 1%/min for mild friction
567
- }
568
-
569
- // Exponential decay: GFI_new = GFI * (1-λ)^Δt
570
- const decayFactor = Math.pow(1 - decayRate, elapsedMinutes);
569
+
570
+ const coreState = toGfiState(state);
571
+ const stage = classifyGfiStage(state.currentGfi, DEFAULT_GFI_POLICY);
572
+ const nextCore = coreApplyDecay(coreState, elapsedMinutes, DEFAULT_GFI_POLICY, stage, Date.now());
571
573
  const previousGfi = state.currentGfi;
572
- state.currentGfi = Math.max(0, state.currentGfi * decayFactor);
573
-
574
- // Apply same decay factor to all sources
575
- const ledger = ensureGfiLedger(state);
576
- for (const source of Object.keys(ledger)) {
577
- ledger[source] = Math.max(0, ledger[source] * decayFactor);
578
- // Remove sources that have decayed below 0.1
579
- if (ledger[source] < 0.1) {
580
- delete ledger[source];
581
- }
582
- }
583
-
584
- // Round to 1 decimal place
585
- state.currentGfi = Math.round(state.currentGfi * 10) / 10;
586
-
587
- // Update last decay timestamp
588
- state.lastGfiDecayAt = Date.now();
589
-
574
+ applyGfiResult(state, nextCore);
575
+
590
576
  // Log if significant decay
591
577
  const decayedAmount = previousGfi - state.currentGfi;
592
578
  if (decayedAmount >= 1) {
593
579
  SystemLogger.log(
594
580
  state.workspaceDir,
595
581
  'GFI_DECAY',
596
- `GFI decayed by ${decayedAmount.toFixed(1)} (${elapsedMinutes}min at ${decayRate*100}%/min). ${previousGfi.toFixed(1)} → ${state.currentGfi.toFixed(1)}`
582
+ `GFI decayed by ${decayedAmount.toFixed(1)} (${elapsedMinutes}min). ${previousGfi.toFixed(1)} → ${state.currentGfi.toFixed(1)}`
597
583
  );
598
584
  }
599
-
585
+
600
586
  schedulePersistence(state);
601
587
  return state;
602
588
  }