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,136 @@
1
+ /**
2
+ * Archive Implementation Command — CLI `/pd-archive-impl`
3
+ * ========================================================
4
+ *
5
+ * PURPOSE: Permanently archive an implementation (disabled -> archived,
6
+ * active -> archived, or candidate -> archived).
7
+ *
8
+ * Used for permanent cleanup of implementations that are no longer relevant.
9
+ */
10
+
11
+ import { WorkspaceContext } from '../core/workspace-context.js';
12
+ import { refreshPrincipleLifecycle } from '../core/principle-internalization/lifecycle-refresh.js';
13
+ import {
14
+ loadLedger,
15
+ transitionImplementationState,
16
+ updateImplementation,
17
+ } from '../core/principle-tree-ledger.js';
18
+ import type { Implementation, ImplementationLifecycleState } from '../types/principle-tree-schema.js';
19
+ import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
20
+
21
+ /**
22
+ * Get all implementations from the ledger.
23
+ */
24
+ function getAllImplementations(stateDir: string): Implementation[] {
25
+ const ledger = loadLedger(stateDir);
26
+ return Object.values(ledger.tree.implementations);
27
+ }
28
+
29
+ /**
30
+ * Valid archive transitions: disabled -> archived, active -> archived, candidate -> archived
31
+ */
32
+ function canArchive(state: ImplementationLifecycleState): boolean {
33
+ return ['disabled', 'active', 'candidate'].includes(state);
34
+ }
35
+
36
+ /**
37
+ * Handle the /pd-archive-impl command.
38
+ *
39
+ * Usage:
40
+ * /pd-archive-impl <implId> - Archive an implementation
41
+ * /pd-archive-impl list - List archivable implementations
42
+ */
43
+ export function handleArchiveImplCommand(ctx: PluginCommandContext): PluginCommandResult {
44
+ const workspaceDir = (ctx.config?.workspaceDir as string) || process.cwd();
45
+ const {stateDir} = WorkspaceContext.fromHookContext({ ...ctx, workspaceDir });
46
+ const lang = (ctx.config?.language as string) || 'en';
47
+ const isZh = lang === 'zh';
48
+
49
+ const args = (ctx.args || '').trim().split(/\s+/);
50
+ const subcommand = args[0] || '';
51
+
52
+ // Subcommand: list
53
+ if (subcommand === 'list') {
54
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: Mutual recursion between helper functions - reordering would break logical grouping
55
+ return _handleListArchivable(stateDir, isZh);
56
+ }
57
+
58
+ // Archive by ID
59
+ const targetId = 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 _handleArchiveImpl(workspaceDir, stateDir, targetId, isZh);
62
+ }
63
+
64
+ function _handleListArchivable(
65
+ stateDir: string,
66
+ isZh: boolean
67
+ ): PluginCommandResult {
68
+ const allImpls = getAllImplementations(stateDir);
69
+ const archivable = allImpls.filter(
70
+ (impl) => canArchive(impl.lifecycleState || 'candidate')
71
+ );
72
+
73
+ if (archivable.length === 0) {
74
+ return {
75
+ text: isZh
76
+ ? '\n\u2139\ufe0f \u6ca1\u6709\u53ef\u5f52\u6863\u7684\u5b9e\u73b0\u3002'
77
+ : '\n\u2139\ufe0f No implementations available to archive.',
78
+ };
79
+ }
80
+
81
+ let output = isZh ? '\n\ud83d\udccb \u53ef\u5f52\u6863\u5b9e\u73b0\n' : '\n\ud83d\udccb Available Implementations to Archive\n';
82
+ output += `${'='.repeat(50)}\n`;
83
+
84
+ for (const impl of archivable) {
85
+ const stateLabel = impl.lifecycleState || 'candidate';
86
+ output += ` ${impl.id}\n`;
87
+ output += ` Rule: ${impl.ruleId} | State: ${stateLabel}\n\n`;
88
+ }
89
+
90
+ output += isZh
91
+ ? '\u7528\u6cd5: /pd-archive-impl <implId>'
92
+ : 'Usage: /pd-archive-impl <implId>';
93
+
94
+ return { text: output };
95
+ }
96
+
97
+ // 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
98
+ function _handleArchiveImpl(
99
+ workspaceDir: string,
100
+ stateDir: string,
101
+ implId: string,
102
+ isZh: boolean
103
+ ): PluginCommandResult {
104
+ const allImpls = getAllImplementations(stateDir);
105
+ const target = allImpls.find((i) => i.id === implId);
106
+
107
+ if (!target) {
108
+ return {
109
+ text: isZh
110
+ ? `\u274c \u672a\u627e\u5230\u5b9e\u73b0: ${implId}`
111
+ : `\u274c Implementation not found: ${implId}`,
112
+ };
113
+ }
114
+
115
+ const currentState = target.lifecycleState || 'candidate';
116
+
117
+ if (!canArchive(currentState)) {
118
+ return {
119
+ text: isZh
120
+ ? `\u274c \u5b9e\u73b0 ${implId} \u4e0d\u80fd\u4ece ${currentState} \u5f52\u6863\u3002\u5df2\u5f52\u6863\u7684\u5b9e\u73b0\u4e0d\u80fd\u518d\u6b21\u5f52\u6863\u3002`
121
+ : `\u274c Implementation ${implId} cannot be archived from state: ${currentState}. Archived implementations cannot be archived again.`,
122
+ };
123
+ }
124
+
125
+ transitionImplementationState(stateDir, implId, 'archived');
126
+ updateImplementation(stateDir, implId, {
127
+ archivedAt: new Date().toISOString(),
128
+ });
129
+ refreshPrincipleLifecycle(workspaceDir, stateDir);
130
+
131
+ return {
132
+ text: isZh
133
+ ? `\n\u2705 \u5b9e\u73b0\u5df2\u5f52\u6863: ${implId}\n \u72b6\u6001: ${currentState} -> archived`
134
+ : `\n\u2705 Implementation archived: ${implId}\n State: ${currentState} -> archived`,
135
+ };
136
+ }
@@ -13,16 +13,18 @@ const TOOLS_TO_SCAN = [
13
13
  { name: 'shellcheck', cmd: ['shellcheck', '--version'] },
14
14
  ];
15
15
 
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: third-party API execSync returns dynamic output - type structure unknown at call site
16
17
  function scanEnvironment(wctx: WorkspaceContext): any {
17
18
  const tools: Record<string, { available: boolean; version?: string }> = {};
18
19
 
19
20
  for (const tool of TOOLS_TO_SCAN) {
20
21
  try {
21
- const output = execSync(tool.cmd.join(' '), { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
22
+ const [versionLine] = execSync(tool.cmd.join(' '), { stdio: ['ignore', 'pipe', 'ignore'] }).toString().split('\n');
22
23
  tools[tool.name] = {
23
24
  available: true,
24
- version: output.split('\n')[0].trim(),
25
+ version: versionLine.trim(),
25
26
  };
27
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: catch parameter intentionally unused - we only care that the command failed
26
28
  } catch (_e) {
27
29
  tools[tool.name] = { available: false };
28
30
  }
@@ -1,7 +1,8 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
4
- import { ContextInjectionConfig, defaultContextConfig } from '../types.js';
4
+ import type { ContextInjectionConfig} from '../types.js';
5
+ import { defaultContextConfig } from '../types.js';
5
6
  import { loadContextInjectionConfig } from '../hooks/prompt.js';
6
7
 
7
8
  /**
@@ -97,6 +98,7 @@ function showStatus(workspaceDir: string, isZh: boolean): string {
97
98
  /**
98
99
  * Toggle a boolean setting
99
100
  */
101
+ // eslint-disable-next-line @typescript-eslint/max-params -- Reason: Command handler signature requires specific params - refactoring would break public API contract
100
102
  function toggleSetting(
101
103
  workspaceDir: string,
102
104
  key: 'thinkingOs' | 'reflectionLog',
@@ -211,6 +213,7 @@ function applyPreset(
211
213
  preset: 'minimal' | 'standard' | 'full',
212
214
  isZh: boolean
213
215
  ): string {
216
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- Reason: assigned in switch block immediately after declaration
214
217
  let config: ContextInjectionConfig;
215
218
 
216
219
  switch (preset) {
@@ -311,6 +314,7 @@ export function handleContextCommand(ctx: PluginCommandContext): PluginCommandRe
311
314
  // Detect language from context
312
315
  const isZh = (ctx.config?.language as string) === 'zh';
313
316
 
317
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- Reason: assigned in switch block immediately after declaration
314
318
  let result: string;
315
319
 
316
320
  switch (subCommand) {
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Disable Implementation Command — CLI `/pd-disable-impl`
3
+ * ========================================================
4
+ *
5
+ * PURPOSE: Manually disable an active implementation (e.g., regression in production).
6
+ *
7
+ * FLOW:
8
+ * 1. List active implementations
9
+ * 2. Select target, ask for reason
10
+ * 3. Transition active -> disabled (or candidate -> disabled)
11
+ * 4. Record disabledAt, disabledBy, disabledReason
12
+ */
13
+
14
+ import { WorkspaceContext } from '../core/workspace-context.js';
15
+ import { refreshPrincipleLifecycle } from '../core/principle-internalization/lifecycle-refresh.js';
16
+ import {
17
+ loadLedger,
18
+ transitionImplementationState,
19
+ updateImplementation,
20
+ getAllowedTransitions,
21
+ } from '../core/principle-tree-ledger.js';
22
+ import type { Implementation, ImplementationLifecycleState } from '../types/principle-tree-schema.js';
23
+ import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
24
+
25
+ /**
26
+ * Get all implementations from the ledger.
27
+ */
28
+ function getAllImplementations(stateDir: string): Implementation[] {
29
+ const ledger = loadLedger(stateDir);
30
+ return Object.values(ledger.tree.implementations);
31
+ }
32
+
33
+ function _handleListActive(
34
+ stateDir: string,
35
+ isZh: boolean
36
+ ): PluginCommandResult {
37
+ const allImpls = getAllImplementations(stateDir);
38
+ const activeImpls = allImpls.filter(
39
+ (impl) => impl.lifecycleState === 'active' || impl.lifecycleState === 'candidate'
40
+ );
41
+
42
+ if (activeImpls.length === 0) {
43
+ return {
44
+ text: isZh
45
+ ? '\n\u2139\ufe0f \u6ca1\u6709\u53ef\u7981\u7528\u7684\u5b9e\u73b0\u3002'
46
+ : '\n\u2139\ufe0f No implementations available to disable.',
47
+ };
48
+ }
49
+
50
+ let output = isZh
51
+ ? '\n\ud83d\udccb \u53ef\u7981\u7528\u5b9e\u73b0\n'
52
+ : '\n\ud83d\udccb Available Implementations to Disable\n';
53
+ output += `${'='.repeat(50)}\n`;
54
+
55
+ for (const impl of activeImpls) {
56
+ const stateLabel = impl.lifecycleState || 'candidate';
57
+ output += ` ${impl.id}\n`;
58
+ output += ` Rule: ${impl.ruleId} | State: ${stateLabel} | Version: ${impl.version}\n`;
59
+ if (impl.disabledReason) {
60
+ output += ` Previous reason: ${impl.disabledReason}\n`;
61
+ }
62
+ output += '\n';
63
+ }
64
+
65
+ output += isZh
66
+ ? `\u7528\u6cd5: /pd-disable-impl <implId> --reason "\u539f\u56e0"`
67
+ : `Usage: /pd-disable-impl <implId> --reason "reason"`;
68
+
69
+ return { text: output };
70
+ }
71
+
72
+ // 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
73
+ function _handleDisableImpl(
74
+ workspaceDir: string,
75
+ stateDir: string,
76
+ implId: string,
77
+ reason: string | null,
78
+ isZh: boolean,
79
+ sessionId?: string,
80
+ ): PluginCommandResult {
81
+ const allImpls = getAllImplementations(stateDir);
82
+ const target = allImpls.find((i) => i.id === implId);
83
+
84
+ if (!target) {
85
+ return {
86
+ text: isZh
87
+ ? `\u274c \u672a\u627e\u5230\u5b9e\u73b0: ${implId}`
88
+ : `\u274c Implementation not found: ${implId}`,
89
+ };
90
+ }
91
+
92
+ const currentState = target.lifecycleState || 'candidate';
93
+
94
+ // Validate: active -> disabled or candidate -> disabled
95
+ if (currentState !== 'active' && currentState !== 'candidate') {
96
+ const allowed = getAllowedTransitions(currentState as ImplementationLifecycleState);
97
+ return {
98
+ text: isZh
99
+ ? `\u274c \u5b9e\u73b0 ${implId} \u5f53\u524d\u72b6\u6001: ${currentState}\n\u5141\u8bb8\u8fc7\u6e21: ${allowed.join(', ') || '\u65e0\u8fc7\u6e21'}`
100
+ : `\u274c Implementation ${implId} is in state: ${currentState}\nAllowed transitions: ${allowed.join(', ') || 'none'}`,
101
+ };
102
+ }
103
+
104
+ const reasonText = reason || (isZh ? '\u7528\u6237\u624b\u52a8\u7981\u7528' : 'User manual disable');
105
+
106
+ transitionImplementationState(stateDir, implId, 'disabled');
107
+ updateImplementation(stateDir, implId, {
108
+ disabledAt: new Date().toISOString(),
109
+ disabledBy: sessionId || 'manual',
110
+ disabledReason: reasonText,
111
+ });
112
+ refreshPrincipleLifecycle(workspaceDir, stateDir);
113
+
114
+ return {
115
+ text: isZh
116
+ ? `\n\u2705 \u5b9e\u73b0\u5df2\u7981\u7528: ${implId}\n \u72b6\u6001: ${currentState} -> disabled\n \u539f\u56e0: ${reasonText}`
117
+ : `\n\u2705 Implementation disabled: ${implId}\n State: ${currentState} -> disabled\n Reason: ${reasonText}`,
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Handle the /pd-disable-impl command.
123
+ *
124
+ * Usage:
125
+ * /pd-disable-impl list - List active implementations
126
+ * /pd-disable-impl <implId> - Disable an implementation
127
+ * /pd-disable-impl <implId> --reason "<reason>" - Disable with reason
128
+ */
129
+ export function handleDisableImplCommand(ctx: PluginCommandContext): PluginCommandResult {
130
+ const workspaceDir = (ctx.config?.workspaceDir as string) || process.cwd();
131
+ const {stateDir} = WorkspaceContext.fromHookContext({ ...ctx, workspaceDir });
132
+ const lang = (ctx.config?.language as string) || 'en';
133
+ const isZh = lang === 'zh';
134
+
135
+ const args = (ctx.args || '').trim();
136
+
137
+ // Parse args: [implId] [--reason "..."]
138
+ const parts = args.split(/\s+/);
139
+ const subcommand = parts[0] || '';
140
+ const implId = subcommand === 'list' ? '' : subcommand;
141
+ const reasonMatch = (/--reason\s+"([^"]+)"/.exec(args)) || (/--reason\s+(\S+)/.exec(args));
142
+ const reason = reasonMatch ? reasonMatch[1] : null;
143
+
144
+ // Subcommand: list
145
+ if (subcommand === 'list' || subcommand === '') {
146
+ return _handleListActive(stateDir, isZh);
147
+ }
148
+
149
+ // Disable
150
+ return _handleDisableImpl(workspaceDir, stateDir, implId, reason, isZh, ctx.sessionId);
151
+ }
@@ -1,7 +1,9 @@
1
- import { EvolutionReducerImpl } from '../core/evolution-reducer.js';
1
+ import type { EvolutionReducerImpl } from '../core/evolution-reducer.js';
2
+ import type { InternalizationRouteRecommendation } from '../core/principle-internalization/internalization-routing-policy.js';
3
+ import { WorkspaceContext } from '../core/workspace-context.js';
2
4
  import { normalizeLanguage } from '../i18n/commands.js';
3
- import { RuntimeSummaryService } from '../service/runtime-summary-service.js';
4
5
  import type { PluginCommandContext } from '../openclaw-sdk.js';
6
+ import { RuntimeSummaryService } from '../service/runtime-summary-service.js';
5
7
 
6
8
  function formatNumber(value: number | null): string {
7
9
  if (value === null || Number.isNaN(value)) {
@@ -11,7 +13,7 @@ function formatNumber(value: number | null): string {
11
13
  }
12
14
 
13
15
  function formatSources(
14
- sources: Array<{ source: string; score?: number }>
16
+ sources: { source: string; score?: number }[],
15
17
  ): string {
16
18
  if (sources.length === 0) {
17
19
  return '--';
@@ -21,17 +23,36 @@ function formatSources(
21
23
  .map((source) =>
22
24
  source.score === undefined
23
25
  ? source.source
24
- : `${source.source}(${formatNumber(source.score)})`
26
+ : `${source.source}(${formatNumber(source.score)})`,
25
27
  )
26
28
  .join(', ');
27
29
  }
28
30
 
31
+ function formatRouteRecommendations(
32
+ recommendations: InternalizationRouteRecommendation[],
33
+ emptyLabel: string,
34
+ ): string {
35
+ if (recommendations.length === 0) {
36
+ return emptyLabel;
37
+ }
38
+
39
+ return recommendations
40
+ .slice(0, 3)
41
+ .map(
42
+ (recommendation) =>
43
+ `${recommendation.principleId}:${recommendation.route}@${formatNumber(recommendation.confidence)}`,
44
+ )
45
+ .join(', ');
46
+ }
47
+
48
+ // 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
29
49
  function buildEnglishOutput(
30
50
  workspaceDir: string,
31
51
  sessionId: string | null,
32
52
  warnings: string[],
33
53
  stats: ReturnType<EvolutionReducerImpl['getStats']>,
34
- summary: ReturnType<typeof RuntimeSummaryService.getSummary>
54
+ summary: ReturnType<typeof RuntimeSummaryService.getSummary>,
55
+ recommendations: InternalizationRouteRecommendation[],
35
56
  ): string {
36
57
  const lines: string[] = [
37
58
  'Evolution Status',
@@ -47,7 +68,7 @@ function buildEnglishOutput(
47
68
  'Evolution',
48
69
  `- Queue: pending ${summary.evolution.queue.pending}, in_progress ${summary.evolution.queue.inProgress}, completed ${summary.evolution.queue.completed} (${summary.evolution.dataQuality})`,
49
70
  `- Legacy Directive File: ${summary.phase3.legacyDirectiveFilePresent ? 'present' : 'missing'} (compatibility-only display artifact)`,
50
- `- Note: Legacy directive file is NOT a truth source for Phase 3 eligibility. Queue is the only authoritative execution truth source.`,
71
+ '- Note: Legacy directive file is NOT a truth source for Phase 3 eligibility. Queue is the only authoritative execution truth source.',
51
72
  `- Active Evolution Task: ${summary.evolution.directive.taskPreview ?? '--'}`,
52
73
  `- Phase 3: ready ${summary.phase3.phase3ShadowEligible ? 'yes' : 'no'}, queueTruthReady ${summary.phase3.queueTruthReady ? 'yes' : 'no'}, eligible ${summary.phase3.evolutionEligible}, reference_only ${summary.phase3.evolutionReferenceOnly}, rejected ${summary.phase3.evolutionRejected}${summary.phase3.evolutionReferenceOnlyReasons.length > 0 ? ` (reference ${summary.phase3.evolutionReferenceOnlyReasons.slice(0, 2).join(', ')})` : ''}${summary.phase3.evolutionRejectedReasons.length > 0 ? ` (${summary.phase3.evolutionRejectedReasons.slice(0, 3).join(', ')})` : ''}`,
53
74
  `- Phase 3 Legacy Directive File: ${summary.phase3.directiveStatus} (${summary.phase3.directiveIgnoredReason})`,
@@ -58,6 +79,7 @@ function buildEnglishOutput(
58
79
  `- active principles: ${stats.activeCount}`,
59
80
  `- deprecated principles: ${stats.deprecatedCount}`,
60
81
  `- last promoted: ${stats.lastPromotedAt ?? 'none'}`,
82
+ `- internalization routes: ${formatRouteRecommendations(recommendations, '--')}`,
61
83
  '',
62
84
  'Metadata',
63
85
  `- workspace: ${workspaceDir}`,
@@ -75,30 +97,32 @@ function buildEnglishOutput(
75
97
  return lines.join('\n');
76
98
  }
77
99
 
100
+ // 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
78
101
  function buildChineseOutput(
79
102
  workspaceDir: string,
80
103
  sessionId: string | null,
81
104
  warnings: string[],
82
105
  stats: ReturnType<EvolutionReducerImpl['getStats']>,
83
- summary: ReturnType<typeof RuntimeSummaryService.getSummary>
106
+ summary: ReturnType<typeof RuntimeSummaryService.getSummary>,
107
+ recommendations: InternalizationRouteRecommendation[],
84
108
  ): string {
85
109
  const lines: string[] = [
86
110
  '进化状态',
87
111
  '================',
88
112
  '',
89
113
  '控制面',
90
- `- 会话 GFI: 当前 ${formatNumber(summary.gfi.current)},峰值 ${formatNumber(summary.gfi.peak)}(${summary.gfi.dataQuality})`,
114
+ `- 会话 GFI: 当前 ${formatNumber(summary.gfi.current)},峰值 ${formatNumber(summary.gfi.peak)} (${summary.gfi.dataQuality})`,
91
115
  `- GFI 来源: ${formatSources(summary.gfi.sources)}`,
92
- `- Pain Flag: ${summary.pain.activeFlag ? 'active' : 'inactive'}${summary.pain.activeFlagSource ? `(${summary.pain.activeFlagSource})` : ''}`,
116
+ `- Pain Flag: ${summary.pain.activeFlag ? 'active' : 'inactive'}${summary.pain.activeFlagSource ? ` (${summary.pain.activeFlagSource})` : ''}`,
93
117
  `- 最近 Pain 信号: ${summary.pain.lastSignal ? `${summary.pain.lastSignal.source}${summary.pain.lastSignal.reason ? ` - ${summary.pain.lastSignal.reason}` : ''}` : '--'}`,
94
- `- Gate 事件: block ${formatNumber(summary.gate.recentBlocks)},bypass ${formatNumber(summary.gate.recentBypasses)}(${summary.gate.dataQuality})`,
118
+ `- Gate 事件: block ${formatNumber(summary.gate.recentBlocks)},bypass ${formatNumber(summary.gate.recentBypasses)} (${summary.gate.dataQuality})`,
95
119
  '',
96
120
  '进化',
97
- `- 队列: pending ${summary.evolution.queue.pending},in_progress ${summary.evolution.queue.inProgress},completed ${summary.evolution.queue.completed}(${summary.evolution.dataQuality})`,
98
- `- Legacy Directive File: ${summary.phase3.legacyDirectiveFilePresent ? 'present' : 'missing'} (兼容仅显示产物)`,
99
- `- 注:Legacy directive file 不是 Phase 3 合格性的真实源。队列是唯一权威的执行真实源。`,
100
- `- Active Evolution Task: ${summary.evolution.directive.taskPreview ?? '--'}`,
101
- `- Phase 3: ready ${summary.phase3.phase3ShadowEligible ? 'yes' : 'no'},queueTruthReady ${summary.phase3.queueTruthReady ? 'yes' : 'no'},eligible ${summary.phase3.evolutionEligible},reference_only ${summary.phase3.evolutionReferenceOnly},rejected ${summary.phase3.evolutionRejected}${summary.phase3.evolutionReferenceOnlyReasons.length > 0 ? `(reference ${summary.phase3.evolutionReferenceOnlyReasons.slice(0, 2).join(', ')})` : ''}${summary.phase3.evolutionRejectedReasons.length > 0 ? `(${summary.phase3.evolutionRejectedReasons.slice(0, 3).join(', ')})` : ''}`,
121
+ `- 队列: pending ${summary.evolution.queue.pending},in_progress ${summary.evolution.queue.inProgress},completed ${summary.evolution.queue.completed} (${summary.evolution.dataQuality})`,
122
+ `- Legacy Directive File: ${summary.phase3.legacyDirectiveFilePresent ? 'present' : 'missing'}(仅兼容展示)`,
123
+ '- 注意:Legacy directive file 不是 Phase 3 合格性的真实来源,队列才是唯一权威的执行真相。',
124
+ `- 当前进化任务: ${summary.evolution.directive.taskPreview ?? '--'}`,
125
+ `- Phase 3: ready ${summary.phase3.phase3ShadowEligible ? 'yes' : 'no'},queueTruthReady ${summary.phase3.queueTruthReady ? 'yes' : 'no'},eligible ${summary.phase3.evolutionEligible},reference_only ${summary.phase3.evolutionReferenceOnly},rejected ${summary.phase3.evolutionRejected}${summary.phase3.evolutionReferenceOnlyReasons.length > 0 ? ` (reference ${summary.phase3.evolutionReferenceOnlyReasons.slice(0, 2).join(', ')})` : ''}${summary.phase3.evolutionRejectedReasons.length > 0 ? ` (${summary.phase3.evolutionRejectedReasons.slice(0, 3).join(', ')})` : ''}`,
102
126
  `- Phase 3 Legacy Directive File: ${summary.phase3.directiveStatus} (${summary.phase3.directiveIgnoredReason})`,
103
127
  '',
104
128
  '原则统计',
@@ -107,10 +131,11 @@ function buildChineseOutput(
107
131
  `- 生效原则: ${stats.activeCount}`,
108
132
  `- 已废弃原则: ${stats.deprecatedCount}`,
109
133
  `- 最近晋升: ${stats.lastPromotedAt ?? '无'}`,
134
+ `- 内化路由: ${formatRouteRecommendations(recommendations, '--')}`,
110
135
  '',
111
136
  '元数据',
112
137
  `- 工作区: ${workspaceDir}`,
113
- `- Session: ${sessionId ?? '--'}(${summary.metadata.selectedSessionReason})`,
138
+ `- Session: ${sessionId ?? '--'} (${summary.metadata.selectedSessionReason})`,
114
139
  `- 生成时间: ${summary.metadata.generatedAt}`,
115
140
  ];
116
141
 
@@ -127,20 +152,40 @@ function buildChineseOutput(
127
152
  export function handleEvolutionStatusCommand(ctx: PluginCommandContext): { text: string } {
128
153
  const workspaceDir = (ctx.config?.workspaceDir as string) || process.cwd();
129
154
  const sessionId = (ctx as { sessionId?: string | null }).sessionId ?? null;
130
- const reducer = new EvolutionReducerImpl({ workspaceDir });
155
+ // #207/#210: Use WorkspaceContext to get evolutionReducer with stateDir
156
+ const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
157
+ const reducer = wctx.evolutionReducer;
131
158
  const stats = reducer.getStats();
132
159
  const summary = RuntimeSummaryService.getSummary(workspaceDir, { sessionId });
160
+ const recommendations = WorkspaceContext.fromHookContext({ workspaceDir })
161
+ .principleLifecycle
162
+ .recomputeAll()
163
+ .map((assessment) => assessment.routeRecommendation);
133
164
  const rawLang = (ctx.config?.language as string) || 'en';
134
165
  const lang = normalizeLanguage(rawLang);
135
166
  const warnings = summary.metadata.warnings.slice(0, 12);
136
167
 
137
168
  if (lang === 'zh') {
138
169
  return {
139
- text: buildChineseOutput(workspaceDir, summary.metadata.sessionId, warnings, stats, summary),
170
+ text: buildChineseOutput(
171
+ workspaceDir,
172
+ summary.metadata.sessionId,
173
+ warnings,
174
+ stats,
175
+ summary,
176
+ recommendations,
177
+ ),
140
178
  };
141
179
  }
142
180
 
143
181
  return {
144
- text: buildEnglishOutput(workspaceDir, summary.metadata.sessionId, warnings, stats, summary),
182
+ text: buildEnglishOutput(
183
+ workspaceDir,
184
+ summary.metadata.sessionId,
185
+ warnings,
186
+ stats,
187
+ summary,
188
+ recommendations,
189
+ ),
145
190
  };
146
191
  }
@@ -42,14 +42,16 @@ export function handleExportCommand(ctx: PluginCommandContext): PluginCommandRes
42
42
  };
43
43
  }
44
44
 
45
+
46
+
45
47
  return {
46
48
  text: zh
47
- ? `已导出 ORPO 决策点样本到 ${result.manifest!.exportPath},` +
48
- `共 ${result.manifest!.sampleCount} 条,模型家族: ${result.manifest!.targetModelFamily},` +
49
- `数据集指纹: ${result.manifest!.datasetFingerprint.substring(0, 16)}...`
50
- : `Exported ORPO decision-point samples to ${result.manifest!.exportPath}, ` +
51
- `${result.manifest!.sampleCount} samples, target: ${result.manifest!.targetModelFamily}, ` +
52
- `dataset fingerprint: ${result.manifest!.datasetFingerprint.substring(0, 16)}...`,
49
+ ? `已导出 ORPO 决策点样本到 ${result.manifest!.exportPath},` + // eslint-disable-line @typescript-eslint/no-non-null-assertion -- Reason: caller guarantees manifest exists via success check
50
+ `共 ${result.manifest!.sampleCount} 条,模型家族: ${result.manifest!.targetModelFamily},` + // eslint-disable-line @typescript-eslint/no-non-null-assertion -- Reason: caller guarantees manifest exists via success check
51
+ `数据集指纹: ${result.manifest!.datasetFingerprint.substring(0, 16)}...` // eslint-disable-line @typescript-eslint/no-non-null-assertion -- Reason: caller guarantees manifest exists via success check
52
+ : `Exported ORPO decision-point samples to ${result.manifest!.exportPath}, ` + // eslint-disable-line @typescript-eslint/no-non-null-assertion -- Reason: caller guarantees manifest exists via success check
53
+ `${result.manifest!.sampleCount} samples, target: ${result.manifest!.targetModelFamily}, ` + // eslint-disable-line @typescript-eslint/no-non-null-assertion -- Reason: caller guarantees manifest exists via success check
54
+ `dataset fingerprint: ${result.manifest!.datasetFingerprint.substring(0, 16)}...`, // eslint-disable-line @typescript-eslint/no-non-null-assertion -- Reason: caller guarantees manifest exists via success check
53
55
  };
54
56
  }
55
57
 
@@ -10,7 +10,6 @@
10
10
 
11
11
  import * as fs from 'fs';
12
12
  import * as path from 'path';
13
- import { randomUUID } from 'node:crypto';
14
13
  import type { PluginCommandContext, PluginCommandResult, OpenClawPluginApi } from '../openclaw-sdk.js';
15
14
  import { WorkspaceContext } from '../core/workspace-context.js';
16
15
  import {
@@ -24,19 +23,6 @@ import {
24
23
  cleanupStaleInfo,
25
24
  } from '../core/focus-history.js';
26
25
 
27
- /**
28
- * 清理 Markdown 代码块围栏
29
- * 移除开头的 ```lang 和结尾的 ```
30
- */
31
- function stripMarkdownFence(content: string): string {
32
- let result = content.trim();
33
- // 移除开头的代码块标记(如 ```markdown, ```text 等)
34
- result = result.replace(/^```[\w]*\n?/, '');
35
- // 移除结尾的代码块标记
36
- result = result.replace(/\n?```$/, '');
37
- return result.trim();
38
- }
39
-
40
26
  /**
41
27
  * 获取工作区目录
42
28
  */
@@ -295,6 +281,7 @@ async function compressFocus(
295
281
  cleanupHistory(focusPath);
296
282
 
297
283
  // 5. 压缩内容
284
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in both try and catch blocks
298
285
  let compressedContent: string;
299
286
  try {
300
287
  compressedContent = compressFocusContent(oldContent, workspaceDir);
@@ -305,10 +292,9 @@ async function compressFocus(
305
292
  }
306
293
 
307
294
  // 6. 更新版本号和日期
308
- const versionParts = oldVersion.split('.');
309
- const majorVersion = parseInt(versionParts[0], 10) || 1;
310
- const newVersion = `${majorVersion + 1}`;
311
- const today = new Date().toISOString().split('T')[0];
295
+ const [majorVersion] = oldVersion.split('.');
296
+ const newVersion = `${(parseInt(majorVersion, 10) || 1) + 1}`;
297
+ const [today] = new Date().toISOString().split('T');
312
298
  const newContent = compressedContent
313
299
  .replace(/\*\*版本\*\*:\s*v[\d.]+/i, `**版本**: v${newVersion}`)
314
300
  .replace(/\*\*更新\*\*:\s*\d{4}-\d{2}-\d{2}/, `**更新**: ${today}`);
@@ -398,7 +384,7 @@ function rollbackFocus(workspaceDir: string, index: number, isZh: boolean): stri
398
384
  // 恢复历史版本
399
385
  const restoredVersion = extractVersion(historyContent);
400
386
  const restoredDate = extractDate(historyContent);
401
- const today = new Date().toISOString().split('T')[0];
387
+ const [today] = new Date().toISOString().split('T');
402
388
 
403
389
  // 获取最大版本号(从当前文件或历史文件中)
404
390
  let maxVersion = parseFloat(restoredVersion) || 1;
@@ -491,6 +477,7 @@ export async function handleFocusCommand(
491
477
  // 检测语言(与 context.ts 保持一致)
492
478
  const isZh = (ctx.config?.language as string) === 'zh';
493
479
 
480
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in all switch cases
494
481
  let result: string;
495
482
 
496
483
  switch (subCommand) {
@@ -506,7 +493,7 @@ export async function handleFocusCommand(
506
493
  result = await compressFocus(workspaceDir, isZh, api);
507
494
  break;
508
495
  case 'rollback':
509
- case 'rb':
496
+ case 'rb': {
510
497
  const index = parseInt(args[1], 10);
511
498
  if (isNaN(index)) {
512
499
  result = isZh
@@ -516,6 +503,7 @@ export async function handleFocusCommand(
516
503
  result = rollbackFocus(workspaceDir, index, isZh);
517
504
  }
518
505
  break;
506
+ }
519
507
  case 'help':
520
508
  case '--help':
521
509
  case '-h':