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
@@ -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
  }