principles-disciple 1.71.0 → 1.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/openclaw.plugin.json +10 -5
  2. package/package.json +17 -19
  3. package/scripts/acceptance-test.mjs +16 -73
  4. package/scripts/sync-plugin.mjs +382 -77
  5. package/src/commands/archive-impl.ts +2 -1
  6. package/src/commands/capabilities.ts +2 -2
  7. package/src/commands/context.ts +2 -2
  8. package/src/commands/disable-impl.ts +2 -1
  9. package/src/commands/evolution-status.ts +16 -16
  10. package/src/commands/export.ts +12 -67
  11. package/src/commands/pain.ts +91 -1
  12. package/src/commands/principle-rollback.ts +2 -1
  13. package/src/commands/promote-impl.ts +7 -43
  14. package/src/commands/rollback-impl.ts +2 -1
  15. package/src/commands/rollback.ts +2 -1
  16. package/src/commands/samples.ts +2 -1
  17. package/src/commands/thinking-os.ts +2 -1
  18. package/src/config/errors.ts +18 -2
  19. package/src/constants/diagnostician.ts +2 -2
  20. package/src/constants/tools.ts +2 -1
  21. package/src/core/__tests__/focus-history.test.ts +210 -0
  22. package/src/core/config.ts +1 -1
  23. package/src/core/confirm-first-gate.ts +255 -0
  24. package/src/core/correction-cue-learner.ts +2 -136
  25. package/src/core/correction-types.ts +16 -88
  26. package/src/core/dictionary.ts +19 -20
  27. package/src/core/empathy-keyword-matcher.ts +17 -289
  28. package/src/core/empathy-types.ts +18 -229
  29. package/src/core/event-log.ts +38 -132
  30. package/src/core/evolution-reducer.ts +21 -2
  31. package/src/core/evolution-types.ts +76 -464
  32. package/src/core/file-store.ts +80 -0
  33. package/src/core/focus-history.ts +228 -955
  34. package/src/core/local-worker-routing.ts +34 -314
  35. package/src/core/merge-gate-audit.ts +0 -195
  36. package/src/core/pain-diagnostic-gate.ts +154 -0
  37. package/src/core/pain-signal.ts +21 -138
  38. package/src/core/pain.ts +15 -88
  39. package/src/core/pd-task-reconciler.ts +26 -115
  40. package/src/core/pd-task-service.ts +9 -9
  41. package/src/core/pd-task-types.ts +23 -127
  42. package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
  43. package/src/core/principle-compiler/code-validator.ts +15 -42
  44. package/src/core/principle-compiler/compiler.ts +100 -15
  45. package/src/core/principle-compiler/index.ts +5 -2
  46. package/src/core/principle-compiler/template-generator.ts +4 -104
  47. package/src/core/principle-injection.ts +10 -202
  48. package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
  49. package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
  50. package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
  51. package/src/core/principle-tree-ledger-adapter.ts +145 -0
  52. package/src/core/principle-tree-ledger.ts +8 -6
  53. package/src/core/reflection/reflection-context.ts +14 -109
  54. package/src/core/replay-engine.ts +8 -500
  55. package/src/core/rule-host-helpers.ts +5 -35
  56. package/src/core/rule-host-types.ts +10 -82
  57. package/src/core/rule-host.ts +6 -63
  58. package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
  59. package/src/core/session-tracker.ts +87 -101
  60. package/src/core/shadow-observation-registry.ts +19 -48
  61. package/src/core/trajectory.ts +3 -1
  62. package/src/core/workflow-funnel-loader.ts +62 -68
  63. package/src/core/workspace-context.ts +46 -0
  64. package/src/core/workspace-dir-service.ts +1 -1
  65. package/src/core/workspace-dir-validation.ts +18 -9
  66. package/src/hooks/AGENTS.md +1 -1
  67. package/src/hooks/gate-block-helper.ts +46 -44
  68. package/src/hooks/gate.ts +207 -7
  69. package/src/hooks/lifecycle.ts +30 -32
  70. package/src/hooks/llm.ts +60 -32
  71. package/src/hooks/pain.ts +297 -103
  72. package/src/hooks/prompt.ts +469 -339
  73. package/src/hooks/subagent.ts +2 -29
  74. package/src/i18n/commands.ts +2 -10
  75. package/src/index.ts +95 -85
  76. package/src/openclaw-sdk.ts +311 -0
  77. package/src/service/central-database.ts +8 -4
  78. package/src/service/evolution-queue-migration.ts +2 -1
  79. package/src/service/evolution-worker.ts +163 -1786
  80. package/src/service/internalization-trigger-adapter.ts +302 -0
  81. package/src/service/keyword-optimization-service.ts +4 -4
  82. package/src/service/monitoring-query-service.ts +1 -215
  83. package/src/service/queue-io.ts +60 -331
  84. package/src/service/runtime-summary-service.ts +115 -18
  85. package/src/service/subagent-workflow/index.ts +0 -41
  86. package/src/service/subagent-workflow/types.ts +9 -120
  87. package/src/service/subagent-workflow/workflow-store.ts +2 -119
  88. package/src/service/workflow-watchdog.ts +0 -43
  89. package/src/types/event-payload.ts +16 -74
  90. package/src/types/event-types.ts +39 -547
  91. package/src/types/hygiene-types.ts +7 -30
  92. package/src/types/principle-tree-schema.ts +20 -222
  93. package/src/types/queue.ts +15 -70
  94. package/src/types/runtime-summary.ts +5 -49
  95. package/src/utils/io.ts +10 -0
  96. package/src/utils/retry.ts +1 -1
  97. package/src/utils/shadow-fingerprint.ts +2 -2
  98. package/src/utils/workspace-resolver.ts +50 -0
  99. package/templates/langs/en/core/AGENTS.md +2 -2
  100. package/templates/langs/en/core/BOOT.md +1 -1
  101. package/templates/langs/en/core/HEARTBEAT.md +2 -2
  102. package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  103. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  104. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  105. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  106. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  107. package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  108. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  109. package/templates/langs/en/skills/evolve-task/SKILL.md +1 -1
  110. package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
  111. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
  112. package/templates/langs/en/skills/pd-mentor/SKILL.md +1 -1
  113. package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
  114. package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
  115. package/templates/langs/zh/core/AGENTS.md +2 -2
  116. package/templates/langs/zh/core/BOOT.md +1 -1
  117. package/templates/langs/zh/core/HEARTBEAT.md +2 -2
  118. package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  119. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  120. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  121. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
  122. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  123. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  124. package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  125. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  126. package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
  127. package/templates/langs/zh/skills/evolve-task/SKILL.md +2 -2
  128. package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
  129. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
  130. package/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -1
  131. package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
  132. package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
  133. package/tests/build-artifacts.test.ts +1 -3
  134. package/tests/commands/evolution-status.test.ts +0 -118
  135. package/tests/core/bootstrap-rules.test.ts +1 -1
  136. package/tests/core/config.test.ts +1 -1
  137. package/tests/core/event-log.test.ts +35 -0
  138. package/tests/core/evolution-engine.test.ts +610 -0
  139. package/tests/core/file-store.test.ts +102 -0
  140. package/tests/core/focus-history.test.ts +203 -11
  141. package/tests/core/merge-gate-audit.test.ts +2 -169
  142. package/tests/core/model-deployment-registry.test.ts +7 -1
  143. package/tests/core/model-training-registry.test.ts +19 -0
  144. package/tests/core/observability.test.ts +0 -1
  145. package/tests/core/pain-diagnostic-gate.test.ts +498 -0
  146. package/tests/core/pain.test.ts +0 -1
  147. package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
  148. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
  149. package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
  150. package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
  151. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
  152. package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
  153. package/tests/core/reflection-context.test.ts +0 -14
  154. package/tests/core/replay-engine.test.ts +127 -215
  155. package/tests/core/rule-host-helpers.test.ts +2 -2
  156. package/tests/core/rule-implementation-runtime.test.ts +0 -27
  157. package/tests/core/workflow-funnel-loader.test.ts +162 -0
  158. package/tests/core/workspace-dir-validation.test.ts +8 -1
  159. package/tests/core-anti-growth.test.ts +192 -0
  160. package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
  161. package/tests/hooks/confirm-first-gate.test.ts +333 -0
  162. package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
  163. package/tests/hooks/gate-auto-correct.test.ts +665 -0
  164. package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
  165. package/tests/hooks/pain.test.ts +269 -12
  166. package/tests/hooks/prompt-characterization.test.ts +500 -0
  167. package/tests/hooks/prompt-size-guard.test.ts +329 -0
  168. package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
  169. package/tests/index.test.ts +94 -1
  170. package/tests/integration/auto-entry-gate.test.ts +248 -0
  171. package/tests/integration/internalization-trigger-guard.test.ts +69 -0
  172. package/tests/integration/m8-legacy-paths.test.ts +63 -0
  173. package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
  174. package/tests/plugin-config-resolution-cutover.test.ts +359 -0
  175. package/tests/runtime-v2-discovery-guard.test.ts +154 -0
  176. package/tests/service/central-database.test.ts +457 -0
  177. package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
  178. package/tests/service/evolution-worker.timeout.test.ts +11 -129
  179. package/tests/service/internalization-trigger-adapter.test.ts +251 -0
  180. package/tests/service/monitoring-query-service.test.ts +1 -47
  181. package/tests/service/queue-io.test.ts +1 -62
  182. package/tests/service/runtime-summary-service.test.ts +184 -3
  183. package/tests/service/workflow-watchdog.test.ts +0 -91
  184. package/tests/utils/file-lock.test.ts +5 -3
  185. package/tests/utils/session-key.test.ts +52 -0
  186. package/tests/utils/subagent-probe.test.ts +48 -1
  187. package/vitest.config.ts +4 -11
  188. package/.planning/codebase/ARCHITECTURE.md +0 -157
  189. package/.planning/codebase/CONCERNS.md +0 -145
  190. package/.planning/codebase/CONVENTIONS.md +0 -148
  191. package/.planning/codebase/INTEGRATIONS.md +0 -81
  192. package/.planning/codebase/STACK.md +0 -87
  193. package/.planning/codebase/STRUCTURE.md +0 -193
  194. package/.planning/codebase/TESTING.md +0 -243
  195. package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
  196. package/docs/COMMAND_REFERENCE.md +0 -76
  197. package/docs/COMMAND_REFERENCE_EN.md +0 -79
  198. package/scripts/build-web.mjs +0 -46
  199. package/scripts/diagnose-nocturnal.mjs +0 -537
  200. package/scripts/seed-nocturnal-scenarios.mjs +0 -384
  201. package/src/commands/nocturnal-review.ts +0 -322
  202. package/src/commands/nocturnal-rollout.ts +0 -790
  203. package/src/commands/nocturnal-train.ts +0 -986
  204. package/src/commands/pd-reflect.ts +0 -88
  205. package/src/core/adaptive-thresholds.ts +0 -478
  206. package/src/core/diagnostician-task-store.ts +0 -192
  207. package/src/core/nocturnal-arbiter.ts +0 -715
  208. package/src/core/nocturnal-artifact-lineage.ts +0 -116
  209. package/src/core/nocturnal-artificer.ts +0 -257
  210. package/src/core/nocturnal-candidate-scoring.ts +0 -530
  211. package/src/core/nocturnal-compliance.ts +0 -1146
  212. package/src/core/nocturnal-dataset.ts +0 -763
  213. package/src/core/nocturnal-executability.ts +0 -428
  214. package/src/core/nocturnal-export.ts +0 -499
  215. package/src/core/nocturnal-paths.ts +0 -240
  216. package/src/core/nocturnal-reasoning-deriver.ts +0 -343
  217. package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
  218. package/src/core/nocturnal-snapshot-contract.ts +0 -99
  219. package/src/core/nocturnal-trajectory-extractor.ts +0 -512
  220. package/src/core/nocturnal-trinity-types.ts +0 -218
  221. package/src/core/nocturnal-trinity.ts +0 -2680
  222. package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
  223. package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
  224. package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
  225. package/src/http/principles-console-route.ts +0 -709
  226. package/src/service/central-health-service.ts +0 -49
  227. package/src/service/central-overview-service.ts +0 -138
  228. package/src/service/control-ui-query-service.ts +0 -900
  229. package/src/service/cooldown-strategy.ts +0 -97
  230. package/src/service/evolution-pain-context.ts +0 -79
  231. package/src/service/evolution-query-service.ts +0 -407
  232. package/src/service/health-query-service.ts +0 -1038
  233. package/src/service/nocturnal-config.ts +0 -214
  234. package/src/service/nocturnal-runtime.ts +0 -734
  235. package/src/service/nocturnal-service.ts +0 -1605
  236. package/src/service/nocturnal-target-selector.ts +0 -545
  237. package/src/service/sleep-cycle.ts +0 -157
  238. package/src/service/startup-reconciler.ts +0 -112
  239. package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
  240. package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
  241. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
  242. package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
  243. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
  244. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
  245. package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
  246. package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
  247. package/src/tools/write-pain-flag.ts +0 -215
  248. package/tests/commands/nocturnal-review.test.ts +0 -448
  249. package/tests/commands/nocturnal-train.test.ts +0 -97
  250. package/tests/commands/pd-reflect.test.ts +0 -49
  251. package/tests/core/adaptive-thresholds.test.ts +0 -261
  252. package/tests/core/nocturnal-arbiter.test.ts +0 -559
  253. package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
  254. package/tests/core/nocturnal-artificer.test.ts +0 -241
  255. package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
  256. package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
  257. package/tests/core/nocturnal-compliance.test.ts +0 -646
  258. package/tests/core/nocturnal-dataset.test.ts +0 -892
  259. package/tests/core/nocturnal-e2e.test.ts +0 -234
  260. package/tests/core/nocturnal-executability.test.ts +0 -357
  261. package/tests/core/nocturnal-export.test.ts +0 -517
  262. package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
  263. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
  264. package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
  265. package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
  266. package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
  267. package/tests/core/nocturnal-trinity.test.ts +0 -2053
  268. package/tests/core/pain-auto-repair.test.ts +0 -96
  269. package/tests/core/pain-integration.test.ts +0 -510
  270. package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
  271. package/tests/http/principles-console-route.test.ts +0 -162
  272. package/tests/integration/chaos-resilience.test.ts +0 -348
  273. package/tests/integration/empathy-workflow-integration.test.ts +0 -626
  274. package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
  275. package/tests/service/control-ui-query-service.test.ts +0 -121
  276. package/tests/service/cooldown-strategy.test.ts +0 -164
  277. package/tests/service/data-endpoints-regression.test.ts +0 -834
  278. package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
  279. package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
  280. package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
  281. package/tests/service/nocturnal-runtime.test.ts +0 -473
  282. package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
  283. package/tests/service/nocturnal-target-selector.test.ts +0 -615
  284. package/tests/service/startup-reconciler.test.ts +0 -148
  285. package/tests/tools/write-pain-flag.test.ts +0 -358
  286. package/ui/src/App.tsx +0 -45
  287. package/ui/src/api.ts +0 -220
  288. package/ui/src/charts.tsx +0 -955
  289. package/ui/src/components/ErrorState.tsx +0 -6
  290. package/ui/src/components/Loading.tsx +0 -13
  291. package/ui/src/components/ProtectedRoute.tsx +0 -12
  292. package/ui/src/components/Shell.tsx +0 -91
  293. package/ui/src/components/WorkspaceConfig.tsx +0 -178
  294. package/ui/src/components/index.ts +0 -5
  295. package/ui/src/context/auth.tsx +0 -80
  296. package/ui/src/context/theme.tsx +0 -66
  297. package/ui/src/hooks/useAutoRefresh.ts +0 -39
  298. package/ui/src/i18n/ui.ts +0 -473
  299. package/ui/src/main.tsx +0 -16
  300. package/ui/src/pages/EvolutionPage.tsx +0 -333
  301. package/ui/src/pages/FeedbackPage.tsx +0 -138
  302. package/ui/src/pages/GateMonitorPage.tsx +0 -136
  303. package/ui/src/pages/LoginPage.tsx +0 -89
  304. package/ui/src/pages/OverviewPage.tsx +0 -599
  305. package/ui/src/pages/SamplesPage.tsx +0 -174
  306. package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
  307. package/ui/src/styles.css +0 -2020
  308. package/ui/src/types.ts +0 -384
  309. package/ui/src/utils/format.ts +0 -15
@@ -1,9 +1,102 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import plugin from '../src/index';
3
+ import type { PluginCommandDefinition, OpenClawPluginApi, PluginCommandContext } from '../src/openclaw-sdk.js';
4
+
5
+ function createMockApi(): { registeredCommands: PluginCommandDefinition[]; api: OpenClawPluginApi } {
6
+ const registeredCommands: PluginCommandDefinition[] = [];
7
+ const api: OpenClawPluginApi = {
8
+ rootDir: '/mock',
9
+ pluginConfig: { language: 'en' },
10
+ logger: {
11
+ debug: () => {},
12
+ info: () => {},
13
+ warn: () => {},
14
+ error: () => {},
15
+ },
16
+ config: {},
17
+ registerCommand: (cmd: PluginCommandDefinition) => {
18
+ registeredCommands.push(cmd);
19
+ },
20
+ registerService: () => {},
21
+ registerTool: () => {},
22
+ registerHttpRoute: () => {},
23
+ on: () => {},
24
+ };
25
+ return { registeredCommands, api };
26
+ }
3
27
 
4
28
  describe('OpenClaw Plugin Scaffolding', () => {
5
29
  it('should export a valid register function', () => {
6
30
  expect(plugin).toBeDefined();
7
31
  expect(typeof plugin.register).toBe('function');
8
32
  });
9
- });
33
+ });
34
+
35
+ describe('Command Registration', () => {
36
+ it('registers /pd-pain with acceptsArgs: true', () => {
37
+ const { registeredCommands, api } = createMockApi();
38
+ plugin.register(api);
39
+
40
+ const pdPain = registeredCommands.find((c) => c.name === 'pd-pain');
41
+ expect(pdPain).toBeDefined();
42
+ expect(pdPain!.acceptsArgs).toBe(true);
43
+ });
44
+
45
+ it('registers /pd-pain handler as async (always returns Promise)', async () => {
46
+ const { registeredCommands, api } = createMockApi();
47
+ plugin.register(api);
48
+
49
+ const pdPain = registeredCommands.find((c) => c.name === 'pd-pain');
50
+ expect(pdPain).toBeDefined();
51
+
52
+ const ctx: PluginCommandContext = {
53
+ sessionId: 'session-123',
54
+ sessionKey: 'sk-123',
55
+ args: 'test pain reason',
56
+ config: { workspaceDir: '/mock', language: 'en' },
57
+ };
58
+
59
+ const result = pdPain!.handler(ctx);
60
+ expect(result).toBeInstanceOf(Promise);
61
+ await result;
62
+ });
63
+
64
+ it('returns error result when workspace resolution fails', async () => {
65
+ const { registeredCommands, api } = createMockApi();
66
+ plugin.register(api);
67
+
68
+ const pdPain = registeredCommands.find((c) => c.name === 'pd-pain');
69
+ expect(pdPain).toBeDefined();
70
+
71
+ const ctx: PluginCommandContext = {
72
+ sessionId: '',
73
+ sessionKey: '',
74
+ args: 'test pain reason',
75
+ config: { language: 'en' },
76
+ };
77
+
78
+ const result = await pdPain!.handler(ctx);
79
+ expect(result).toBeDefined();
80
+ expect(result.text).toBeDefined();
81
+ });
82
+
83
+ it('passes workspaceDir to handler even when ctx.config is undefined', async () => {
84
+ const { registeredCommands, api } = createMockApi();
85
+ plugin.register(api);
86
+
87
+ const pdPain = registeredCommands.find((c) => c.name === 'pd-pain');
88
+ expect(pdPain).toBeDefined();
89
+
90
+ const ctx: PluginCommandContext = {
91
+ sessionId: 'session-123',
92
+ sessionKey: 'sk-123',
93
+ workspaceDir: '/mock/workspace',
94
+ args: 'test pain reason',
95
+ };
96
+
97
+ const result = await pdPain!.handler(ctx);
98
+ expect(result).toBeDefined();
99
+ expect(result.text).toBeDefined();
100
+ expect(ctx.workspaceDir).toBe('/mock/workspace');
101
+ });
102
+ });
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Auto-Entry Gate Integration Tests
3
+ *
4
+ * Verifies that the PainDiagnosticGate → emitPainDetectedEvent → PainSignalBridge
5
+ * path behaves correctly under various conditions.
6
+ *
7
+ * TC1: Trivial tool failure → no diagnosis (GFI below threshold)
8
+ * TC2: Repeated failures → diagnosis triggered
9
+ * TC3: High GFI event → enters Runtime V2
10
+ * TC4: Gate rejection → structured log with gfi, reason, threshold
11
+ */
12
+
13
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+ import { handleAfterToolCall } from '../../src/hooks/pain.js';
17
+ import { WorkspaceContext } from '../../src/core/workspace-context.js';
18
+ import { EventLogService } from '../../src/core/event-log.js';
19
+ import { resetPainDiagnosticGateForTest, evaluatePainDiagnosticGate } from '../../src/core/pain-diagnostic-gate.js';
20
+ import * as ioUtils from '../../src/utils/io.js';
21
+
22
+ vi.mock('fs');
23
+ vi.mock('../../src/utils/io.js');
24
+ vi.mock('../../src/core/evolution-engine.js', () => ({
25
+ recordEvolutionSuccess: vi.fn(),
26
+ recordEvolutionFailure: vi.fn(),
27
+ }));
28
+ vi.mock('../../src/core/evolution-logger.js', () => ({
29
+ createTraceId: vi.fn(() => 'trace-123'),
30
+ getEvolutionLogger: vi.fn(() => ({
31
+ logPainDetected: vi.fn(),
32
+ })),
33
+ }));
34
+
35
+ const mockEmitSync = vi.fn();
36
+ const mockRecordProbationFeedback = vi.fn();
37
+ const mockUpdatePrincipleValueMetrics = vi.fn();
38
+
39
+ describe('Auto-Entry Gate Integration', () => {
40
+ const workspaceDir = '/mock/workspace';
41
+ const mockEventLog = {
42
+ recordToolCall: vi.fn(),
43
+ recordPainSignal: vi.fn(),
44
+ };
45
+ const mockConfig = {
46
+ get: vi.fn().mockReturnValue(30),
47
+ };
48
+
49
+ const mockWctx = {
50
+ workspaceDir,
51
+ stateDir: '/mock/state',
52
+ config: mockConfig,
53
+ eventLog: mockEventLog,
54
+ trajectory: {
55
+ recordToolCall: vi.fn(),
56
+ recordPainEvent: vi.fn(),
57
+ },
58
+ principleTreeLedger: {
59
+ updatePrincipleValueMetrics: mockUpdatePrincipleValueMetrics,
60
+ },
61
+ evolutionReducer: {
62
+ emitSync: mockEmitSync,
63
+ recordProbationFeedback: mockRecordProbationFeedback,
64
+ getPrincipleById: vi.fn().mockReturnValue(null),
65
+ getActivePrinciples: vi.fn().mockReturnValue([]),
66
+ },
67
+ resolve: vi.fn().mockImplementation((key) => {
68
+ if (key === 'PROFILE') return path.join(workspaceDir, '.principles', 'PROFILE.json');
69
+ return '';
70
+ }),
71
+ };
72
+
73
+ beforeEach(() => {
74
+ vi.clearAllMocks();
75
+ mockEmitSync.mockReset();
76
+ mockRecordProbationFeedback.mockReset();
77
+ mockUpdatePrincipleValueMetrics.mockReset();
78
+ vi.spyOn(WorkspaceContext, 'fromHookContext').mockReturnValue(mockWctx as any);
79
+ vi.spyOn(EventLogService, 'get').mockReturnValue(mockEventLog as any);
80
+ vi.spyOn(fs, 'existsSync').mockReturnValue(false);
81
+ resetPainDiagnosticGateForTest();
82
+ });
83
+
84
+ afterEach(() => {
85
+ vi.restoreAllMocks();
86
+ });
87
+
88
+ // ── TC1: Trivial tool failure → no diagnosis ─────────────────────────
89
+
90
+ it('TC1: trivial tool failure does not trigger diagnosis (GFI below threshold)', () => {
91
+ const mockCtx = { workspaceDir, sessionId: 's-tc1', api: { logger: {} } };
92
+ const mockEvent = {
93
+ toolName: 'write',
94
+ params: { file_path: 'src/utils.ts' },
95
+ error: 'Permission denied',
96
+ result: { exitCode: 1 },
97
+ };
98
+
99
+ vi.mocked(ioUtils.normalizePath).mockReturnValue('src/utils.ts');
100
+ vi.mocked(ioUtils.isRisky).mockReturnValue(false);
101
+
102
+ handleAfterToolCall(mockEvent as any, mockCtx as any);
103
+
104
+ // No pain_detected event should be emitted
105
+ expect(mockEmitSync).not.toHaveBeenCalled();
106
+ expect(mockEventLog.recordPainSignal).not.toHaveBeenCalled();
107
+ expect(mockWctx.trajectory.recordPainEvent).not.toHaveBeenCalled();
108
+ });
109
+
110
+ // ── TC2: Repeated failures → diagnosis triggered ──────────────────────
111
+
112
+ it('TC2: repeated same-file write failures trigger diagnosis', () => {
113
+ const mockCtx = { workspaceDir, sessionId: 's-tc2', api: { logger: {} } };
114
+ const mockEvent = {
115
+ toolName: 'write',
116
+ params: { file_path: 'src/main.ts' },
117
+ error: 'EACCES: permission denied',
118
+ result: { exitCode: 1 },
119
+ };
120
+
121
+ vi.mocked(ioUtils.normalizePath).mockReturnValue('src/main.ts');
122
+ vi.mocked(ioUtils.isRisky).mockReturnValue(false);
123
+
124
+ // First failure — accumulates GFI, does not emit
125
+ handleAfterToolCall(mockEvent as any, mockCtx as any);
126
+ expect(mockEmitSync).not.toHaveBeenCalled();
127
+
128
+ // Second failure — repeated, should emit
129
+ handleAfterToolCall(mockEvent as any, mockCtx as any);
130
+
131
+ expect(mockEmitSync).toHaveBeenCalledWith(
132
+ expect.objectContaining({
133
+ type: 'pain_detected',
134
+ data: expect.objectContaining({
135
+ painType: 'tool_failure',
136
+ source: 'write',
137
+ reason: expect.stringContaining('diagnosticGate=high_gfi'),
138
+ }),
139
+ }),
140
+ );
141
+ });
142
+
143
+ // ── TC3: High GFI event → enters Runtime V2 ───────────────────────────
144
+
145
+ it('TC3: high GFI event enters Runtime V2 via PainDiagnosticGate', () => {
146
+ const decision = evaluatePainDiagnosticGate({
147
+ source: 'tool_failure',
148
+ score: 60,
149
+ currentGfi: 80,
150
+ consecutiveErrors: 3,
151
+ sessionId: 's-tc3',
152
+ });
153
+
154
+ expect(decision).toMatchObject({
155
+ shouldDiagnose: true,
156
+ reason: 'high_gfi',
157
+ });
158
+ expect(decision.detail).toContain('GFI');
159
+ });
160
+
161
+ it('TC3: risky high-score operation enters Runtime V2', () => {
162
+ const decision = evaluatePainDiagnosticGate({
163
+ source: 'tool_failure',
164
+ score: 75,
165
+ currentGfi: 0,
166
+ isRisky: true,
167
+ sessionId: 's-tc3-risky',
168
+ });
169
+
170
+ expect(decision).toMatchObject({
171
+ shouldDiagnose: true,
172
+ reason: 'risky_high_score',
173
+ });
174
+ });
175
+
176
+ // ── TC4: Gate rejection structured log ─────────────────────────────────
177
+
178
+ it('TC4: gate rejection produces structured detail with gfi and reason', () => {
179
+ const decision = evaluatePainDiagnosticGate({
180
+ source: 'tool_failure',
181
+ score: 50,
182
+ currentGfi: 32,
183
+ consecutiveErrors: 1,
184
+ sessionId: 's-tc4',
185
+ });
186
+
187
+ expect(decision).toMatchObject({
188
+ shouldDiagnose: false,
189
+ reason: 'below_gate',
190
+ });
191
+
192
+ // The detail string contains score and gfi
193
+ expect(decision.detail).toContain('score=50');
194
+ expect(decision.detail).toContain('gfi=32');
195
+ });
196
+
197
+ it('TC4: gate rejection on low-signal subagent error', () => {
198
+ const decision = evaluatePainDiagnosticGate({
199
+ source: 'subagent_error',
200
+ score: 30,
201
+ currentGfi: 0,
202
+ sessionId: 's-tc4-subagent',
203
+ });
204
+
205
+ expect(decision).toMatchObject({
206
+ shouldDiagnose: false,
207
+ reason: 'below_gate',
208
+ });
209
+ });
210
+
211
+ // ── Manual pain bypasses gate ──────────────────────────────────────────
212
+
213
+ it('manual pain always bypasses gate (except cooldown)', () => {
214
+ const decision = evaluatePainDiagnosticGate({
215
+ source: 'manual',
216
+ score: 1,
217
+ currentGfi: 0,
218
+ sessionId: 's-manual',
219
+ });
220
+
221
+ expect(decision).toMatchObject({
222
+ shouldDiagnose: true,
223
+ reason: 'manual',
224
+ });
225
+ });
226
+
227
+ // ── Cooldown deduplication ─────────────────────────────────────────────
228
+
229
+ it('cooldown prevents duplicate diagnosis within window', () => {
230
+ const input = {
231
+ source: 'tool_failure',
232
+ score: 50,
233
+ currentGfi: 80,
234
+ sessionId: 's-cooldown',
235
+ errorHash: 'same-error',
236
+ nowMs: 1_000,
237
+ };
238
+
239
+ const first = evaluatePainDiagnosticGate(input);
240
+ expect(first.shouldDiagnose).toBe(true);
241
+
242
+ const second = evaluatePainDiagnosticGate({ ...input, nowMs: 2_000 });
243
+ expect(second).toMatchObject({
244
+ shouldDiagnose: false,
245
+ reason: 'cooldown',
246
+ });
247
+ });
248
+ });
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Internalization Trigger Adapter - Integration Guards (PRI-63)
3
+ *
4
+ * Verifies plugin-level architecture constraints:
5
+ * - adapter does not import nocturnal-trinity / runTrinity
6
+ * - adapter does not write .pain_flag or subagent_workflows
7
+ * - wake() is read-only (no mutating store calls)
8
+ * - adapter reuses TaskRecord/PITaskRecord (no second task model)
9
+ */
10
+
11
+ import { describe, it, expect } from 'vitest';
12
+ import { readFileSync } from 'fs';
13
+ import { resolve } from 'path';
14
+ import type { TaskRecord } from '@principles/core/runtime-v2';
15
+
16
+ describe('Internalization Trigger Adapter — Integration Guards', () => {
17
+ const adapterSrc = () =>
18
+ readFileSync(
19
+ resolve(__dirname, '../../src/service/internalization-trigger-adapter.ts'),
20
+ 'utf8',
21
+ );
22
+
23
+ it('adapter source does not import nocturnal-trinity', () => {
24
+ expect(adapterSrc()).not.toContain('nocturnal-trinity');
25
+ });
26
+
27
+ it('adapter source does not import runTrinity or runTrinityAsync', () => {
28
+ const src = adapterSrc();
29
+ expect(src).not.toContain('runTrinity');
30
+ expect(src).not.toContain('runTrinityAsync');
31
+ });
32
+
33
+ it('adapter source does not import Dreamer/Philosopher/Scribe executors', () => {
34
+ const src = adapterSrc();
35
+ expect(src).not.toContain("from 'dreamer'");
36
+ expect(src).not.toContain("from 'philosopher'");
37
+ expect(src).not.toContain("from 'scribe'");
38
+ expect(src).not.toContain("from 'artificer'");
39
+ });
40
+
41
+ it('adapter source does not call PDRuntimeAdapter', () => {
42
+ expect(adapterSrc()).not.toContain('PDRuntimeAdapter');
43
+ });
44
+
45
+ it('adapter source does not write .pain_flag', () => {
46
+ const src = adapterSrc();
47
+ expect(src).not.toContain('.pain_flag');
48
+ expect(src).not.toContain('pain_flag');
49
+ });
50
+
51
+ it('adapter source does not write subagent_workflows', () => {
52
+ const src = adapterSrc();
53
+ expect(src).not.toContain('subagent_workflows');
54
+ });
55
+
56
+ it('adapter imports TaskRecord type from @principles/core/runtime-v2', () => {
57
+ expect(adapterSrc()).toContain('TaskRecord');
58
+ expect(adapterSrc()).toContain('@principles/core/runtime-v2');
59
+ });
60
+
61
+ it('adapter source does not use node:fs/node:path directly (provider abstraction)', () => {
62
+ const src = adapterSrc();
63
+ // node:fs and node:path should not appear in adapter source
64
+ expect(src).not.toContain("from 'node:fs'");
65
+ expect(src).not.toContain('from "node:fs"');
66
+ expect(src).not.toContain("from 'node:path'");
67
+ expect(src).not.toContain('from "node:path"');
68
+ });
69
+ });
@@ -0,0 +1,63 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ const ROOT = path.join(__dirname, '../../../..');
6
+
7
+ function readFile(relativePath: string): string {
8
+ return fs.readFileSync(path.join(ROOT, relativePath), 'utf-8');
9
+ }
10
+
11
+ describe('M8: Legacy diagnostician/cron/subagent paths disabled', () => {
12
+ test('does NOT register createWritePainFlagTool', () => {
13
+ const index = readFile('packages/openclaw-plugin/src/index.ts');
14
+ expect(index).not.toMatch(/createWritePainFlagTool/);
15
+ });
16
+
17
+ test('does NOT have empathy-optimizer in BUILTIN_PD_TASKS', () => {
18
+ const content = readFile('packages/openclaw-plugin/src/core/pd-task-types.ts');
19
+ expect(content).not.toMatch(/empathy-optimizer/);
20
+ });
21
+
22
+ test('does NOT have empathy-optimizer case in buildTaskPrompt', () => {
23
+ const content = readFile('packages/openclaw-plugin/src/core/pd-task-reconciler.ts');
24
+ expect(content).not.toMatch(/case 'empathy-optimizer'/);
25
+ });
26
+
27
+ test('does NOT have EmpathyObserverWorkflowManager in prompt.ts active code', () => {
28
+ const content = readFile('packages/openclaw-plugin/src/hooks/prompt.ts');
29
+ // Only check for actual usage (not comments)
30
+ const uncommented = content.replace(/\/\/.*$/mg, '').replace(/\/\*[\s\S]*?\*\//g, '');
31
+ expect(uncommented).not.toMatch(/EmpathyObserverWorkflowManager/);
32
+ });
33
+
34
+ test('does NOT have EmpathyObserverWorkflowManager import in subagent.ts', () => {
35
+ const content = readFile('packages/openclaw-plugin/src/hooks/subagent.ts');
36
+ expect(content).not.toMatch(/EmpathyObserverWorkflowManager/);
37
+ });
38
+
39
+ test('does NOT have empathy-observer case in subagent.ts active code', () => {
40
+ const content = readFile('packages/openclaw-plugin/src/hooks/subagent.ts');
41
+ const uncommented = content.replace(/\/\/.*$/mg, '');
42
+ expect(uncommented).not.toMatch(/empathy-observer/);
43
+ });
44
+
45
+ test('PD task reconciliation does NOT create PD Empathy Optimizer cron job', () => {
46
+ const content = readFile('packages/openclaw-plugin/src/core/pd-task-types.ts');
47
+ // BUILTIN_PD_TASKS should be empty array
48
+ const match = content.match(/BUILTIN_PD_TASKS\s*:\s*PDTaskSpec\[\]\s*=\s*\[\s*\]/);
49
+ expect(match).not.toBeNull();
50
+ });
51
+
52
+ test('pain signal calls PainSignalBridge.emit via evolutionReducer', () => {
53
+ const content = readFile('packages/openclaw-plugin/src/hooks/pain.ts');
54
+ expect(content).toMatch(/emitPainDetectedEvent/);
55
+ expect(content).toMatch(/evolutionReducer\.emitSync/);
56
+ });
57
+
58
+ test('empathy keyword matcher is still functional (kept capability)', () => {
59
+ const content = readFile('packages/openclaw-plugin/src/core/empathy-keyword-matcher.ts');
60
+ expect(content).toMatch(/matchEmpathyKeywords/);
61
+ expect(content).toMatch(/loadKeywordStore/);
62
+ });
63
+ });
@@ -0,0 +1,125 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ // Find repo root by walking up from cwd until we find .git
6
+ function findRepoRoot(cwd: string): string {
7
+ let dir = cwd;
8
+ while (dir !== path.dirname(dir)) {
9
+ if (fs.existsSync(path.join(dir, '.git'))) {
10
+ return dir;
11
+ }
12
+ dir = path.dirname(dir);
13
+ }
14
+ return cwd;
15
+ }
16
+
17
+ const repoRoot = findRepoRoot(process.cwd());
18
+
19
+ function read(relativePath: string): string {
20
+ return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
21
+ }
22
+
23
+ function listSourceFiles(dir: string): string[] {
24
+ const absDir = path.join(repoRoot, dir);
25
+ const result: string[] = [];
26
+ for (const entry of fs.readdirSync(absDir, { withFileTypes: true })) {
27
+ const absPath = path.join(absDir, entry.name);
28
+ const relPath = path.relative(repoRoot, absPath).replace(/\\/g, '/');
29
+ if (entry.isDirectory()) {
30
+ result.push(...listSourceFiles(relPath));
31
+ } else if (entry.isFile() && relPath.endsWith('.ts')) {
32
+ result.push(relPath);
33
+ }
34
+ }
35
+ return result;
36
+ }
37
+
38
+ describe('Runtime V2 pain entrypoint guard', () => {
39
+ it('does not keep active pain flag writer APIs in openclaw-plugin/src', () => {
40
+ const offenders = listSourceFiles('packages/openclaw-plugin/src')
41
+ .filter((file) => read(file).match(/\b(writePainFlag|recordAndWritePainFlag)\b/));
42
+
43
+ expect(offenders).toEqual([]);
44
+ });
45
+
46
+ it('does not actively write .pain_flag from openclaw-plugin/src', () => {
47
+ const writePatterns = [
48
+ /writeFileSync\s*\([^)]*painFlagPath/s,
49
+ /appendFileSync\s*\([^)]*painFlagPath/s,
50
+ /atomicWriteFileSync\s*\([^)]*painFlagPath/s,
51
+ /createWriteStream\s*\([^)]*painFlagPath/s,
52
+ ];
53
+ const offenders = listSourceFiles('packages/openclaw-plugin/src')
54
+ .filter((file) => writePatterns.some((pattern) => pattern.test(read(file))));
55
+
56
+ expect(offenders).toEqual([]);
57
+ });
58
+
59
+ it('pd pain record enters PainToPrincipleService directly', () => {
60
+ const source = read('packages/pd-cli/src/commands/pain-record.ts');
61
+
62
+ expect(source).toMatch(/PainToPrincipleService/);
63
+ expect(source).toMatch(/service\.recordPain/);
64
+ expect(source).not.toMatch(/createPainSignalBridge/);
65
+ expect(source).not.toMatch(/\.pain_flag|PAIN_FLAG|writePainFlag/);
66
+ });
67
+
68
+ it('after_tool_call failure emits Runtime V2 pain event instead of writing .pain_flag', () => {
69
+ const source = read('packages/openclaw-plugin/src/hooks/pain.ts');
70
+
71
+ expect(source).toMatch(/emitPainDetectedEvent\(wctx,\s*\{/);
72
+ expect(source).toMatch(/type:\s*'pain_detected'/);
73
+ expect(source).toMatch(/PainToPrincipleService/);
74
+ // Verify it calls createPainToPrincipleService (the factory function inside pain.ts)
75
+ expect(source).toMatch(/createPainToPrincipleService\(/);
76
+ // Verify it does NOT call createPainSignalBridge (legacy factory)
77
+ expect(source).not.toMatch(/createPainSignalBridge\(/);
78
+ expect(source).not.toMatch(/writePainFlag|recordAndWritePainFlag/);
79
+ expect(source).not.toMatch(/writeFileSync\s*\([^)]*painFlagPath/s);
80
+ expect(source).not.toMatch(/atomicWriteFileSync\s*\([^)]*painFlagPath/s);
81
+ });
82
+
83
+ // PRI-29: emitPainDetectedEvent → PainToPrincipleService service contract guards
84
+ it('pain.ts constructs PainToPrincipleService with workspaceDir and stateDir from wctx', () => {
85
+ const source = read('packages/openclaw-plugin/src/hooks/pain.ts');
86
+
87
+ // Must construct PrincipleTreeLedgerAdapter with wctx.stateDir
88
+ expect(source).toMatch(/new PrincipleTreeLedgerAdapter\(\{\s*stateDir:\s*wctx\.stateDir\s*\}\)/);
89
+
90
+ // Must construct PainToPrincipleService with wctx properties
91
+ expect(source).toMatch(/new PainToPrincipleService\(\{/);
92
+ expect(source).toMatch(/workspaceDir:\s*wctx\.workspaceDir/);
93
+ expect(source).toMatch(/stateDir:\s*wctx\.stateDir/);
94
+ expect(source).toMatch(/owner:\s*['"]openclaw-plugin['"]/);
95
+ expect(source).toMatch(/autoIntakeEnabled:\s*true/);
96
+ });
97
+
98
+ it('pain.ts passes recordObservability: true to service.recordPain', () => {
99
+ const source = read('packages/openclaw-plugin/src/hooks/pain.ts');
100
+
101
+ expect(source).toMatch(/recordObservability:\s*true/);
102
+ });
103
+
104
+ it('pain.ts logs PAIN_SERVICE_FAILED for failed status with failureCategory', () => {
105
+ const source = read('packages/openclaw-plugin/src/hooks/pain.ts');
106
+
107
+ expect(source).toMatch(/PAIN_SERVICE_FAILED/);
108
+ // Must include failureCategory in the log payload
109
+ expect(source).toMatch(/failureCategory:\s*result\.failureCategory/);
110
+ });
111
+
112
+ it('pain.ts logs PAIN_SERVICE_SKIPPED for skipped status', () => {
113
+ const source = read('packages/openclaw-plugin/src/hooks/pain.ts');
114
+
115
+ expect(source).toMatch(/PAIN_SERVICE_SKIPPED/);
116
+ });
117
+
118
+ it('pain.ts logs PAIN_SERVICE_ERROR for exceptions', () => {
119
+ const source = read('packages/openclaw-plugin/src/hooks/pain.ts');
120
+
121
+ expect(source).toMatch(/PAIN_SERVICE_ERROR/);
122
+ // Must include "recordPain threw" in the error message
123
+ expect(source).toMatch(/recordPain threw:/);
124
+ });
125
+ });