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
@@ -15,42 +15,6 @@ vi.mock('../../src/core/session-tracker.js', () => ({
15
15
  listSessions: vi.fn(() => []),
16
16
  }));
17
17
 
18
- const { mockStartWorkflow, mockGetWorkflowDebugSummary } = vi.hoisted(() => ({
19
- mockStartWorkflow: vi.fn(),
20
- mockGetWorkflowDebugSummary: vi.fn(),
21
- }));
22
-
23
- vi.mock('../../src/service/subagent-workflow/nocturnal-workflow-manager.js', () => ({
24
- NocturnalWorkflowManager: class {
25
- startWorkflow = mockStartWorkflow;
26
- getWorkflowDebugSummary = mockGetWorkflowDebugSummary;
27
- },
28
- nocturnalWorkflowSpec: {
29
- workflowType: 'nocturnal',
30
- transport: 'runtime_direct',
31
- timeoutMs: 15 * 60 * 1000,
32
- ttlMs: 30 * 60 * 1000,
33
- },
34
- }));
35
-
36
- const { mockGetNocturnalSessionSnapshot, mockListRecentNocturnalCandidateSessions } = vi.hoisted(() => ({
37
- mockGetNocturnalSessionSnapshot: vi.fn(),
38
- mockListRecentNocturnalCandidateSessions: vi.fn(() => []),
39
- }));
40
-
41
- vi.mock('../../src/core/nocturnal-trajectory-extractor.js', async () => {
42
- const actual = await vi.importActual<typeof import('../../src/core/nocturnal-trajectory-extractor.js')>(
43
- '../../src/core/nocturnal-trajectory-extractor.js'
44
- );
45
- return {
46
- ...actual,
47
- createNocturnalTrajectoryExtractor: vi.fn(() => ({
48
- getNocturnalSessionSnapshot: mockGetNocturnalSessionSnapshot,
49
- listRecentNocturnalCandidateSessions: mockListRecentNocturnalCandidateSessions,
50
- })),
51
- };
52
- });
53
-
54
18
  import { EvolutionWorkerService } from '../../src/service/evolution-worker.js';
55
19
  import { safeRmDir } from '../test-utils.js';
56
20
 
@@ -88,8 +52,7 @@ describe('EvolutionWorkerService timeout mechanisms', () => {
88
52
 
89
53
  // ── Pain diagnosis timeout (30 min) ──
90
54
 
91
- // TODO: Fix - task status not transitioning correctly in test
92
- it.skip('times out pain_diagnosis task after 30 minutes → resolution = diagnostician_timeout', async () => { const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-timeout-pain-'));
55
+ it('drops legacy pain_diagnosis tasks correctly on startup', async () => { const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-timeout-pain-'));
93
56
  const stateDir = path.join(workspaceDir, '.state');
94
57
  fs.mkdirSync(stateDir, { recursive: true });
95
58
 
@@ -138,21 +101,20 @@ describe('EvolutionWorkerService timeout mechanisms', () => {
138
101
  const queue = readQueue(stateDir);
139
102
  const task = queue.find((t: any) => t.id === 'timeout-test-30min');
140
103
 
141
- expect(task.status).toBe('completed');
142
- expect(task.resolution).toBe('diagnostician_timeout');
143
- expect(task.completed_at).toBeDefined();
104
+ expect(task).toBeUndefined();
144
105
  } finally {
145
106
  EvolutionWorkerService.stop!({ workspaceDir, stateDir, logger: console } as any);
146
107
  safeRmDir(workspaceDir);
147
108
  }
148
109
  });
149
110
 
150
- it('does not timeout pain_diagnosis task under 30 minutes', async () => {
111
+ it('drops legacy pain_diagnosis tasks instead of timing them out', async () => {
151
112
  const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-no-timeout-'));
152
113
  const stateDir = path.join(workspaceDir, '.state');
153
114
  fs.mkdirSync(stateDir, { recursive: true });
154
115
 
155
- // Create an in_progress pain_diagnosis task that started 10 minutes ago
116
+ // Runtime v2 owns pain diagnosis. EvolutionWorker should not keep or
117
+ // timeout legacy pain_diagnosis queue items.
156
118
  const startedAt = new Date(Date.now() - 10 * 60 * 1000).toISOString();
157
119
  fs.writeFileSync(
158
120
  path.join(stateDir, 'evolution_queue.json'),
@@ -196,85 +158,7 @@ describe('EvolutionWorkerService timeout mechanisms', () => {
196
158
  const queue = readQueue(stateDir);
197
159
  const task = queue.find((t: any) => t.id === 'no-timeout-10min');
198
160
 
199
- // Task should still be in_progress — not yet timed out
200
- expect(task.status).toBe('in_progress');
201
- expect(task.resolution).toBeUndefined();
202
- } finally {
203
- EvolutionWorkerService.stop!({ workspaceDir, stateDir, logger: console } as any);
204
- safeRmDir(workspaceDir);
205
- }
206
- });
207
-
208
- // ── Sleep reflection timeout (60 min default) ──
209
-
210
- it('times out sleep_reflection task after 60 minutes → resolution = failed_max_retries', async () => {
211
- const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-timeout-sleep-'));
212
- const stateDir = path.join(workspaceDir, '.state');
213
- fs.mkdirSync(path.join(stateDir, 'sessions'), { recursive: true });
214
- fs.mkdirSync(path.join(stateDir, 'logs'), { recursive: true });
215
-
216
- // Create an in_progress sleep_reflection task that started 61 minutes ago
217
- const startedAt = new Date(Date.now() - 61 * 60 * 1000).toISOString();
218
- fs.writeFileSync(
219
- path.join(stateDir, 'evolution_queue.json'),
220
- JSON.stringify([
221
- {
222
- id: 'sleep-timeout-60min',
223
- taskKind: 'sleep_reflection',
224
- priority: 'medium',
225
- score: 50,
226
- source: 'nocturnal',
227
- reason: 'Test sleep reflection timeout',
228
- timestamp: startedAt,
229
- enqueued_at: startedAt,
230
- status: 'in_progress',
231
- session_id: 'test',
232
- agent_id: 'main',
233
- started_at: startedAt,
234
- resultRef: 'wf-sleep-timeout',
235
- retryCount: 0,
236
- maxRetries: 1,
237
- recentPainContext: {
238
- mostRecent: null,
239
- recentPainCount: 0,
240
- recentMaxPainScore: 0,
241
- },
242
- },
243
- ], null, 2),
244
- 'utf8'
245
- );
246
-
247
- mockStartWorkflow.mockResolvedValue({
248
- workflowId: 'wf-sleep-timeout',
249
- childSessionKey: 'child-sleep',
250
- state: 'terminal_error',
251
- });
252
- mockGetWorkflowDebugSummary.mockResolvedValue({
253
- state: 'terminal_error',
254
- metadata: {},
255
- recentEvents: [{ reason: 'Test: simulating stuck sleep reflection', payload: {} }],
256
- });
257
-
258
- const mockApi = createMockApi();
259
- EvolutionWorkerService.api = mockApi;
260
-
261
- try {
262
- EvolutionWorkerService.start({
263
- workspaceDir,
264
- stateDir,
265
- logger: mockApi.logger,
266
- config: fastPollConfig,
267
- api: mockApi,
268
- } as any);
269
-
270
- await vi.advanceTimersByTimeAsync(5000);
271
-
272
- const queue = readQueue(stateDir);
273
- const task = queue.find((t: any) => t.id === 'sleep-timeout-60min');
274
-
275
- expect(task.status).toBe('failed');
276
- expect(task.resolution).toBe('failed_max_retries');
277
- expect(task.completed_at).toBeDefined();
161
+ expect(task).toBeUndefined();
278
162
  } finally {
279
163
  EvolutionWorkerService.stop!({ workspaceDir, stateDir, logger: console } as any);
280
164
  safeRmDir(workspaceDir);
@@ -283,8 +167,7 @@ describe('EvolutionWorkerService timeout mechanisms', () => {
283
167
 
284
168
  // ── Report file cleanup on timeout ──
285
169
 
286
- // TODO: Fix - report file not being cleaned up in test
287
- it.skip('cleans up .diagnostician_report_*.json file on pain_diagnosis timeout', async () => {
170
+ it('drops legacy pain_diagnosis tasks and ignores stale diagnostician reports', async () => {
288
171
  const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-timeout-cleanup-'));
289
172
  const stateDir = path.join(workspaceDir, '.state');
290
173
  fs.mkdirSync(stateDir, { recursive: true });
@@ -335,14 +218,13 @@ describe('EvolutionWorkerService timeout mechanisms', () => {
335
218
 
336
219
  await vi.advanceTimersByTimeAsync(5000);
337
220
 
338
- // Verify the report file was cleaned up
339
- expect(fs.existsSync(reportPath)).toBe(false);
221
+ // Verify the report file is ignored because legacy pain_diagnosis tasks are dropped immediately
222
+ expect(fs.existsSync(reportPath)).toBe(true);
340
223
 
341
- // Verify the task was marked as completed with timeout resolution
224
+ // Verify the task was dropped
342
225
  const queue = readQueue(stateDir);
343
226
  const task = queue.find((t: any) => t.id === 'timeout-cleanup');
344
- expect(task.status).toBe('completed');
345
- expect(task.resolution).toBe('diagnostician_timeout');
227
+ expect(task).toBeUndefined();
346
228
  } finally {
347
229
  EvolutionWorkerService.stop!({ workspaceDir, stateDir, logger: console } as any);
348
230
  safeRmDir(workspaceDir);
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Internalization Trigger Adapter - Unit Tests (PRI-63/65)
3
+ *
4
+ * PRI-65: Updated to use diagnosticJson for PI metadata, simulating
5
+ * real SqliteTaskStore behavior where PI fields live inside diagnosticJson.
6
+ */
7
+
8
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
9
+ import { readFileSync } from 'fs';
10
+ import { resolve } from 'path';
11
+ import type { TaskRecord } from '@principles/core/runtime-v2';
12
+ import { createPITaskDiagnosticJson } from '@principles/core/runtime-v2';
13
+ import type { InternalizationChannel, ArtifactRef } from '@principles/core/runtime-v2';
14
+
15
+ type MockLogger = {
16
+ debug?: (msg: string, meta?: Record<string, unknown>) => void;
17
+ info?: (msg: string, meta?: Record<string, unknown>) => void;
18
+ warn?: (msg: string, meta?: Record<string, unknown>) => void;
19
+ error?: (msg: string, meta?: Record<string, unknown>) => void;
20
+ };
21
+
22
+ interface MockProvider {
23
+ listTasks: ReturnType<typeof vi.fn>;
24
+ getTask: ReturnType<typeof vi.fn>;
25
+ }
26
+
27
+ /** Create a base TaskRecord (no PI metadata). Provider returns this from SQLite. */
28
+ function makeTask(overrides: Partial<TaskRecord> & { taskId: string; taskKind: string }): TaskRecord {
29
+ return {
30
+ taskId: overrides.taskId,
31
+ taskKind: overrides.taskKind,
32
+ status: overrides.status ?? 'pending',
33
+ createdAt: overrides.createdAt ?? new Date().toISOString(),
34
+ updatedAt: overrides.updatedAt ?? new Date().toISOString(),
35
+ attemptCount: 0,
36
+ maxAttempts: 3,
37
+ leaseOwner: undefined,
38
+ leaseExpiresAt: undefined,
39
+ lastError: undefined,
40
+ inputRef: undefined,
41
+ resultRef: undefined,
42
+ ...overrides,
43
+ } as TaskRecord;
44
+ }
45
+
46
+ /** Create a TaskRecord with PI metadata stored in diagnosticJson (simulates SqliteTaskStore). */
47
+ function makePITask(
48
+ taskId: string,
49
+ taskKind: string,
50
+ channel: InternalizationChannel,
51
+ options?: {
52
+ status?: TaskRecord['status'];
53
+ dependencyTaskIds?: string[];
54
+ parentTaskId?: string;
55
+ correlationId?: string;
56
+ timeoutMs?: number;
57
+ inputArtifactRefs?: ArtifactRef[];
58
+ outputArtifactRefs?: ArtifactRef[];
59
+ },
60
+ ): TaskRecord {
61
+ const meta = {
62
+ dependencyTaskIds: options?.dependencyTaskIds ?? [],
63
+ channel,
64
+ timeoutMs: options?.timeoutMs ?? 300000,
65
+ inputArtifactRefs: options?.inputArtifactRefs ?? [],
66
+ outputArtifactRefs: options?.outputArtifactRefs ?? [],
67
+ parentTaskId: options?.parentTaskId,
68
+ correlationId: options?.correlationId,
69
+ };
70
+ return {
71
+ ...makeTask({ taskId, taskKind, status: options?.status ?? 'pending' }),
72
+ diagnosticJson: createPITaskDiagnosticJson(meta),
73
+ } as TaskRecord;
74
+ }
75
+
76
+ function readAdapterSource(): string {
77
+ return readFileSync(
78
+ resolve(__dirname, '../../src/service/internalization-trigger-adapter.ts'),
79
+ 'utf8',
80
+ );
81
+ }
82
+
83
+ describe('Internalization Trigger Adapter', () => {
84
+ let mockProvider: MockProvider;
85
+ let mockLogger: MockLogger;
86
+ let adapter: {
87
+ wake: (ctx: { workspaceDir: string; stateDir: string }) => Promise<void>;
88
+ start: (ctx: { workspaceDir: string; stateDir: string }, intervalMs?: number) => () => void;
89
+ stop: () => void;
90
+ };
91
+
92
+ beforeEach(async () => {
93
+ mockLogger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
94
+ mockProvider = { listTasks: vi.fn(), getTask: vi.fn() };
95
+ const mod = await import('../../src/service/internalization-trigger-adapter.js');
96
+ adapter = mod.createInternalizationTrigger(
97
+ mockProvider as import('../../src/service/internalization-trigger-adapter.js').InternalizationTaskProvider,
98
+ mockLogger,
99
+ ) as typeof adapter;
100
+ });
101
+
102
+ describe('wake — basic behavior', () => {
103
+ it('no pending tasks → returns without error', async () => {
104
+ mockProvider.listTasks.mockResolvedValue([]);
105
+ await expect(adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' })).resolves.not.toThrow();
106
+ });
107
+
108
+ it('pending task with no deps → logs INTERNALIZATION_TRIGGER_WAKE with correct payload', async () => {
109
+ const task = makePITask('task-1', 'dreamer', 'prompt', { status: 'pending', dependencyTaskIds: [] });
110
+ mockProvider.listTasks.mockResolvedValue([task]);
111
+ await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
112
+ expect(mockLogger.info).toHaveBeenCalledWith(
113
+ '[PD:InternalizationTrigger] INTERNALIZATION_TRIGGER_WAKE',
114
+ expect.objectContaining({ taskId: 'task-1', taskKind: 'dreamer', gateDecision: 'proceed' }),
115
+ );
116
+ });
117
+
118
+ it('retry_wait task → logs INTERNALIZATION_TRIGGER_WAKE', async () => {
119
+ const task = makePITask('task-retry', 'philosopher', 'prompt', { status: 'retry_wait', dependencyTaskIds: [] });
120
+ mockProvider.listTasks.mockResolvedValue([task]);
121
+ await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
122
+ expect(mockLogger.info).toHaveBeenCalledWith(
123
+ '[PD:InternalizationTrigger] INTERNALIZATION_TRIGGER_WAKE',
124
+ expect.objectContaining({ taskId: 'task-retry', taskKind: 'philosopher' }),
125
+ );
126
+ });
127
+
128
+ it('task with unmet deps → logs INTERNALIZATION_TRIGGER_BLOCKED with gateDecision blocked', async () => {
129
+ const depTask = makePITask('dep-1', 'scribe', 'prompt', { status: 'pending', dependencyTaskIds: [] });
130
+ const task = makePITask('task-blocked', 'dreamer', 'prompt', { status: 'pending', dependencyTaskIds: ['dep-1'] });
131
+ mockProvider.listTasks.mockResolvedValue([task]);
132
+ mockProvider.getTask.mockResolvedValue(depTask);
133
+ await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
134
+ expect(mockLogger.debug).toHaveBeenCalledWith(
135
+ '[PD:InternalizationTrigger] INTERNALIZATION_TRIGGER_BLOCKED',
136
+ expect.objectContaining({ taskId: 'task-blocked', gateDecision: 'blocked', blockedBy: expect.any(Array) }),
137
+ );
138
+ });
139
+
140
+ it('task with failed dep → logs INTERNALIZATION_TRIGGER_BLOCKED with dependency_failed', async () => {
141
+ const depTask = makePITask('dep-failed', 'scribe', 'prompt', { status: 'failed', dependencyTaskIds: [] });
142
+ const task = makePITask('task-dep-failed', 'dreamer', 'prompt', { status: 'pending', dependencyTaskIds: ['dep-failed'] });
143
+ mockProvider.listTasks.mockResolvedValue([task]);
144
+ mockProvider.getTask.mockResolvedValue(depTask);
145
+ await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
146
+ expect(mockLogger.debug).toHaveBeenCalledWith(
147
+ '[PD:InternalizationTrigger] INTERNALIZATION_TRIGGER_BLOCKED',
148
+ expect.objectContaining({ taskId: 'task-dep-failed', gateDecision: 'dependency_failed' }),
149
+ );
150
+ });
151
+
152
+ it('task with missing dep → logs INTERNALIZATION_TRIGGER_BLOCKED (fail closed)', async () => {
153
+ const task = makePITask('task-missing-dep', 'dreamer', 'prompt', { status: 'pending', dependencyTaskIds: ['nonexistent'] });
154
+ mockProvider.listTasks.mockResolvedValue([task]);
155
+ mockProvider.getTask.mockResolvedValue(null);
156
+ await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
157
+ expect(mockLogger.debug).toHaveBeenCalledWith(
158
+ '[PD:InternalizationTrigger] INTERNALIZATION_TRIGGER_BLOCKED',
159
+ expect.objectContaining({ taskId: 'task-missing-dep', gateDecision: 'blocked' }),
160
+ );
161
+ });
162
+ });
163
+
164
+ describe('wake — fail closed', () => {
165
+ it('missing workspaceDir → does not throw', async () => {
166
+ await expect(adapter.wake({ workspaceDir: '', stateDir: '/test/.state' })).resolves.not.toThrow();
167
+ });
168
+
169
+ it('missing stateDir → does not throw', async () => {
170
+ await expect(adapter.wake({ workspaceDir: '/test', stateDir: '' })).resolves.not.toThrow();
171
+ });
172
+
173
+ it('provider throws → does not throw', async () => {
174
+ mockProvider.listTasks.mockRejectedValue(new Error('SQLite error'));
175
+ await expect(adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' })).resolves.not.toThrow();
176
+ });
177
+
178
+ it('task with unknown taskKind → filtered out silently', async () => {
179
+ const unknownTask = makeTask({ taskId: 'task-unknown', taskKind: 'diagnostician', status: 'pending', dependencyTaskIds: [] });
180
+ mockProvider.listTasks.mockResolvedValue([unknownTask]);
181
+ await expect(adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' })).resolves.not.toThrow();
182
+ });
183
+ });
184
+
185
+ describe('wake — fire and forget', () => {
186
+ it('wake() returns in < 200ms even with many tasks', async () => {
187
+ const manyTasks = Array.from({ length: 20 }, (_, i) =>
188
+ makePITask(`task-${i}`, 'dreamer', 'prompt', { status: 'pending', dependencyTaskIds: [] }),
189
+ );
190
+ mockProvider.listTasks.mockResolvedValue(manyTasks);
191
+ const start = Date.now();
192
+ await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
193
+ expect(Date.now() - start).toBeLessThan(200);
194
+ });
195
+
196
+ it('wake() calls only read methods', async () => {
197
+ const task = makePITask('task-readonly', 'scribe', 'prompt', { status: 'pending', dependencyTaskIds: [] });
198
+ mockProvider.listTasks.mockResolvedValue([task]);
199
+ await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
200
+ expect(mockProvider.listTasks).toHaveBeenCalled();
201
+ });
202
+ });
203
+
204
+ describe('start / stop', () => {
205
+ beforeEach(() => { vi.useFakeTimers(); });
206
+ afterEach(() => { vi.useRealTimers(); });
207
+
208
+ it('start() returns a stop function', () => {
209
+ mockProvider.listTasks.mockResolvedValue([]);
210
+ const stop = adapter.start({ workspaceDir: '/test', stateDir: '/test/.state' });
211
+ expect(typeof stop).toBe('function');
212
+ stop();
213
+ });
214
+
215
+ it('stop() is idempotent', () => {
216
+ mockProvider.listTasks.mockResolvedValue([]);
217
+ const stop = adapter.start({ workspaceDir: '/test', stateDir: '/test/.state' });
218
+ expect(() => stop()).not.toThrow();
219
+ expect(() => stop()).not.toThrow();
220
+ });
221
+ });
222
+
223
+ describe('architecture guards (source-level)', () => {
224
+ it('does not import nocturnal-trinity', () => {
225
+ expect(readAdapterSource()).not.toContain('nocturnal-trinity');
226
+ });
227
+
228
+ it('does not import runTrinity', () => {
229
+ expect(readAdapterSource()).not.toContain('runTrinity');
230
+ });
231
+
232
+ it('does import @principles/core/runtime-v2', () => {
233
+ expect(readAdapterSource()).toContain('@principles/core/runtime-v2');
234
+ });
235
+
236
+ it('imports hydratePITaskRecord from core (not own JSON.parse)', () => {
237
+ const src = readAdapterSource();
238
+ expect(src).toContain('hydratePITaskRecord');
239
+ // Must not contain ad-hoc JSON.parse for PI metadata
240
+ expect(src).not.toMatch(/JSON\.parse.*diagnosticJson/);
241
+ });
242
+
243
+ it('does NOT use isValidPITaskRecord directly on raw provider tasks', () => {
244
+ // The adapter now uses hydratePITaskRecord instead of isValidPITaskRecord
245
+ // to determine if a task is a valid PI task.
246
+ // isValidPITaskRecord checks top-level fields which don't exist on persisted tasks.
247
+ const src = readAdapterSource();
248
+ expect(src).not.toMatch(/isValidPITaskRecord\s*\(\s*t\s*\)/);
249
+ });
250
+ });
251
+ });
@@ -22,7 +22,7 @@ import { MonitoringQueryService } from '../../src/service/monitoring-query-servi
22
22
  function createWorkflow(overrides: Partial<WorkflowRow> = {}): WorkflowRow {
23
23
  return {
24
24
  workflow_id: overrides.workflow_id ?? 'wf-1',
25
- workflow_type: overrides.workflow_type ?? 'nocturnal',
25
+ workflow_type: overrides.workflow_type ?? 'rulehost',
26
26
  transport: overrides.transport ?? 'runtime_direct',
27
27
  parent_session_id: overrides.parent_session_id ?? 'parent-1',
28
28
  child_session_key: overrides.child_session_key ?? 'child-1',
@@ -37,23 +37,6 @@ function createWorkflow(overrides: Partial<WorkflowRow> = {}): WorkflowRow {
37
37
  };
38
38
  }
39
39
 
40
- function createEvent(
41
- workflowId: string,
42
- eventType: string,
43
- createdAt: number,
44
- reason = ''
45
- ): WorkflowEventRow {
46
- return {
47
- workflow_id: workflowId,
48
- event_type: eventType,
49
- from_state: null,
50
- to_state: 'completed',
51
- reason,
52
- payload_json: '{}',
53
- created_at: createdAt,
54
- };
55
- }
56
-
57
40
  describe('MonitoringQueryService', () => {
58
41
  beforeEach(() => {
59
42
  vi.clearAllMocks();
@@ -81,33 +64,4 @@ describe('MonitoringQueryService', () => {
81
64
  stuckDuration: null,
82
65
  });
83
66
  });
84
-
85
- it('computes failure rate per terminal workflow instead of per failed stage', () => {
86
- mockListWorkflows.mockReturnValue([
87
- createWorkflow({ workflow_id: 'wf-complete', state: 'completed', duration_ms: 500 }),
88
- createWorkflow({ workflow_id: 'wf-failed', state: 'terminal_error', duration_ms: 750 }),
89
- ]);
90
- mockGetEvents.mockImplementation((workflowId: string) => {
91
- if (workflowId === 'wf-failed') {
92
- return [
93
- createEvent(workflowId, 'trinity_dreamer_start', 1),
94
- createEvent(workflowId, 'trinity_dreamer_failed', 2, 'dreamer failed'),
95
- createEvent(workflowId, 'trinity_philosopher_start', 3),
96
- createEvent(workflowId, 'trinity_philosopher_failed', 4, 'philosopher failed'),
97
- ];
98
- }
99
- return [
100
- createEvent(workflowId, 'trinity_dreamer_start', 1),
101
- createEvent(workflowId, 'trinity_dreamer_complete', 2),
102
- ];
103
- });
104
-
105
- const service = new MonitoringQueryService('/workspace');
106
- const result = service.getTrinityHealth();
107
-
108
- expect(result.totalCalls).toBe(2);
109
- expect(result.failureRate).toBe(0.5);
110
- expect(result.stageStats.dreamer.failed).toBe(1);
111
- expect(result.stageStats.philosopher.failed).toBe(1);
112
- });
113
67
  });
@@ -2,7 +2,7 @@
2
2
  * Unit tests for queue-io.ts — queue persistence layer.
3
3
  */
4
4
 
5
- import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
5
+ import { describe, expect, it, beforeEach, afterEach } from 'vitest';
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
8
  import * as os from 'os';
@@ -15,14 +15,7 @@ import {
15
15
  LOCK_MAX_RETRIES,
16
16
  LOCK_RETRY_DELAY_MS,
17
17
  LOCK_STALE_MS,
18
- readRecentPainContext,
19
18
  } from '../../src/service/queue-io.js';
20
- import { readPainFlagContract } from '../../src/core/pain.js';
21
-
22
- // Mock readPainFlagContract for readRecentPainContext tests
23
- vi.mock('../../src/core/pain.js', () => ({
24
- readPainFlagContract: vi.fn(),
25
- }));
26
19
 
27
20
  let tmpDir: string;
28
21
  beforeEach(() => {
@@ -173,57 +166,3 @@ describe('withQueueLock RAII', () => {
173
166
  release();
174
167
  });
175
168
  });
176
-
177
- describe('readRecentPainContext', () => {
178
- // readPainFlagContract is mocked via vi.mock above
179
- const mockWctx = { workspaceDir: '/fake/workspace' } as any;
180
-
181
- it('returns null context when contract status is not valid', () => {
182
- vi.mocked(readPainFlagContract).mockReturnValueOnce({
183
- status: 'missing',
184
- format: 'missing',
185
- data: {},
186
- missingFields: [],
187
- } as any);
188
- const result = readRecentPainContext(mockWctx);
189
- expect(result.mostRecent).toBeNull();
190
- expect(result.recentPainCount).toBe(0);
191
- });
192
-
193
- it('returns null context when score parses to 0', () => {
194
- vi.mocked(readPainFlagContract).mockReturnValueOnce({
195
- status: 'valid',
196
- format: 'kv',
197
- data: { score: '0', source: 'tool_failure', reason: 'err', time: '2026-04-14T00:00:00Z', session_id: 's1' },
198
- missingFields: [],
199
- } as any);
200
- const result = readRecentPainContext(mockWctx);
201
- expect(result.mostRecent).toBeNull();
202
- });
203
-
204
- it('returns mostRecent with valid score > 0', () => {
205
- vi.mocked(readPainFlagContract).mockReturnValueOnce({
206
- status: 'valid',
207
- format: 'kv',
208
- data: { score: '75', source: 'tool_failure', reason: 'File write failed', time: '2026-04-14T10:00:00Z', session_id: 'sess-001' },
209
- missingFields: [],
210
- } as any);
211
- const result = readRecentPainContext(mockWctx);
212
- expect(result.mostRecent).not.toBeNull();
213
- expect(result.mostRecent!.score).toBe(75);
214
- expect(result.mostRecent!.source).toBe('tool_failure');
215
- expect(result.recentPainCount).toBe(1);
216
- expect(result.recentMaxPainScore).toBe(75);
217
- });
218
-
219
- it('returns null context when contract data is empty object', () => {
220
- vi.mocked(readPainFlagContract).mockReturnValueOnce({
221
- status: 'valid',
222
- format: 'empty',
223
- data: {},
224
- missingFields: [],
225
- } as any);
226
- const result = readRecentPainContext(mockWctx);
227
- expect(result.mostRecent).toBeNull();
228
- });
229
- });
@@ -1000,7 +1000,9 @@ describe('RuntimeSummaryService', () => {
1000
1000
  });
1001
1001
  });
1002
1002
 
1003
- describe('Stalled diagnostician warning', () => {
1003
+ // M8: Runtime v2 uses SQLite task store for pending tasks; legacy diagnostician_tasks.json
1004
+ // is no longer read for pendingTasks count. This test verified stale behavior.
1005
+ describe.skip('Stalled diagnostician warning', () => {
1004
1006
  it('raises a high-signal warning when tasks are injected but no reports are written', () => {
1005
1007
  const workspace = makeWorkspace();
1006
1008
  writeJson(path.join(workspace, '.state', 'AGENT_SCORECARD.json'), {