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
@@ -27,7 +27,6 @@ import {
27
27
  getDatasetRecord,
28
28
  updateReviewStatus,
29
29
  updateTargetModelFamily,
30
- readDatasetArtifact,
31
30
  getDatasetStats,
32
31
  type NocturnalDatasetRecord,
33
32
  type NocturnalReviewStatus,
@@ -53,7 +52,7 @@ function formatRecord(record: NocturnalDatasetRecord, includeDetails = false): s
53
52
  lines.push(` createdAt: ${new Date(record.createdAt).toLocaleString('en-US')}`);
54
53
 
55
54
  if (includeDetails) {
56
- const artifactPath = record.artifactPath;
55
+ const {artifactPath} = record;
57
56
  if (fs.existsSync(artifactPath)) {
58
57
  try {
59
58
  const content = fs.readFileSync(artifactPath, 'utf-8');
@@ -122,7 +121,7 @@ export function handleNocturnalReviewCommand(ctx: PluginCommandContext): PluginC
122
121
  }
123
122
 
124
123
  if (action === 'show') {
125
- const fingerprint = parts[1];
124
+ const [, fingerprint] = parts;
126
125
  if (!fingerprint) {
127
126
  return {
128
127
  text: zh
@@ -149,7 +148,7 @@ export function handleNocturnalReviewCommand(ctx: PluginCommandContext): PluginC
149
148
  }
150
149
 
151
150
  if (action === 'approve') {
152
- const fingerprint = parts[1];
151
+ const [, fingerprint] = parts;
153
152
  const reason = parts.slice(2).join(' ') || (zh ? 'Approved by human reviewer' : 'Approved by human reviewer');
154
153
 
155
154
  if (!fingerprint) {
@@ -203,7 +202,7 @@ export function handleNocturnalReviewCommand(ctx: PluginCommandContext): PluginC
203
202
  }
204
203
 
205
204
  if (action === 'reject') {
206
- const fingerprint = parts[1];
205
+ const [, fingerprint] = parts;
207
206
  const reason = parts.slice(2).join(' ') || (zh ? 'Rejected by human reviewer' : 'Rejected by human reviewer');
208
207
 
209
208
  if (!fingerprint) {
@@ -249,8 +248,7 @@ export function handleNocturnalReviewCommand(ctx: PluginCommandContext): PluginC
249
248
  }
250
249
 
251
250
  if (action === 'set-family') {
252
- const fingerprint = parts[1];
253
- const family = parts[2];
251
+ const [, fingerprint, family] = parts;
254
252
 
255
253
  if (!fingerprint || !family) {
256
254
  return {
@@ -24,15 +24,12 @@
24
24
  * /nocturnal-rollout show-promotion <checkpointId>
25
25
  */
26
26
 
27
- import { WorkspaceContext } from '../core/workspace-context.js';
28
27
  import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
29
- import type { TrainableWorkerProfile } from '../core/external-training-contract.js';
30
28
  import {
31
29
  evaluatePromotionGate,
32
30
  advancePromotion,
33
31
  getPromotionRecord,
34
32
  listPromotionsByState,
35
- rejectCheckpoint,
36
33
  DEFAULT_BASELINE_METRICS,
37
34
  DEFAULT_MIN_DELTA,
38
35
  DEFAULT_ALLOWED_MARGIN,
@@ -44,9 +41,6 @@ import {
44
41
  disableRoutingForProfile,
45
42
  rollbackDeployment,
46
43
  getDeployment,
47
- getDeploymentLineage,
48
- listDeployments,
49
- getFullDeploymentRegistry,
50
44
  isRoutingEnabledForProfile,
51
45
  type WorkerProfile,
52
46
  } from '../core/model-deployment-registry.js';
@@ -61,7 +55,6 @@ import {
61
55
  } from '../core/shadow-observation-registry.js';
62
56
  import {
63
57
  getCheckpoint,
64
- listCheckpoints,
65
58
  } from '../core/model-training-registry.js';
66
59
 
67
60
  function isZh(ctx: PluginCommandContext): boolean {
@@ -473,10 +466,6 @@ Routing: ${deployment.routingEnabled ? 'Enabled' : 'Disabled'}`,
473
466
  ? getCheckpoint(workspaceDir, deployment.activeCheckpointId)
474
467
  : null;
475
468
 
476
- const lineage = deployment.activeCheckpointId
477
- ? getDeploymentLineage(workspaceDir, profile)
478
- : null;
479
-
480
469
  return {
481
470
  text: zh
482
471
  ? `=== ${profile} 部署状态 ===
@@ -583,7 +572,7 @@ ${promotion.previousPromotionId ? `Previous Promotion: ${promotion.previousPromo
583
572
 
584
573
  // ── List by State ─────────────────────────────────────────────────────
585
574
  if (subcommand === 'list-by-state') {
586
- const stateArg = parts[1];
575
+ const [, stateArg] = parts;
587
576
  if (!stateArg) {
588
577
  return { text: zh ? '错误: 需要状态参数' : 'Error: state argument required' };
589
578
  }
@@ -26,18 +26,13 @@ import * as path from 'path';
26
26
  import * as fs from 'fs';
27
27
  import { execFileSync, spawn } from 'child_process';
28
28
  import { fileURLToPath } from 'url';
29
- import { WorkspaceContext } from '../core/workspace-context.js';
30
29
  import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
31
30
  import {
32
31
  type TrainerBackendKind,
33
32
  type HardwareTier,
34
- generateExperimentId,
35
- computeDatasetFingerprint,
36
33
  } from '../core/external-training-contract.js';
37
34
  import {
38
35
  TrainingProgram,
39
- DEFAULT_ORPO_HYPERPARAMETERS,
40
- type CreateExperimentParams,
41
36
  } from '../core/training-program.js';
42
37
  import {
43
38
  listTrainingRuns,
@@ -48,20 +43,11 @@ import {
48
43
  getTrainingRegistryStats,
49
44
  } from '../core/model-training-registry.js';
50
45
  import { getDeployment } from '../core/model-deployment-registry.js';
51
- import {
52
- DEFAULT_MIN_DELTA,
53
- DEFAULT_ALLOWED_MARGIN,
54
- DEFAULT_BASELINE_METRICS,
55
- } from '../core/promotion-gate.js';
56
46
 
57
47
  function isZh(ctx: PluginCommandContext): boolean {
58
48
  return String(ctx.config?.language || 'en').startsWith('zh');
59
49
  }
60
50
 
61
- function zh(cond: string): string {
62
- return cond;
63
- }
64
-
65
51
  const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
66
52
  const REPO_ROOT = path.resolve(MODULE_DIR, '..', '..', '..', '..');
67
53
  const TRAINER_SCRIPTS_DIR = path.join(REPO_ROOT, 'scripts', 'nocturnal', 'trainer');
@@ -111,24 +97,6 @@ function formatTrainingRun(run: ReturnType<typeof getTrainingRun>, zh: boolean):
111
97
  return lines.join('\n ');
112
98
  }
113
99
 
114
- /**
115
- * Format checkpoint for display.
116
- */
117
- function formatCheckpoint(cp: ReturnType<typeof getCheckpoint>, zh: boolean): string {
118
- if (!cp) return zh ? '未找到' : 'Not found';
119
- const lines = [
120
- `ID: ${cp.checkpointId.substring(0, 8)}...`,
121
- `Family: ${cp.targetModelFamily}`,
122
- `Artifact: ${cp.artifactPath}`,
123
- `Deployable: ${cp.deployable ? (zh ? '是' : 'Yes') : (zh ? '否' : 'No')}`,
124
- `Created: ${new Date(cp.createdAt).toLocaleString()}`,
125
- ];
126
- if (cp.lastEvalSummaryRef) {
127
- lines.push(`Eval: ${cp.lastEvalSummaryRef.substring(0, 12)}...`);
128
- }
129
- return lines.join('\n ');
130
- }
131
-
132
100
  export async function handleNocturnalTrainCommand(ctx: PluginCommandContext): Promise<PluginCommandResult> {
133
101
  const workspaceDir = (ctx.config?.workspaceDir as string) || process.cwd();
134
102
  const zh = isZh(ctx);
@@ -261,7 +229,7 @@ Hardware tiers:
261
229
  : `Error: manifest missing datasetFingerprint: ${manifestPath}`,
262
230
  };
263
231
  }
264
- datasetFingerprint = manifest.datasetFingerprint;
232
+ ({ datasetFingerprint } = manifest);
265
233
  }
266
234
 
267
235
  const benchmarkExportId = benchmarkExportIdArg || datasetExportId || 'benchmark-default';
@@ -284,7 +252,7 @@ Hardware tiers:
284
252
  // The trainer reads spec from scripts/nocturnal/trainer/experiment-<id>.json.
285
253
  // import-result reads spec from .state/nocturnal/checkpoints/experiment-<id>.json.
286
254
  // Both must be written so the manual create-experiment -> trainer -> import-result chain works.
287
- const spec = createResult.spec;
255
+ const {spec} = createResult;
288
256
  const trainerSpecPath = path.join(TRAINER_SCRIPTS_DIR, `experiment-${spec.experimentId}.json`);
289
257
  const workspaceSpecPath = path.join(workspaceDir, '.state', 'nocturnal', 'checkpoints', `experiment-${spec.experimentId}.json`);
290
258
  const trainerSpecDir = path.dirname(trainerSpecPath);
@@ -302,11 +270,13 @@ Hardware tiers:
302
270
  // This closes the gap in the create-experiment -> trainer -> import-result chain.
303
271
  // NOTE: This blocks until training completes (could be minutes).
304
272
  if (runNow) {
305
- const spec = createResult.spec;
273
+
274
+ const {spec} = createResult;
306
275
  const baseDir = TRAINER_SCRIPTS_DIR;
307
276
  const scriptPath = path.join(baseDir, 'main.py');
308
277
  const specPath = path.join(baseDir, `experiment-${spec.experimentId}.json`);
309
- const outputDir = spec.outputDir;
278
+
279
+ const {outputDir} = spec;
310
280
  const resultFilePath = path.join(outputDir, `result-${spec.experimentId}.json`);
311
281
 
312
282
  // Write spec file
@@ -316,6 +286,7 @@ Hardware tiers:
316
286
  }
317
287
  fs.writeFileSync(specPath, JSON.stringify(spec, null, 2), 'utf-8');
318
288
 
289
+
319
290
  let trainerResult!: import('../core/external-training-contract.js').TrainingExperimentResult;
320
291
 
321
292
  try {
@@ -423,6 +394,7 @@ Hardware tiers:
423
394
 
424
395
  // Process trainer result (register checkpoint)
425
396
  // dry_run returns null (no checkpoint); other statuses throw on error
397
+
426
398
  let processed: { checkpointId: string; checkpointRef: string } | null;
427
399
  try {
428
400
  processed = program.processResult({
@@ -523,7 +495,7 @@ Next steps:
523
495
 
524
496
  // ── Show Experiment ───────────────────────────────────────────────────
525
497
  if (subcommand === 'show-experiment') {
526
- const experimentId = parts[1];
498
+ const [, experimentId] = parts;
527
499
  if (!experimentId) {
528
500
  return { text: zh ? '错误: 需要实验 ID' : 'Error: experiment ID required' };
529
501
  }
@@ -540,7 +512,7 @@ Next steps:
540
512
 
541
513
  // ── Import Result ─────────────────────────────────────────────────────
542
514
  if (subcommand === 'import-result') {
543
- const experimentId = parts[1];
515
+ const [, experimentId] = parts;
544
516
  if (!experimentId) {
545
517
  return { text: zh ? '错误: 需要实验 ID' : 'Error: experiment ID required' };
546
518
  }
@@ -564,6 +536,7 @@ Next steps:
564
536
  }
565
537
  }
566
538
 
539
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: JSON.parse returns dynamic JSON - type unknown at parse time, narrowed via type narrowing below
567
540
  let result: any;
568
541
  try {
569
542
  result = JSON.parse(resultJson);
@@ -594,6 +567,7 @@ Next steps:
594
567
 
595
568
  // Process the result
596
569
  const program = new TrainingProgram(workspaceDir);
570
+
597
571
  let processed: { checkpointId: string; checkpointRef: string } | null;
598
572
  try {
599
573
  processed = program.processResult({
@@ -673,11 +647,11 @@ Next steps:
673
647
  let benchmarkId = benchmarkIdArg || `bench-${Date.now()}`;
674
648
  let delta = deltaArg ? parseFloat(deltaArg) : NaN;
675
649
  let verdict: 'fail' | 'pass' | 'compare_only' = verdictArg === 'pass' || verdictArg === 'fail' || verdictArg === 'compare_only'
676
- ? (verdictArg as 'fail' | 'pass' | 'compare_only')
650
+ ? (verdictArg)
677
651
  : 'compare_only';
678
652
  let baselineScore = baselineScoreArg ? parseFloat(baselineScoreArg) : 0.5;
679
653
  let candidateScore = candidateScoreArg ? parseFloat(candidateScoreArg) : 0.5;
680
- const mode = (modeArg === 'prompt_assisted' ? 'prompt_assisted' : 'reduced_prompt') as 'prompt_assisted' | 'reduced_prompt';
654
+ const mode: 'prompt_assisted' | 'reduced_prompt' = (modeArg === 'prompt_assisted' ? 'prompt_assisted' : 'reduced_prompt');
681
655
 
682
656
  // --- Run benchmark mode: execute real benchmark to get scores ---
683
657
  // This closes the gap in the attach-eval command chain.
@@ -717,7 +691,7 @@ Next steps:
717
691
  if (checkpoint.trainRunId) {
718
692
  const run = getTrainingRun(workspaceDir, checkpoint.trainRunId);
719
693
  if (run?.exportId) {
720
- exportId = run.exportId;
694
+ ({ exportId } = run);
721
695
  }
722
696
  }
723
697
  const scorerType = 'local-model'; // Use real model scorer
@@ -780,11 +754,11 @@ Next steps:
780
754
  };
781
755
  }
782
756
 
783
- delta = benchmarkResult.delta.delta;
784
- baselineScore = benchmarkResult.delta.baselineScore;
785
- candidateScore = benchmarkResult.delta.candidateScore;
786
- benchmarkId = benchmarkResult.benchmarkId;
787
- verdict = benchmarkResult.verdict;
757
+ ({
758
+ delta: { delta, baselineScore, candidateScore },
759
+ benchmarkId,
760
+ verdict,
761
+ } = benchmarkResult);
788
762
  } else {
789
763
  // Manual mode: require explicit delta and verdict
790
764
  if (!deltaArg || !verdictArg) {
@@ -855,7 +829,7 @@ Next steps:
855
829
 
856
830
  // ── Show Lineage ─────────────────────────────────────────────────────
857
831
  if (subcommand === 'show-lineage') {
858
- const checkpointId = parts[1];
832
+ const [, checkpointId] = parts;
859
833
  if (!checkpointId) {
860
834
  return { text: zh ? '错误: 需要 checkpointId' : 'Error: checkpointId required' };
861
835
  }
@@ -1,4 +1,4 @@
1
- import { trackFriction, resetFriction, getSession } from '../core/session-tracker.js';
1
+ import { resetFriction, getSession } from '../core/session-tracker.js';
2
2
  import { WorkspaceContext } from '../core/workspace-context.js';
3
3
  import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
4
4
  import type { EmpathyEventStats } from '../types/event-types.js';
@@ -6,7 +6,7 @@ import type { EmpathyEventStats } from '../types/event-types.js';
6
6
  /**
7
7
  * Creates a visual progress bar (e.g., [██████░░░░])
8
8
  */
9
- function createProgressBar(value: number, max: number, length: number = 10): string {
9
+ function createProgressBar(value: number, max: number, length = 10): string {
10
10
  const filledLength = Math.round((value / max) * length);
11
11
  const emptyLength = length - filledLength;
12
12
  return `[${'█'.repeat(filledLength)}${'░'.repeat(emptyLength)}]`;
@@ -15,7 +15,7 @@ function createProgressBar(value: number, max: number, length: number = 10): str
15
15
  /**
16
16
  * Creates a mini bar for daily trends
17
17
  */
18
- function createMiniBar(count: number, max: number, length: number = 6): string {
18
+ function createMiniBar(count: number, max: number, length = 6): string {
19
19
  const filledLength = Math.round((count / max) * length);
20
20
  return '█'.repeat(filledLength) + '░'.repeat(length - filledLength);
21
21
  }
@@ -89,12 +89,14 @@ export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResul
89
89
  const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
90
90
  const lang = (ctx.config?.language as string) || 'en';
91
91
  const isZh = lang === 'zh';
92
- const sessionId = (ctx as any).sessionId;
92
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: sessionId injected by OpenClaw plugin framework - type not available in PluginCommandContext
93
+ const {sessionId} = (ctx as any);
93
94
 
94
95
  const args = (ctx.args || '').trim();
95
96
 
96
97
  // Handle empathy subcommand
97
98
  if (args.startsWith('empathy')) {
99
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between main handler and empathy subcommand handler - reordering would break logical grouping
98
100
  return handleEmpathySubcommand(wctx, args, sessionId, isZh);
99
101
  }
100
102
 
@@ -118,12 +120,13 @@ export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResul
118
120
  // Default: Show status
119
121
  const session = sessionId ? getSession(sessionId) : undefined;
120
122
  const gfi = session ? session.currentGfi : 0;
121
- const dictionary = wctx.dictionary;
123
+ const {dictionary} = wctx;
122
124
  const stats = dictionary.getStats();
123
125
 
124
126
  const gfiBar = createProgressBar(gfi, 100, 15);
125
127
 
126
128
  // Determine Mental Mode (aligned with prompt.ts logic)
129
+ // eslint-disable-next-line no-useless-assignment -- Reason: initial value unused due to immediate reassignment - keeping for type inference compatibility
127
130
  let mentalMode = '';
128
131
  if (isZh) {
129
132
  if (gfi >= 70) mentalMode = '🚑 救赎模式 (HUMBLE_RECOVERY)';
@@ -136,6 +139,7 @@ export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResul
136
139
  }
137
140
 
138
141
  // Determine health status based on GFI
142
+ // eslint-disable-next-line no-useless-assignment -- Reason: initial values unused due to immediate reassignment in all branches
139
143
  let healthLabel = 'Healthy';
140
144
  let suggestionText = '';
141
145
 
@@ -214,6 +218,7 @@ export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResul
214
218
  /**
215
219
  * Handle /pd-status empathy subcommand
216
220
  */
221
+ // eslint-disable-next-line @typescript-eslint/max-params -- Reason: empathy subcommand requires all 4 context params - refactoring would break API
217
222
  function handleEmpathySubcommand(
218
223
  wctx: WorkspaceContext,
219
224
  args: string,
@@ -1,4 +1,4 @@
1
- import { EvolutionReducerImpl } from '../core/evolution-reducer.js';
1
+ import { WorkspaceContext } from '../core/workspace-context.js';
2
2
  import type { PluginCommandContext } from '../openclaw-sdk.js';
3
3
 
4
4
  export function handlePrincipleRollbackCommand(ctx: PluginCommandContext): { text: string } {
@@ -12,7 +12,9 @@ export function handlePrincipleRollbackCommand(ctx: PluginCommandContext): { tex
12
12
  return { text: isZh ? '用法: /pd-principle-rollback <principleId> [reason]' : 'Usage: /pd-principle-rollback <principleId> [reason]' };
13
13
  }
14
14
 
15
- const reducer = new EvolutionReducerImpl({ workspaceDir });
15
+ // #207/#210: Use WorkspaceContext to get evolutionReducer with stateDir
16
+ const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
17
+ const reducer = wctx.evolutionReducer;
16
18
  const principle = reducer.getPrincipleById(principleId);
17
19
  if (!principle) {
18
20
  return { text: isZh ? `未找到原则: ${principleId}` : `Principle not found: ${principleId}` };
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Promote Implementation Command — CLI `/pd-promote-impl`
3
+ * ========================================================
4
+ *
5
+ * PURPOSE: Manually promote a candidate implementation to active status
6
+ * after replay evaluation review.
7
+ *
8
+ * FLOW:
9
+ * 1. List candidates with replay reports
10
+ * 2. Show replay report summary for selected candidate
11
+ * 3. Ask for confirmation
12
+ * 4. On confirm: transition candidate->active, demote previous active->disabled
13
+ *
14
+ * VALIDATION:
15
+ * - Candidate must have at least one replay report with 'pass' decision
16
+ * - Only candidate -> active is valid for promotion
17
+ */
18
+
19
+ import * as fs from 'fs';
20
+ import * as path from 'path';
21
+ import { ReplayEngine, formatReplayReport } from '../core/replay-engine.js';
22
+ import { refreshPrincipleLifecycle } from '../core/principle-internalization/lifecycle-refresh.js';
23
+ import {
24
+ findActiveImplementation,
25
+ loadLedger,
26
+ transitionImplementationState,
27
+ updateImplementation,
28
+ } from '../core/principle-tree-ledger.js';
29
+ import { WorkspaceContext } from '../core/workspace-context.js';
30
+ import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
31
+ import type { Implementation } from '../types/principle-tree-schema.js';
32
+ import { withLock } from '../utils/file-lock.js';
33
+
34
+ function getAllImplementations(stateDir: string): Implementation[] {
35
+ const ledger = loadLedger(stateDir);
36
+ return Object.values(ledger.tree.implementations);
37
+ }
38
+
39
+ function _handleListCandidates(
40
+ stateDir: string,
41
+ isZh: boolean,
42
+ ): PluginCommandResult {
43
+ const engine = new ReplayEngine('', stateDir);
44
+ const allImpls = getAllImplementations(stateDir);
45
+ const candidates = allImpls.filter(
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: lifecycleState is a dynamic property added by the system - type not in official interface
47
+ (impl) => (impl as any).lifecycleState === 'candidate',
48
+ );
49
+
50
+ if (candidates.length === 0) {
51
+ return {
52
+ text: isZh
53
+ ? '\nℹ️ 未找到候选实现。\n\n用法: /pd-promote-impl list'
54
+ : '\nℹ️ No candidate implementations found.\n\nUsage: /pd-promote-impl list',
55
+ };
56
+ }
57
+
58
+ let output = isZh ? '\n📋 候选实现列表\n' : '\n📋 Candidate Implementations\n';
59
+ output += `${'='.repeat(50)}\n`;
60
+
61
+ for (const impl of candidates) {
62
+ const hasPass = engine.hasPassingReport(impl.id);
63
+ const passBadge = hasPass
64
+ ? isZh ? '✅ 有通过报告' : '✅ Has pass report'
65
+ : isZh ? '❌ 无通过报告' : '❌ No pass report';
66
+ output += ` ${impl.id}\n`;
67
+ output += ` Rule: ${impl.ruleId} | Version: ${impl.version}\n`;
68
+ output += ` Replay: ${passBadge}\n\n`;
69
+ }
70
+
71
+ return { text: output };
72
+ }
73
+
74
+ function _handleShowReport(
75
+ stateDir: string,
76
+ implId: string,
77
+ isZh: boolean,
78
+ ): PluginCommandResult {
79
+ const engine = new ReplayEngine('', stateDir);
80
+ const report = engine.getLatestReport(implId);
81
+
82
+ if (!report) {
83
+ return {
84
+ text: isZh
85
+ ? `❌ 实现 ${implId} 没有回放报告。`
86
+ : `❌ No replay report found for implementation ${implId}.`,
87
+ };
88
+ }
89
+
90
+ return { text: formatReplayReport(report) };
91
+ }
92
+
93
+ interface RunReplayOptions {
94
+ workspaceDir: string;
95
+ stateDir: string;
96
+ implId: string;
97
+ isZh: boolean;
98
+ }
99
+
100
+ function _handleRunReplay(options: RunReplayOptions): PluginCommandResult {
101
+ const { workspaceDir, stateDir, implId, isZh } = options;
102
+ const engine = new ReplayEngine(workspaceDir, stateDir);
103
+
104
+ try {
105
+ const report = engine.runReplayForImplementation(implId);
106
+ let text = formatReplayReport(report);
107
+ if (report.sampleFingerprints.length === 0) {
108
+ text += isZh
109
+ ? '\n⚠️ 未找到已分类的 replay 样本。报告已生成,但当前结果只反映空样本集。\n'
110
+ : '\n⚠️ No classified replay samples were found. The report was generated, but it only reflects an empty sample set.\n';
111
+ }
112
+ return { text };
113
+ } catch (error: unknown) {
114
+ return {
115
+ text: isZh
116
+ ? `❌ 回放评估失败: ${String(error)}`
117
+ : `❌ Replay evaluation failed: ${String(error)}`,
118
+ };
119
+ }
120
+ }
121
+
122
+ interface PromoteImplOptions {
123
+ workspaceDir: string;
124
+ stateDir: string;
125
+ implId: string;
126
+ isZh: boolean;
127
+ }
128
+
129
+ function _handlePromoteImpl(options: PromoteImplOptions): PluginCommandResult {
130
+ const { workspaceDir, stateDir, implId, isZh } = options;
131
+ const engine = new ReplayEngine('', stateDir);
132
+ const allImpls = getAllImplementations(stateDir);
133
+ const candidate = allImpls.find((i) => i.id === implId);
134
+
135
+ if (!candidate) {
136
+ return {
137
+ text: isZh
138
+ ? `❌ 未找到实现: ${implId}`
139
+ : `❌ Implementation not found: ${implId}`,
140
+ };
141
+ }
142
+
143
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: lifecycleState is a dynamic property added by the system - type not in official interface
144
+ const currentState = (candidate as any).lifecycleState || 'candidate';
145
+
146
+ if (currentState !== 'candidate' && currentState !== 'disabled') {
147
+ return {
148
+ text: isZh
149
+ ? `❌ 只能晋升 candidate 或 disabled 状态的实现。当前: ${currentState}`
150
+ : `❌ Can only promote 'candidate' or 'disabled' implementations. Current: ${currentState}`,
151
+ };
152
+ }
153
+
154
+ if (!engine.hasPassingReport(implId)) {
155
+ return {
156
+ text: isZh
157
+ ? `❌ 实现 ${implId} 没有通过的回放报告,无法晋升。\n\n请先运行回放评估。`
158
+ : `❌ Implementation ${implId} has no passing replay report. Promotion rejected.\n\nPlease run a replay evaluation first.`,
159
+ };
160
+ }
161
+
162
+ const report = engine.getLatestReport(implId);
163
+ let output = '';
164
+ if (report) {
165
+ output = `${formatReplayReport(report)}\n`;
166
+ }
167
+
168
+ if (currentState === 'disabled') {
169
+ transitionImplementationState(stateDir, implId, 'active');
170
+ updateImplementation(stateDir, implId, {
171
+ disabledAt: undefined,
172
+ disabledBy: undefined,
173
+ disabledReason: undefined,
174
+ });
175
+ refreshPrincipleLifecycle(workspaceDir, stateDir);
176
+
177
+ output += isZh
178
+ ? `\n✅ 实现已重新启用: ${implId}\n 状态: disabled -> active`
179
+ : `\n✅ Implementation re-enabled: ${implId}\n State: disabled -> active`;
180
+
181
+ return { text: output };
182
+ }
183
+
184
+ const activeForRule = findActiveImplementation(stateDir, candidate.ruleId);
185
+
186
+ if (activeForRule) {
187
+ updateImplementation(stateDir, implId, {
188
+ previousActive: activeForRule.id,
189
+ });
190
+ }
191
+
192
+ transitionImplementationState(stateDir, implId, 'active');
193
+
194
+ if (activeForRule) {
195
+ transitionImplementationState(stateDir, activeForRule.id, 'disabled');
196
+
197
+ output += isZh
198
+ ? `\n上一个活跃实现已禁用: ${activeForRule.id}\n 状态: active -> disabled`
199
+ : `\nPrevious active implementation disabled: ${activeForRule.id}\n State: active -> disabled`;
200
+ }
201
+
202
+ const eventsDir = path.join(
203
+ stateDir,
204
+ 'principles',
205
+ 'implementations',
206
+ implId,
207
+ 'events',
208
+ );
209
+ if (!fs.existsSync(eventsDir)) {
210
+ fs.mkdirSync(eventsDir, { recursive: true });
211
+ }
212
+ const eventPath = path.join(eventsDir, 'promotion.json');
213
+ const promotionEvent = {
214
+ eventType: 'promotion',
215
+ implementationId: implId,
216
+ ruleId: candidate.ruleId,
217
+ fromState: 'candidate',
218
+ toState: 'active',
219
+ previousActive: activeForRule?.id ?? null,
220
+ replayReportId: report
221
+ ? report.generatedAt.replace(/[:.]/g, '-')
222
+ : null,
223
+ promotedAt: new Date().toISOString(),
224
+ };
225
+ withLock(eventPath, () => {
226
+ fs.writeFileSync(eventPath, JSON.stringify(promotionEvent, null, 2), 'utf-8');
227
+ });
228
+ refreshPrincipleLifecycle(workspaceDir, stateDir);
229
+
230
+ output += isZh
231
+ ? `\n\n✅ 实现已晋升: ${implId}\n 状态: candidate -> active`
232
+ : `\n\n✅ Implementation promoted: ${implId}\n State: candidate -> active`;
233
+
234
+ return { text: output };
235
+ }
236
+
237
+ export function handlePromoteImplCommand(ctx: PluginCommandContext): PluginCommandResult {
238
+ const workspaceDir = (ctx.config?.workspaceDir as string) || process.cwd();
239
+ const {stateDir} = WorkspaceContext.fromHookContext({ ...ctx, workspaceDir });
240
+ const lang = (ctx.config?.language as string) || 'en';
241
+ const isZh = lang === 'zh';
242
+
243
+ const args = (ctx.args || '').trim().split(/\s+/);
244
+ const subcommand = args[0] || '';
245
+ const implId = args[1] || '';
246
+
247
+ if (subcommand === 'list' || subcommand === '') {
248
+ return _handleListCandidates(stateDir, isZh);
249
+ }
250
+
251
+ if (subcommand === 'show') {
252
+ if (!implId) {
253
+ return {
254
+ text: isZh
255
+ ? '请指定要查看的实现ID: /pd-promote-impl show <implId>'
256
+ : 'Please specify an implementation ID: /pd-promote-impl show <implId>',
257
+ };
258
+ }
259
+ return _handleShowReport(stateDir, implId, isZh);
260
+ }
261
+
262
+ if (subcommand === 'eval') {
263
+ if (!implId) {
264
+ return {
265
+ text: isZh
266
+ ? '请指定要评估的实现ID: /pd-promote-impl eval <implId>'
267
+ : 'Please specify an implementation ID: /pd-promote-impl eval <implId>',
268
+ };
269
+ }
270
+ return _handleRunReplay({ workspaceDir, stateDir, implId, isZh });
271
+ }
272
+
273
+ return _handlePromoteImpl({ workspaceDir, stateDir, implId: subcommand, isZh });
274
+ }