principles-disciple 1.11.0 → 1.13.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 (233) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +66 -0
  3. package/esbuild.config.js +1 -1
  4. package/openclaw.plugin.json +4 -4
  5. package/package.json +2 -3
  6. package/run-nocturnal.mjs +30 -0
  7. package/scripts/db-migrate.mjs +170 -0
  8. package/scripts/sync-plugin.mjs +250 -6
  9. package/src/commands/archive-impl.ts +136 -0
  10. package/src/commands/capabilities.ts +4 -2
  11. package/src/commands/context.ts +5 -1
  12. package/src/commands/disable-impl.ts +151 -0
  13. package/src/commands/evolution-status.ts +64 -19
  14. package/src/commands/export.ts +8 -6
  15. package/src/commands/focus.ts +8 -20
  16. package/src/commands/nocturnal-review.ts +5 -7
  17. package/src/commands/nocturnal-rollout.ts +1 -12
  18. package/src/commands/nocturnal-train.ts +21 -47
  19. package/src/commands/pain.ts +10 -5
  20. package/src/commands/principle-rollback.ts +4 -2
  21. package/src/commands/promote-impl.ts +274 -0
  22. package/src/commands/rollback-impl.ts +234 -0
  23. package/src/commands/rollback.ts +6 -3
  24. package/src/commands/samples.ts +2 -0
  25. package/src/commands/thinking-os.ts +3 -4
  26. package/src/commands/workflow-debug.ts +2 -1
  27. package/src/config/errors.ts +1 -0
  28. package/src/core/AGENTS.md +34 -0
  29. package/src/core/adaptive-thresholds.ts +4 -3
  30. package/src/core/code-implementation-storage.ts +241 -0
  31. package/src/core/config.ts +5 -2
  32. package/src/core/control-ui-db.ts +29 -10
  33. package/src/core/detection-funnel.ts +12 -7
  34. package/src/core/diagnostician-task-store.ts +156 -0
  35. package/src/core/dictionary.ts +4 -4
  36. package/src/core/empathy-keyword-matcher.ts +7 -3
  37. package/src/core/empathy-types.ts +13 -2
  38. package/src/core/event-log.ts +14 -6
  39. package/src/core/evolution-engine.ts +27 -31
  40. package/src/core/evolution-logger.ts +3 -2
  41. package/src/core/evolution-reducer.ts +110 -31
  42. package/src/core/evolution-types.ts +10 -0
  43. package/src/core/external-training-contract.ts +1 -0
  44. package/src/core/focus-history.ts +38 -24
  45. package/src/core/hygiene/tracker.ts +10 -6
  46. package/src/core/init.ts +5 -2
  47. package/src/core/migration.ts +3 -3
  48. package/src/core/model-deployment-registry.ts +6 -4
  49. package/src/core/model-training-registry.ts +5 -3
  50. package/src/core/nocturnal-arbiter.ts +13 -14
  51. package/src/core/nocturnal-artifact-lineage.ts +117 -0
  52. package/src/core/nocturnal-artificer.ts +257 -0
  53. package/src/core/nocturnal-candidate-scoring.ts +4 -2
  54. package/src/core/nocturnal-compliance.ts +67 -19
  55. package/src/core/nocturnal-dataset.ts +95 -2
  56. package/src/core/nocturnal-executability.ts +2 -3
  57. package/src/core/nocturnal-export.ts +6 -3
  58. package/src/core/nocturnal-rule-implementation-validator.ts +245 -0
  59. package/src/core/nocturnal-trajectory-extractor.ts +10 -3
  60. package/src/core/nocturnal-trinity.ts +319 -61
  61. package/src/core/pain-context-extractor.ts +29 -15
  62. package/src/core/pain.ts +7 -5
  63. package/src/core/path-resolver.ts +16 -15
  64. package/src/core/paths.ts +2 -1
  65. package/src/core/pd-task-reconciler.ts +463 -0
  66. package/src/core/pd-task-service.ts +42 -0
  67. package/src/core/pd-task-store.ts +77 -0
  68. package/src/core/pd-task-types.ts +128 -0
  69. package/src/core/principle-internalization/deprecated-readiness.ts +91 -0
  70. package/src/core/principle-internalization/internalization-routing-policy.ts +208 -0
  71. package/src/core/principle-internalization/lifecycle-metrics.ts +149 -0
  72. package/src/core/principle-internalization/lifecycle-read-model.ts +243 -0
  73. package/src/core/principle-internalization/lifecycle-refresh.ts +11 -0
  74. package/src/core/principle-internalization/principle-lifecycle-service.ts +167 -0
  75. package/src/core/principle-training-state.ts +95 -370
  76. package/src/core/principle-tree-ledger.ts +733 -0
  77. package/src/core/principle-tree-migration.ts +195 -0
  78. package/src/core/profile.ts +3 -1
  79. package/src/core/promotion-gate.ts +14 -18
  80. package/src/core/replay-engine.ts +562 -0
  81. package/src/core/risk-calculator.ts +6 -4
  82. package/src/core/rule-host-helpers.ts +39 -0
  83. package/src/core/rule-host-types.ts +82 -0
  84. package/src/core/rule-host.ts +245 -0
  85. package/src/core/rule-implementation-runtime.ts +38 -0
  86. package/src/core/schema/db-types.ts +16 -0
  87. package/src/core/schema/index.ts +26 -0
  88. package/src/core/schema/migration-runner.ts +207 -0
  89. package/src/core/schema/migrations/001-init-trajectory.ts +211 -0
  90. package/src/core/schema/migrations/002-init-central.ts +122 -0
  91. package/src/core/schema/migrations/003-init-workflow.ts +55 -0
  92. package/src/core/schema/migrations/004-add-thinking-and-gfi.ts +74 -0
  93. package/src/core/schema/migrations/index.ts +31 -0
  94. package/src/core/schema/schema-definitions.ts +650 -0
  95. package/src/core/session-tracker.ts +6 -4
  96. package/src/core/shadow-observation-registry.ts +6 -3
  97. package/src/core/system-logger.ts +2 -2
  98. package/src/core/thinking-models.ts +182 -46
  99. package/src/core/thinking-os-parser.ts +156 -0
  100. package/src/core/training-program.ts +7 -7
  101. package/src/core/trajectory.ts +42 -36
  102. package/src/core/workspace-context.ts +77 -11
  103. package/src/core/workspace-dir-validation.ts +152 -0
  104. package/src/hooks/AGENTS.md +31 -0
  105. package/src/hooks/bash-risk.ts +3 -1
  106. package/src/hooks/edit-verification.ts +9 -5
  107. package/src/hooks/gate-block-helper.ts +5 -1
  108. package/src/hooks/gate.ts +152 -5
  109. package/src/hooks/gfi-gate.ts +9 -2
  110. package/src/hooks/lifecycle-routing.ts +124 -0
  111. package/src/hooks/lifecycle.ts +12 -12
  112. package/src/hooks/llm.ts +17 -109
  113. package/src/hooks/message-sanitize.ts +5 -3
  114. package/src/hooks/pain.ts +19 -15
  115. package/src/hooks/progressive-trust-gate.ts +7 -1
  116. package/src/hooks/prompt.ts +169 -60
  117. package/src/hooks/subagent.ts +5 -4
  118. package/src/hooks/thinking-checkpoint.ts +2 -0
  119. package/src/hooks/trajectory-collector.ts +15 -12
  120. package/src/http/principles-console-route.ts +31 -68
  121. package/src/i18n/commands.ts +2 -2
  122. package/src/index.ts +130 -40
  123. package/src/service/central-database.ts +131 -43
  124. package/src/service/central-health-service.ts +47 -0
  125. package/src/service/central-overview-service.ts +135 -0
  126. package/src/service/central-sync-service.ts +87 -0
  127. package/src/service/control-ui-query-service.ts +46 -36
  128. package/src/service/event-log-auditor.ts +261 -0
  129. package/src/service/evolution-query-service.ts +23 -22
  130. package/src/service/evolution-worker.ts +565 -261
  131. package/src/service/health-query-service.ts +213 -36
  132. package/src/service/nocturnal-runtime.ts +8 -4
  133. package/src/service/nocturnal-service.ts +503 -59
  134. package/src/service/nocturnal-target-selector.ts +5 -7
  135. package/src/service/runtime-summary-service.ts +2 -1
  136. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +25 -336
  137. package/src/service/subagent-workflow/dynamic-timeout.ts +30 -0
  138. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +48 -386
  139. package/src/service/subagent-workflow/index.ts +2 -0
  140. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +169 -284
  141. package/src/service/subagent-workflow/runtime-direct-driver.ts +114 -16
  142. package/src/service/subagent-workflow/subagent-error-utils.ts +25 -0
  143. package/src/service/subagent-workflow/types.ts +9 -4
  144. package/src/service/subagent-workflow/workflow-manager-base.ts +573 -0
  145. package/src/service/subagent-workflow/workflow-store.ts +71 -11
  146. package/src/service/trajectory-service.ts +2 -1
  147. package/src/tools/critique-prompt.ts +1 -1
  148. package/src/tools/deep-reflect.ts +175 -209
  149. package/src/tools/model-index.ts +2 -1
  150. package/src/types/event-types.ts +2 -2
  151. package/src/types/principle-tree-schema.ts +29 -23
  152. package/src/utils/file-lock.ts +5 -3
  153. package/src/utils/io.ts +5 -2
  154. package/src/utils/nlp.ts +5 -46
  155. package/src/utils/node-vm-polyfill.ts +11 -0
  156. package/src/utils/plugin-logger.ts +2 -0
  157. package/src/utils/retry.ts +572 -0
  158. package/src/utils/subagent-probe.ts +1 -1
  159. package/templates/langs/en/core/AGENTS.md +0 -13
  160. package/templates/langs/en/core/SOUL.md +1 -31
  161. package/templates/langs/en/core/TOOLS.md +0 -4
  162. package/templates/langs/en/principles/THINKING_OS.md +77 -0
  163. package/templates/langs/en/skills/admin/SKILL.md +0 -1
  164. package/templates/langs/en/skills/evolution-framework-update/SKILL.md +1 -1
  165. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +18 -5
  166. package/templates/langs/zh/core/AGENTS.md +0 -22
  167. package/templates/langs/zh/core/SOUL.md +1 -31
  168. package/templates/langs/zh/core/TOOLS.md +0 -4
  169. package/templates/langs/zh/principles/THINKING_OS.md +77 -0
  170. package/templates/langs/zh/skills/admin/SKILL.md +0 -1
  171. package/templates/langs/zh/skills/evolution-framework-update/SKILL.md +1 -1
  172. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +25 -4
  173. package/tests/commands/evolution-status.test.ts +119 -0
  174. package/tests/commands/implementation-lifecycle.test.ts +362 -0
  175. package/tests/core/code-implementation-storage.test.ts +398 -0
  176. package/tests/core/evolution-reducer.detector-metadata.test.ts +28 -28
  177. package/tests/core/nocturnal-artifact-lineage.test.ts +53 -0
  178. package/tests/core/nocturnal-artificer.test.ts +241 -0
  179. package/tests/core/nocturnal-compliance-p-principles.test.ts +133 -0
  180. package/tests/core/nocturnal-rule-implementation-validator.test.ts +127 -0
  181. package/tests/core/pd-task-store.test.ts +126 -0
  182. package/tests/core/principle-internalization/deprecated-readiness.test.ts +193 -0
  183. package/tests/core/principle-internalization/internalization-routing-policy.test.ts +212 -0
  184. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +350 -0
  185. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +211 -0
  186. package/tests/core/principle-training-state.test.ts +228 -1
  187. package/tests/core/principle-tree-ledger.test.ts +423 -0
  188. package/tests/core/regression-v1-9-1.test.ts +265 -0
  189. package/tests/core/replay-engine.test.ts +234 -0
  190. package/tests/core/rule-host-helpers.test.ts +120 -0
  191. package/tests/core/rule-host.test.ts +389 -0
  192. package/tests/core/rule-implementation-runtime.test.ts +64 -0
  193. package/tests/core/workspace-context.test.ts +53 -0
  194. package/tests/core/workspace-dir-validation.test.ts +272 -0
  195. package/tests/hooks/gate-rule-host-pipeline.test.ts +385 -0
  196. package/tests/hooks/pain.test.ts +74 -10
  197. package/tests/hooks/prompt.test.ts +63 -1
  198. package/tests/integration/principle-lifecycle.e2e.test.ts +197 -0
  199. package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +211 -0
  200. package/tests/service/data-endpoints-regression.test.ts +834 -0
  201. package/tests/service/evolution-worker.test.ts +0 -123
  202. package/tests/service/nocturnal-service-code-candidate.test.ts +330 -0
  203. package/tests/utils/nlp.test.ts +1 -19
  204. package/tests/utils/retry.test.ts +327 -0
  205. package/ui/src/App.tsx +1 -1
  206. package/ui/src/api.ts +4 -0
  207. package/ui/src/charts.tsx +366 -0
  208. package/ui/src/components/WorkspaceConfig.tsx +107 -75
  209. package/ui/src/i18n/ui.ts +89 -31
  210. package/ui/src/pages/EvolutionPage.tsx +1 -1
  211. package/ui/src/pages/OverviewPage.tsx +441 -81
  212. package/ui/src/pages/ThinkingModelsPage.tsx +287 -69
  213. package/ui/src/styles.css +43 -0
  214. package/ui/src/types.ts +17 -1
  215. package/src/agents/nocturnal-dreamer.md +0 -152
  216. package/src/agents/nocturnal-philosopher.md +0 -138
  217. package/src/agents/nocturnal-reflector.md +0 -126
  218. package/src/agents/nocturnal-scribe.md +0 -164
  219. package/templates/workspace/.principles/00-kernel.md +0 -51
  220. package/templates/workspace/.principles/DECISION_POLICY.json +0 -44
  221. package/templates/workspace/.principles/PRINCIPLES.md +0 -20
  222. package/templates/workspace/.principles/PROFILE.json +0 -54
  223. package/templates/workspace/.principles/PROFILE.schema.json +0 -56
  224. package/templates/workspace/.principles/THINKING_OS.md +0 -64
  225. package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +0 -7
  226. package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +0 -9
  227. package/templates/workspace/.principles/models/_INDEX.md +0 -27
  228. package/templates/workspace/.principles/models/first_principles.md +0 -62
  229. package/templates/workspace/.principles/models/marketing_4p.md +0 -52
  230. package/templates/workspace/.principles/models/porter_five.md +0 -63
  231. package/templates/workspace/.principles/models/swot.md +0 -60
  232. package/templates/workspace/.principles/models/user_story_map.md +0 -63
  233. package/templates/workspace/.state/WORKBOARD.json +0 -4
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Rollback Implementation Command — CLI `/pd-rollback-impl`
3
+ * ==========================================================
4
+ *
5
+ * PURPOSE: Revert the current active implementation and restore the previous one.
6
+ *
7
+ * FLOW:
8
+ * 1. List active implementations
9
+ * 2. On confirm: current -> disabled, previous -> active
10
+ *
11
+ * SAFETY:
12
+ * - If no previous active: current -> disabled (degrades to hard-boundary gates)
13
+ * - Rollback records persisted with full audit trail
14
+ */
15
+
16
+ import * as fs from 'fs';
17
+ import * as path from 'path';
18
+ import { withLock } from '../utils/file-lock.js';
19
+ import { WorkspaceContext } from '../core/workspace-context.js';
20
+ import { refreshPrincipleLifecycle } from '../core/principle-internalization/lifecycle-refresh.js';
21
+ import {
22
+ loadLedger,
23
+ transitionImplementationState,
24
+ } from '../core/principle-tree-ledger.js';
25
+ import type { Implementation } from '../types/principle-tree-schema.js';
26
+ import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
27
+
28
+ /**
29
+ * Get all implementations from the ledger.
30
+ */
31
+ function getAllImplementations(stateDir: string): Implementation[] {
32
+ const ledger = loadLedger(stateDir);
33
+ return Object.values(ledger.tree.implementations);
34
+ }
35
+
36
+ /**
37
+ * Handle the /pd-rollback-impl command.
38
+ *
39
+ * Usage:
40
+ * /pd-rollback-impl list - List active implementations
41
+ * /pd-rollback-impl <implId> - Rollback current active
42
+ * /pd-rollback-impl <implId> --reason "<reason>" - Rollback with reason
43
+ */
44
+ export function handleRollbackImplCommand(ctx: PluginCommandContext): PluginCommandResult {
45
+ const workspaceDir = (ctx.config?.workspaceDir as string) || process.cwd();
46
+ const {stateDir} = WorkspaceContext.fromHookContext({ ...ctx, workspaceDir });
47
+ const lang = (ctx.config?.language as string) || 'en';
48
+ const isZh = lang === 'zh';
49
+
50
+ const args = (ctx.args || '').trim();
51
+
52
+ // Parse args
53
+ const subcommand = args.split(/\s+/)[0] || '';
54
+ const implId = subcommand === 'list' ? '' : subcommand;
55
+ const reasonMatch = (/--reason\s+"([^"]+)"/.exec(args)) || (/--reason\s+(\S+)/.exec(args));
56
+ const reason = reasonMatch ? reasonMatch[1] : null;
57
+
58
+ // List active
59
+ if (subcommand === 'list' || subcommand === '') {
60
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: Mutual recursion between helper functions - reordering would break logical grouping
61
+ return _handleListActiveRollback(stateDir, isZh);
62
+ }
63
+
64
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: Mutual recursion between helper functions - reordering would break logical grouping
65
+ return _handleRollbackImpl(workspaceDir, stateDir, implId, reason, isZh, ctx.sessionId);
66
+ }
67
+
68
+ function _handleListActiveRollback(
69
+ stateDir: string,
70
+ isZh: boolean
71
+ ): PluginCommandResult {
72
+ const allImpls = getAllImplementations(stateDir);
73
+ const activeImpls = allImpls.filter(
74
+ (impl) => impl.lifecycleState === 'active'
75
+ );
76
+
77
+ if (activeImpls.length === 0) {
78
+ return {
79
+ text: isZh
80
+ ? '\n\u2139\ufe0f \u6ca1\u6709\u6d3b\u8dc3\u5b9e\u73b0\u53ef\u56de\u6eda\u3002'
81
+ : '\n\u2139\ufe0f No active implementations to rollback.',
82
+ };
83
+ }
84
+
85
+ let output = isZh ? '\n\ud83d\udccb \u53ef\u56de\u6eda\u6d3b\u8dc3\u5b9e\u73b0\n' : '\n\ud83d\udccb Active Implementations Available for Rollback\n';
86
+ output += `${'='.repeat(50)}\n`;
87
+
88
+ for (const impl of activeImpls) {
89
+ const hasPrevious = impl.previousActive ? '\u2705' : '\u26a0\ufe0f \u65e0\u524d\u4e00\u4e2a';
90
+ output += ` ${impl.id}\n`;
91
+ output += ` Rule: ${impl.ruleId} | Version: ${impl.version}\n`;
92
+ output += ` Has previous: ${hasPrevious}\n`;
93
+ if (impl.previousActive) {
94
+ output += ` Previous: ${impl.previousActive}\n`;
95
+ }
96
+ output += '\n';
97
+ }
98
+
99
+ output += isZh
100
+ ? '\u7528\u6cd5: /pd-rollback-impl <implId> --reason "\u539f\u56e0"'
101
+ : 'Usage: /pd-rollback-impl <implId> --reason "reason"';
102
+
103
+ return { text: output };
104
+ }
105
+
106
+ // eslint-disable-next-line @typescript-eslint/max-params -- Reason: Command handler signature must match OpenClaw plugin interface - breaking API change to options objects would affect public contracts
107
+ function _handleRollbackImpl(
108
+ workspaceDir: string,
109
+ stateDir: string,
110
+ implId: string,
111
+ reason: string | null,
112
+ isZh: boolean,
113
+ sessionId?: string,
114
+ ): PluginCommandResult {
115
+ const allImpls = getAllImplementations(stateDir);
116
+ const currentActive = allImpls.find((i) => i.id === implId);
117
+
118
+ if (!currentActive) {
119
+ return {
120
+ text: isZh
121
+ ? `\u274c \u672a\u627e\u5230\u5b9e\u73b0: ${implId}`
122
+ : `\u274c Implementation not found: ${implId}`,
123
+ };
124
+ }
125
+
126
+ const currentState = currentActive.lifecycleState;
127
+ if (currentState !== 'active') {
128
+ return {
129
+ text: isZh
130
+ ? `\u274c \u5b9e\u73b0 ${implId} \u5f53\u524d\u72b6\u6001: ${currentState}\n\u53ea\u80fd\u56de\u6eda\u6d3b\u8dc3\u5b9e\u73b0\u3002`
131
+ : `\u274c Implementation ${implId} is in state: ${currentState}\nCan only rollback active implementations.`,
132
+ };
133
+ }
134
+
135
+ const previousActiveId = currentActive.previousActive;
136
+ const reasonText = reason || (isZh ? '\u7528\u6237\u624b\u52a8\u56de\u6eda' : 'User manual rollback');
137
+
138
+ // Step 1: Current active -> disabled
139
+ transitionImplementationState(stateDir, implId, 'disabled');
140
+
141
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- Reason: assigned immediately in if/else branches before use
142
+ let restoredMessage: string;
143
+
144
+ if (previousActiveId && allImpls.some((i) => i.id === previousActiveId)) {
145
+ // Step 2: Restore previous active -> active
146
+ transitionImplementationState(stateDir, previousActiveId, 'active');
147
+
148
+ restoredMessage = isZh
149
+ ? `\n \u5df2\u6062\u590d\u524d\u4e00\u4e2a\u6d3b\u8dc3\u5b9e\u73b0: ${previousActiveId}\n \u72b6\u6001: disabled -> active`
150
+ : `\n Restored previous active implementation: ${previousActiveId}\n State: disabled -> active`;
151
+ } else {
152
+ // No previous active — degrade to hard-boundary gates (per Phase 12 D-08)
153
+ restoredMessage = isZh
154
+ ? `\n \u26a0\ufe0f \u6ca1\u6709\u524d\u4e00\u4e2a\u6d3b\u8dc3\u5b9e\u73b0\uff0c\u8be5\u89c4\u5219\u6ca1\u6709\u6d3b\u8dc3\u5b9e\u73b0\u3002`
155
+ + `\n GFI\u3001Progressive Gate\u7b49\u786c\u56fa\u62b1\u95e8\u4ecd\u7136\u6b63\u5e38\u8fd0\u884c\u3002`
156
+ : `\n \u26a0\ufe0f No previous active implementation. Rule has no active code implementation.`
157
+ + ` Existing hard-boundary gates (GFI, Progressive Gate) continue functioning normally.`;
158
+ }
159
+
160
+ // Store rollback record
161
+ const rollbackDir = path.join(
162
+ stateDir,
163
+ 'principles',
164
+ 'implementations',
165
+ implId,
166
+ 'rollbacks'
167
+ );
168
+ if (!fs.existsSync(rollbackDir)) {
169
+ fs.mkdirSync(rollbackDir, { recursive: true });
170
+ }
171
+
172
+ const rollbackRecord = {
173
+ rolledBackBy: sessionId || 'manual',
174
+ rolledBackAt: new Date().toISOString(),
175
+ reason: reasonText,
176
+ previousImplementationId: previousActiveId || null,
177
+ restoredImplementationId: previousActiveId && allImpls.some((i) => i.id === previousActiveId)
178
+ ? previousActiveId
179
+ : null,
180
+ rolledBackImplId: implId,
181
+ };
182
+
183
+ const rollbackTimestamp = new Date().toISOString().replace(/[:.]/g, '-');
184
+ const rollbackPath = path.join(rollbackDir, `${rollbackTimestamp}.json`);
185
+ withLock(rollbackPath, () => {
186
+ fs.writeFileSync(rollbackPath, JSON.stringify(rollbackRecord, null, 2), 'utf-8');
187
+ });
188
+ refreshPrincipleLifecycle(workspaceDir, stateDir);
189
+
190
+ let output = isZh
191
+ ? `\n\u2705 \u56de\u6eda\u5b8c\u6210: ${implId}\n \u72b6\u6001: active -> disabled\n \u539f\u56e0: ${reasonText}`
192
+ : `\n\u2705 Rollback complete: ${implId}\n State: active -> disabled\n Reason: ${reasonText}`;
193
+
194
+ output += restoredMessage;
195
+
196
+ return { text: output };
197
+ }
198
+
199
+ /**
200
+ * Handle natural language rollback request.
201
+ * Detects phrases like "回滚这个规则实现", "rollback this implementation".
202
+ */
203
+ export function handleNaturalLanguageRollbackImpl(
204
+ workspaceDir: string,
205
+ sessionId: string | undefined,
206
+ reason: string
207
+ ): { success: boolean; message: string } {
208
+ const isZh = (/[\u4e00-\u9fff]/.exec(reason)) || false;
209
+ const {stateDir} = WorkspaceContext.fromHookContext({ workspaceDir });
210
+
211
+ if (!sessionId) {
212
+ return {
213
+ success: false,
214
+ message: isZh ? '\u65e0\u6cd5\u8bc6\u522b\u5f53\u524d\u4f1a\u8bdd' : 'Session not found',
215
+ };
216
+ }
217
+
218
+ // Natural language entry: get last active implementation
219
+ const allImpls = getAllImplementations(stateDir);
220
+ const lastActive = allImpls.find((i) => i.lifecycleState === 'active');
221
+
222
+ if (!lastActive) {
223
+ return {
224
+ success: false,
225
+ message: isZh ? '\u5f53\u524d\u6ca1\u6709\u6d3b\u8dc3\u5b9e\u73b0' : 'No active implementations',
226
+ };
227
+ }
228
+
229
+ // Would be called via the hook — return the impl ID for the hook to route
230
+ return {
231
+ success: true,
232
+ message: `${lastActive.id}|${reason}`,
233
+ };
234
+ }
@@ -15,7 +15,8 @@ export function handleRollbackCommand(ctx: PluginCommandContext): PluginCommandR
15
15
  const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
16
16
  const lang = (ctx.config?.language as string) || 'en';
17
17
  const isZh = lang === 'zh';
18
- const sessionId = (ctx as any).sessionId;
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: sessionId injected by OpenClaw plugin framework - type not available in PluginCommandContext
19
+ const {sessionId} = (ctx as any);
19
20
 
20
21
  const args = (ctx.args || '').trim();
21
22
 
@@ -43,8 +44,10 @@ Usage:
43
44
  };
44
45
  }
45
46
 
46
- let eventId: string | null = null;
47
- let triggerMethod: 'user_command' = 'user_command';
47
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- Reason: assigned immediately in if/else branches before use
48
+ let eventId: string | null;
49
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: triggerMethod is reserved for future extension - tracking rollback trigger source
50
+ const _triggerMethod = 'user_command' as const;
48
51
 
49
52
  if (args === 'last') {
50
53
  // Find the last empathy event in current session
@@ -29,9 +29,11 @@ export function handleSamplesCommand(ctx: PluginCommandContext): PluginCommandRe
29
29
  }
30
30
  const normalizedDecision = decision === 'approve' ? 'approved' : 'rejected';
31
31
  const note = noteParts.join(' ').trim();
32
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in try block, catch has early return
32
33
  let record;
33
34
  try {
34
35
  record = wctx.trajectory.reviewCorrectionSample(sampleId, normalizedDecision, note);
36
+ /* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: error handling only - returning failure response */
35
37
  } catch (error) {
36
38
  return {
37
39
  text: zh
@@ -1,5 +1,4 @@
1
1
  import * as fs from 'fs';
2
- import * as path from 'path';
3
2
  import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
4
3
  import { WorkspaceContext } from '../core/workspace-context.js';
5
4
 
@@ -16,7 +15,7 @@ function getModels(wctx: WorkspaceContext): Record<string, string> {
16
15
  const content = fs.readFileSync(modelsPath, 'utf8');
17
16
  const lines = content.split('\n');
18
17
  for (const line of lines) {
19
- const match = line.match(/^###\s*(T-\d+):\s*(.*)/);
18
+ const match = /^###\s*(T-\d+):\s*(.*)/.exec(line);
20
19
  if (match) {
21
20
  models[match[1]] = match[2].trim();
22
21
  }
@@ -36,7 +35,7 @@ function formatUsageReport(wctx: WorkspaceContext): string {
36
35
 
37
36
  try {
38
37
  const usage: Record<string, number> = JSON.parse(fs.readFileSync(logPath, 'utf8'));
39
- const totalTurns = usage['_total_turns'] || 1;
38
+ const totalTurns = usage._total_turns || 1;
40
39
  const models = getModels(wctx);
41
40
 
42
41
  let report = `# 🧠 Thinking OS — Usage Report\n\n`;
@@ -121,7 +120,7 @@ function formatAuditReport(wctx: WorkspaceContext): string {
121
120
 
122
121
  try {
123
122
  const usage: Record<string, number> = JSON.parse(fs.readFileSync(logPath, 'utf8'));
124
- const totalTurns = usage['_total_turns'] || 1;
123
+ const totalTurns = usage._total_turns || 1;
125
124
 
126
125
  const overused: string[] = [];
127
126
  const underused: string[] = [];
@@ -20,6 +20,7 @@ function formatState(state: string): string {
20
20
  return `${icon} ${state}`;
21
21
  }
22
22
 
23
+ // eslint-disable-next-line @typescript-eslint/max-params -- Reason: debug output builder requires all context parameters - refactoring would break API
23
24
  function buildOutput(
24
25
  workflowId: string,
25
26
  summary: ReturnType<InstanceType<typeof WorkflowStore>['getWorkflow']>,
@@ -87,7 +88,7 @@ export function handleWorkflowDebugCommand(
87
88
 
88
89
  // Parse workflow ID from args
89
90
  const args = (ctx as { args?: string }).args?.trim() || '';
90
- const workflowId = args.split(/\s+/)[0];
91
+ const [workflowId] = args.split(/\s+/);
91
92
 
92
93
  if (!workflowId) {
93
94
  return {
@@ -15,6 +15,7 @@
15
15
  export class PdError extends Error {
16
16
  constructor(
17
17
  message: string,
18
+ // eslint-disable-next-line no-unused-vars -- public parameter property, accessed externally
18
19
  public readonly code: string,
19
20
  options?: { cause?: unknown }
20
21
  ) {
@@ -0,0 +1,34 @@
1
+ # src/core/ — Domain Core
2
+
3
+ **27 TypeScript files.** Trust engine, evolution pipeline, pain calculation, config, trajectory DB, event log, hygiene tracking.
4
+
5
+ ## WHERE TO LOOK
6
+
7
+ | Task | File | Notes |
8
+ |------|------|-------|
9
+ | Trust permissions | `trust-engine.ts` | 4-stage model, score floor=30, cold-start grace |
10
+ | Evolution points | `evolution-engine.ts` | 5-tier EP system (Seed→Forest), double-reward after recovery |
11
+ | Principle lifecycle | `evolution-reducer.ts` | Event sourcing → `evolution.jsonl` stream |
12
+ | Pain scoring | `pain.ts` | Tool failures + gate blocks → pain score |
13
+ | Config management | `config.ts`, `config-service.ts` | PainConfig, dot-notation `get()`, singleton factory |
14
+ | Trajectory analytics | `trajectory.ts` | SQLite (better-sqlite3), sessions/turns/tool_calls |
15
+ | Event logging | `event-log.ts` | JSONL buffered writes (20 entries or 30s flush) |
16
+ | Pain patterns | `dictionary.ts`, `dictionary-service.ts` | Regex + exact_match rules |
17
+ | Detection funnel | `detection-funnel.ts`, `detection-service.ts` | Text input queue |
18
+ | Session state | `session-tracker.ts` | GFI, token usage, stuck loop detection |
19
+ | Central facade | `workspace-context.ts` | `WorkspaceContext.fromHookContext(ctx)` — all services flow through here |
20
+ | Path resolution | `paths.ts`, `path-resolver.ts` | `api.resolvePath()` is the only compliant entry |
21
+
22
+ ## CONVENTIONS
23
+
24
+ - **Singleton factory**: `XxxService.get(stateDir)` — cached per stateDir
25
+ - **WorkspaceContext facade**: lazy-initializes config, trust, eventLog, dictionary, hygiene, evolutionReducer, trajectory
26
+ - **Event sourcing**: EvolutionReducerImpl appends events → in-memory state update
27
+ - **File locking**: `withLock()` / `withLockAsync()` for critical state (evolution, trajectory, trust)
28
+ - **Buffered flush**: EventLog batches 20 entries or flushes every 30s
29
+
30
+ ## ANTI-PATTERNS
31
+
32
+ - ❌ Never bypass WorkspaceContext — always go through the facade
33
+ - ❌ Never write to `.state/` files directly — use the service layer
34
+ - ❌ Never hardcode paths — use `paths.ts` / `path-resolver.ts`
@@ -300,6 +300,7 @@ export function getEffectiveThresholds(stateDir: string): ThresholdValues {
300
300
  * @param reason - Reason for the adjustment (required for tracking)
301
301
  * @returns UpdateThresholdResult
302
302
  */
303
+ // eslint-disable-next-line @typescript-eslint/max-params -- Reason: threshold state update requires all parameters - refactoring would break API
303
304
  export function updateThresholdState(
304
305
  stateDir: string,
305
306
  thresholdName: ThresholdName,
@@ -439,7 +440,7 @@ export function adjustThresholdsFromSignals(
439
440
  currentThresholds.principleAlignmentMin + adjustment,
440
441
  `High arbiter reject rate (${signals.arbiterRejectRate.toFixed(2)}) → tightening alignment threshold`
441
442
  );
442
- if (result.changed && (!bestResult.changed || (result.newValue! - result.oldValue!) > 0)) {
443
+ if (result.changed && (!bestResult.changed || ((result.newValue! - result.oldValue!) > 0))) { // eslint-disable-line @typescript-eslint/no-non-null-assertion -- Reason: changed flag guarantees newValue/oldValue are defined
443
444
  bestResult = result;
444
445
  }
445
446
  }
@@ -453,7 +454,7 @@ export function adjustThresholdsFromSignals(
453
454
  currentThresholds.executabilityMin + adjustment,
454
455
  `High executability reject rate (${signals.executabilityRejectRate.toFixed(2)}) → tightening executability threshold`
455
456
  );
456
- if (result.changed && (!bestResult.changed || (result.newValue! - result.oldValue!) > 0)) {
457
+ if (result.changed && (!bestResult.changed || ((result.newValue! - result.oldValue!) > 0))) { // eslint-disable-line @typescript-eslint/no-non-null-assertion -- Reason: changed flag guarantees newValue/oldValue are defined
457
458
  bestResult = result;
458
459
  }
459
460
  }
@@ -467,7 +468,7 @@ export function adjustThresholdsFromSignals(
467
468
  Math.max(currentThresholds.aggregateMin - reward, THRESHOLD_MIN),
468
469
  `Positive quality delta (${signals.qualityDelta.toFixed(2)}) → rewarding with slightly lower aggregate threshold`
469
470
  );
470
- if (result.changed && (!bestResult.changed || (result.oldValue! - result.newValue!) > 0)) {
471
+ if (result.changed && (!bestResult.changed || ((result.oldValue! - result.newValue!) > 0))) { // eslint-disable-line @typescript-eslint/no-non-null-assertion -- Reason: changed flag guarantees newValue/oldValue are defined
471
472
  bestResult = result;
472
473
  }
473
474
  }
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Code Implementation Asset Storage
3
+ * ================================
4
+ *
5
+ * Manages versioned code implementation assets: manifests, entry files,
6
+ * and metadata pointers for Implementation(type=code) records.
7
+ *
8
+ * DESIGN CONSTRAINTS (per D-09 through D-12):
9
+ * - Manifest is loading metadata, NOT the source of truth for lifecycle state (D-11)
10
+ * - The Principle Tree ledger remains canonical for lifecycle and relationships (D-11)
11
+ * - Asset root follows the PD stateDir convention:
12
+ * {stateDir}/principles/implementations/{implId}/
13
+ * - All writes use withLock for atomicity (matching ledger pattern)
14
+ * - This module does NOT implement replay execution, evaluation report generation,
15
+ * or promotion logic (D-12)
16
+ */
17
+
18
+ import * as fs from 'fs';
19
+ import * as path from 'path';
20
+ import { withLock } from '../utils/file-lock.js';
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Types
24
+ // ---------------------------------------------------------------------------
25
+
26
+ /**
27
+ * Manifest shape for a code implementation's filesystem assets.
28
+ * Subordinate to the ledger per D-11: does NOT store lifecycleState.
29
+ */
30
+ export interface CodeImplementationManifest {
31
+ /** Asset version (distinct from Implementation.version in the ledger) */
32
+ version: string;
33
+ /** Relative filename of the entry point (e.g., 'entry.js') */
34
+ entryFile: string;
35
+ /** ISO timestamp of initial creation */
36
+ createdAt: string;
37
+ /** ISO timestamp of last manifest update */
38
+ updatedAt: string;
39
+ /** Fingerprints of samples this implementation was tested against */
40
+ replaySampleRefs: string[];
41
+ /** Relative path to most recent eval report, or null */
42
+ lastEvalReportRef: string | null;
43
+ /** Provenance carried with the generated candidate assets */
44
+ lineage?: CodeImplementationLineageMetadata;
45
+ }
46
+
47
+ export interface CodeImplementationLineageMetadata {
48
+ principleId: string;
49
+ ruleId: string;
50
+ sourceSnapshotRef: string;
51
+ sourcePainIds: string[];
52
+ sourceGateBlockIds: string[];
53
+ sourceSessionId: string;
54
+ artificerArtifactId: string;
55
+ }
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Constants
59
+ // ---------------------------------------------------------------------------
60
+
61
+ const MANIFEST_FILENAME = 'manifest.json';
62
+ const ENTRY_FILENAME = 'entry.js';
63
+ const REPLAYS_DIRNAME = 'replays';
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Path validation
67
+ // ---------------------------------------------------------------------------
68
+
69
+ /**
70
+ * Validate that an implId contains no path separators.
71
+ * implId comes from ledger record IDs (controlled namespace), but we
72
+ * validate to prevent path traversal (T-12-08).
73
+ */
74
+ function validateImplId(implId: string): void {
75
+ if (implId.includes('/') || implId.includes('\\') || implId.includes('..')) {
76
+ throw new Error(`Invalid implementation ID: "${implId}" contains path separators`);
77
+ }
78
+ if (!implId) {
79
+ throw new Error('Implementation ID must not be empty');
80
+ }
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Helpers
85
+ // ---------------------------------------------------------------------------
86
+
87
+ function ensureDir(dir: string): void {
88
+ if (!fs.existsSync(dir)) {
89
+ fs.mkdirSync(dir, { recursive: true });
90
+ }
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Public API
95
+ // ---------------------------------------------------------------------------
96
+
97
+ /**
98
+ * Get the absolute path for an implementation's asset root.
99
+ * Convention: {stateDir}/principles/implementations/{implId}/
100
+ */
101
+ export function getImplementationAssetRoot(stateDir: string, implId: string): string {
102
+ validateImplId(implId);
103
+ return path.join(stateDir, 'principles', 'implementations', implId);
104
+ }
105
+
106
+ /**
107
+ * Load manifest from disk. Returns null if not found (does not throw).
108
+ */
109
+ export function loadManifest(stateDir: string, implId: string): CodeImplementationManifest | null {
110
+ validateImplId(implId);
111
+ const manifestPath = path.join(getImplementationAssetRoot(stateDir, implId), MANIFEST_FILENAME);
112
+ if (!fs.existsSync(manifestPath)) return null;
113
+ try {
114
+ return JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) as CodeImplementationManifest;
115
+ } catch {
116
+ return null;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Write manifest atomically using withLock.
122
+ */
123
+ export function writeManifest(
124
+ stateDir: string,
125
+ implId: string,
126
+ manifest: CodeImplementationManifest,
127
+ ): void {
128
+ validateImplId(implId);
129
+ const assetRoot = getImplementationAssetRoot(stateDir, implId);
130
+ const manifestPath = path.join(assetRoot, MANIFEST_FILENAME);
131
+ ensureDir(assetRoot);
132
+ withLock(manifestPath, () => {
133
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
134
+ });
135
+ }
136
+
137
+ // eslint-disable-next-line @typescript-eslint/max-params -- Reason: entry source writer requires state + id + source - refactoring would break API
138
+ export function writeEntrySource(
139
+ stateDir: string,
140
+ implId: string,
141
+ sourceCode: string,
142
+ entryFile = ENTRY_FILENAME,
143
+ ): void {
144
+ validateImplId(implId);
145
+ const assetRoot = getImplementationAssetRoot(stateDir, implId);
146
+ const entryPath = path.join(assetRoot, entryFile);
147
+ ensureDir(assetRoot);
148
+ withLock(entryPath, () => {
149
+ fs.writeFileSync(entryPath, sourceCode, 'utf-8');
150
+ });
151
+ }
152
+
153
+ export function deleteImplementationAssetDir(stateDir: string, implId: string): void {
154
+ validateImplId(implId);
155
+ const assetRoot = getImplementationAssetRoot(stateDir, implId);
156
+ if (!fs.existsSync(assetRoot)) {
157
+ return;
158
+ }
159
+ withLock(assetRoot, () => {
160
+ fs.rmSync(assetRoot, { recursive: true, force: true });
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Load the entry source code from disk.
166
+ * Returns null if manifest doesn't exist or entry file is missing.
167
+ */
168
+ export function loadEntrySource(stateDir: string, implId: string): string | null {
169
+ validateImplId(implId);
170
+ const manifest = loadManifest(stateDir, implId);
171
+ if (!manifest) return null;
172
+ const entryPath = path.join(getImplementationAssetRoot(stateDir, implId), manifest.entryFile);
173
+ if (!fs.existsSync(entryPath)) return null;
174
+ try {
175
+ return fs.readFileSync(entryPath, 'utf-8');
176
+ } catch {
177
+ return null;
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Create the full asset directory structure for a new implementation.
183
+ *
184
+ * Creates:
185
+ * {implId}/ - asset root
186
+ * {implId}/entry.js - placeholder entry point (only if not already present)
187
+ * {implId}/manifest.json - asset manifest with version and timestamps
188
+ * {implId}/replays/ - empty directory for future replay reports
189
+ *
190
+ * Idempotent: calling again with the same implId will NOT overwrite an existing entry.js.
191
+ */
192
+ // eslint-disable-next-line @typescript-eslint/max-params -- Reason: asset dir creation requires state + id + version - refactoring would break API
193
+ export function createImplementationAssetDir(
194
+ stateDir: string,
195
+ implId: string,
196
+ version: string,
197
+ options: {
198
+ entrySource?: string;
199
+ lineage?: CodeImplementationLineageMetadata;
200
+ } = {},
201
+ ): CodeImplementationManifest {
202
+ validateImplId(implId);
203
+ const assetRoot = getImplementationAssetRoot(stateDir, implId);
204
+ const replaysDir = path.join(assetRoot, REPLAYS_DIRNAME);
205
+ const entryPath = path.join(assetRoot, ENTRY_FILENAME);
206
+ const entrySource =
207
+ options.entrySource ??
208
+ [
209
+ '// Code implementation entry point',
210
+ '// Exports: meta (RuleHostMeta), evaluate (input: RuleHostInput) => RuleHostResult',
211
+ '// This file will be replaced by nocturnal candidate generation (Phase 14)',
212
+ 'export const meta = { name: "placeholder", version: "0.0.1", ruleId: "", coversCondition: "" };',
213
+ 'export function evaluate(input) { return { decision: "allow", matched: false, reason: "placeholder" }; }',
214
+ ].join('\n');
215
+
216
+ withLock(assetRoot, () => {
217
+ ensureDir(assetRoot);
218
+ ensureDir(replaysDir);
219
+ if (!fs.existsSync(entryPath)) {
220
+ fs.writeFileSync(entryPath, entrySource, 'utf-8');
221
+ }
222
+ });
223
+
224
+ if (fs.existsSync(entryPath) && options.entrySource) {
225
+ writeEntrySource(stateDir, implId, options.entrySource);
226
+ }
227
+
228
+ const now = new Date().toISOString();
229
+ const manifest: CodeImplementationManifest = {
230
+ version,
231
+ entryFile: ENTRY_FILENAME,
232
+ createdAt: now,
233
+ updatedAt: now,
234
+ replaySampleRefs: [],
235
+ lastEvalReportRef: null,
236
+ ...(options.lineage ? { lineage: options.lineage } : {}),
237
+ };
238
+
239
+ writeManifest(stateDir, implId, manifest);
240
+ return manifest;
241
+ }
@@ -230,7 +230,7 @@ export const DEFAULT_SETTINGS: PainSettings = {
230
230
 
231
231
  export class PainConfig {
232
232
  private settings: PainSettings = { ...DEFAULT_SETTINGS };
233
- private filePath: string;
233
+ private readonly filePath: string;
234
234
 
235
235
  constructor(stateDir: string) {
236
236
  this.filePath = path.join(stateDir, 'pain_settings.json');
@@ -242,7 +242,7 @@ export class PainConfig {
242
242
  const loaded = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
243
243
  this.settings = this.deepMerge(DEFAULT_SETTINGS, loaded);
244
244
  this.validate(this.settings);
245
- } catch (e) {
245
+ } catch {
246
246
  console.error('[PD] Failed to parse pain_settings.json, using defaults.');
247
247
  }
248
248
  } else {
@@ -264,6 +264,8 @@ export class PainConfig {
264
264
  }
265
265
  }
266
266
 
267
+ /* eslint-disable @typescript-eslint/no-explicit-any */
268
+ // Reason: deepMerge handles arbitrary nested object structures where static typing cannot precisely capture recursive object shapes
267
269
  private deepMerge(target: any, source: any): any {
268
270
  const output = { ...target };
269
271
  if (source && typeof source === 'object') {
@@ -288,6 +290,7 @@ export class PainConfig {
288
290
  /**
289
291
  * Basic validation for critical settings
290
292
  */
293
+ // eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: validate is a pure validation function that modifies settings in place, no this reference needed
291
294
  private validate(settings: PainSettings): void {
292
295
  // Ensure intervals are positive
293
296
  if (settings.intervals.worker_poll_ms < 1000) settings.intervals.worker_poll_ms = 15 * 60 * 1000;