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
@@ -38,11 +38,12 @@ export type BashRiskLevel = 'safe' | 'dangerous' | 'normal';
38
38
  * @param logger - Optional logger for warnings about invalid patterns
39
39
  * @returns The risk level: 'safe', 'dangerous', or 'normal'
40
40
  */
41
+ /* eslint-disable @typescript-eslint/max-params -- Reason: Bash risk analysis requires command + pattern lists - refactoring to options object would be breaking API change */
41
42
  export function analyzeBashCommand(
42
43
  command: string,
43
44
  safePatterns: string[],
44
45
  dangerousPatterns: string[],
45
- logger?: { warn?: (message: string) => void }
46
+ logger?: { warn?: (/* eslint-disable-line no-unused-vars -- Reason: callback parameter, unused by design */_message: string) => void }
46
47
  ): BashRiskLevel {
47
48
  let normalizedCmd = command.trim().toLowerCase();
48
49
 
@@ -64,6 +65,7 @@ export function analyzeBashCommand(
64
65
  // - Zero-width joiner (U+200D)
65
66
  // - Word joiner (U+2060)
66
67
  // - Zero-width invisible separator (U+FEFF)
68
+ // eslint-disable-next-line no-misleading-character-class -- Reason: zero-width character class ranges are intentional - documented in comment above
67
69
  const ZERO_WIDTH_CHARS = /[\u200B\u200C\u200D\u2060\uFEFF]/g;
68
70
  if (ZERO_WIDTH_CHARS.test(command)) {
69
71
  logger?.warn?.(`[PD_GATE] Bash command contains zero-width characters — blocking as dangerous`);
@@ -44,7 +44,7 @@ export function normalizeLine(line: string): string {
44
44
  * @param threshold - Match threshold (0-1)
45
45
  * @returns Match index or -1 if not found
46
46
  */
47
- export function findFuzzyMatch(lines: string[], oldLines: string[], threshold: number = 0.8): number {
47
+ export function findFuzzyMatch(lines: string[], oldLines: string[], threshold = 0.8): number {
48
48
  if (oldLines.length === 0) return -1; // P2 fix: empty array boundary check
49
49
 
50
50
  const normalizedLines = lines.map(normalizeLine);
@@ -75,7 +75,7 @@ export function findFuzzyMatch(lines: string[], oldLines: string[], threshold: n
75
75
  * @param threshold - Match threshold (0-1)
76
76
  * @returns Object with found status and corrected text if found
77
77
  */
78
- export function tryFuzzyMatch(currentContent: string, oldText: string, threshold: number = 0.8): { found: boolean; correctedText?: string } {
78
+ export function tryFuzzyMatch(currentContent: string, oldText: string, threshold = 0.8): { found: boolean; correctedText?: string } {
79
79
  const lines = currentContent.split('\n');
80
80
  const oldLines = oldText.split('\n');
81
81
 
@@ -126,9 +126,11 @@ This is enforced by P-03 (精确匹配前验证原则).`;
126
126
  * Handle edit tool verification before allowing operation
127
127
  * This enforces P-03 at the tool layer
128
128
  */
129
+ // eslint-disable-next-line @typescript-eslint/max-params -- Reason: Hook handler signature requires event + context + config - refactoring would break plugin interface
129
130
  export function handleEditVerification(
130
131
  event: PluginHookBeforeToolCallEvent,
131
132
  wctx: WorkspaceContext,
133
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: logger is typed as any by plugin framework - type not available
132
134
  ctx: { logger?: any; sessionId?: string },
133
135
  config: EditVerificationConfig = {}
134
136
  ): PluginHookBeforeToolCallResult | void {
@@ -153,10 +155,11 @@ export function handleEditVerification(
153
155
  }
154
156
 
155
157
  // 2. Resolve and read file
158
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in try, catch has early return
156
159
  let absolutePath: string;
157
160
  try {
158
161
  absolutePath = wctx.resolve(filePath);
159
- } catch (error) {
162
+ } catch (_error) { // eslint-disable-line @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: intentionally unused - let it fail naturally on path resolution error
160
163
  // Path resolution error, let it fail naturally
161
164
  return;
162
165
  }
@@ -200,7 +203,7 @@ export function handleEditVerification(
200
203
  } catch (statError) {
201
204
  // File stat error (e.g., permission denied)
202
205
  const errStr = statError instanceof Error ? statError.message : String(statError);
203
- const errCode = (statError as any).code;
206
+ const errCode = (statError as { code?: string }).code;
204
207
 
205
208
  if (errCode === 'EACCES' || errCode === 'EPERM') {
206
209
  logger?.error?.(`[PD_GATE:EDIT_VERIFY] Permission denied accessing file: ${path.basename(filePath)} (${errStr})`);
@@ -219,12 +222,13 @@ export function handleEditVerification(
219
222
  }
220
223
 
221
224
  // 3. Read current file content with improved error handling
225
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in try, all catch paths return early
222
226
  let currentContent: string;
223
227
  try {
224
228
  currentContent = fs.readFileSync(absolutePath, 'utf-8');
225
229
  } catch (readError) {
226
230
  const errStr = readError instanceof Error ? readError.message : String(readError);
227
- const errCode = (readError as any).code;
231
+ const errCode = (readError as { code?: string }).code;
228
232
 
229
233
  if (errCode === 'EACCES' || errCode === 'EPERM') {
230
234
  logger?.error?.(`[PD_GATE:EDIT_VERIFY] Permission denied reading file: ${path.basename(filePath)} (${errStr})`);
@@ -48,7 +48,9 @@ export interface BlockContext {
48
48
  export function recordGateBlockAndReturn(
49
49
  wctx: WorkspaceContext,
50
50
  blockCtx: BlockContext,
51
- logger: { warn?: (message: string) => void; error?: (message: string) => void; info?: (message: string) => void }
51
+ /* eslint-disable no-unused-vars -- Reason: type-only callback parameters in logger type */
52
+ logger: { warn?: (_message: string) => void; error?: (_message: string) => void; info?: (_message: string) => void }
53
+ /* eslint-enable no-unused-vars */
52
54
  ): PluginHookBeforeToolCallResult {
53
55
  const { filePath, reason, toolName, sessionId, blockSource } = blockCtx;
54
56
 
@@ -91,6 +93,7 @@ export function recordGateBlockAndReturn(
91
93
  wctx.trajectory?.recordGateBlock?.(trajectoryPayload);
92
94
  } catch (error: unknown) {
93
95
  logWarn(`[PD_GATE] Failed to record trajectory gate block: ${String(error)}`);
96
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: function is defined later but called in this helper for retry logic
94
97
  scheduleTrajectoryGateBlockRetry(wctx, trajectoryPayload, 1, logWarn, logError);
95
98
  }
96
99
 
@@ -130,6 +133,7 @@ This is a mandatory security gate. The operation was blocked because the modific
130
133
  * Uses exponential backoff with max retries.
131
134
  * Failures are logged but do not affect the runtime block decision.
132
135
  */
136
+ /* eslint-disable @typescript-eslint/max-params, no-unused-vars -- Reason: Function requires all params for retry scheduling */
133
137
  function scheduleTrajectoryGateBlockRetry(
134
138
  wctx: WorkspaceContext,
135
139
  payload: {
package/src/hooks/gate.ts CHANGED
@@ -7,11 +7,12 @@
7
7
  * 2. Thinking OS Checkpoint (P-10): Deep reflection enforcement
8
8
  * 3. GFI Gate: Fatigue index-based blocking
9
9
  * 4. Bash Mutation Detection: Heuristic for bash file modifications
10
+ * 4.5. Rule Host: Active code implementation evaluation (Phase 12)
10
11
  * 5. Progressive Gate: EP tier-based access control
11
12
  * 6. Edit Verification (P-03): Exact/fuzzy match for edit operations
12
13
  *
13
14
  * IMPORTANT: This is the SINGLE AUTHORITATIVE orchestration path.
14
- * All policy modules (gfi-gate, progressive-trust-gate) use the shared
15
+ * All policy modules (gfi-gate, progressive-trust-gate, rule-host) use the shared
15
16
  * `recordGateBlockAndReturn` helper to ensure consistent block persistence.
16
17
  *
17
18
  * Zero-width character detection is handled in bash-risk.ts.
@@ -28,16 +29,20 @@ import { handleEditVerification } from './edit-verification.js';
28
29
  import { checkGfiGate } from './gfi-gate.js';
29
30
  import { checkProgressiveTrustGate } from './progressive-trust-gate.js';
30
31
  import { recordGateBlockAndReturn } from './gate-block-helper.js';
31
- import type { PluginHookBeforeToolCallEvent, PluginHookToolContext, PluginHookBeforeToolCallResult } from '../openclaw-sdk.js';
32
+ import { RuleHost } from '../core/rule-host.js';
33
+ import type { RuleHostInput } from '../core/rule-host-types.js';
34
+ import type { PluginHookBeforeToolCallEvent, PluginHookToolContext, PluginHookBeforeToolCallResult, PluginLogger } from '../openclaw-sdk.js';
32
35
  import {
33
36
  AGENT_TOOLS,
34
37
  BASH_TOOLS_SET,
35
38
  WRITE_TOOLS,
36
39
  } from '../constants/tools.js';
40
+ import { getSession, hasRecentThinking } from '../core/session-tracker.js';
41
+ import { getEvolutionEngine } from '../core/evolution-engine.js';
37
42
 
38
43
  export function handleBeforeToolCall(
39
44
  event: PluginHookBeforeToolCallEvent,
40
- ctx: PluginHookToolContext & { workspaceDir?: string; pluginConfig?: Record<string, unknown>; logger?: any }
45
+ ctx: PluginHookToolContext & { workspaceDir?: string; pluginConfig?: Record<string, unknown>; logger?: Partial<PluginLogger> }
41
46
  ): PluginHookBeforeToolCallResult | void {
42
47
  const logger = ctx.logger || console;
43
48
 
@@ -126,9 +131,10 @@ export function handleBeforeToolCall(
126
131
  // Heuristic for bash mutation detection
127
132
  if (isBash && !filePath) {
128
133
  const command = String(event.params.command || event.params.args || "");
129
- const mutationMatch = command.match(/(?:>|>>|sed\s+-i|rm|mv|mkdir|touch|cp)\s+(?:-[a-zA-Z]+\s+)*([^\s;&|<>]+)/);
134
+ const mutationMatch = /(?:>|>>|sed\s+-i|rm|mv|mkdir|touch|cp)\s+(?:-[a-zA-Z]+\s+)*([^\s;&|<>]+)/.exec(command);
130
135
 
131
136
  if (mutationMatch) {
137
+ // eslint-disable-next-line @typescript-eslint/prefer-destructuring -- Reason: mutationMatch[1] assigned to reassignable outer let - destructuring would shadow outer variable
132
138
  filePath = mutationMatch[1];
133
139
  } else {
134
140
  const hasRiskPath = profile.risk_paths.some(rp => command.includes(rp));
@@ -149,6 +155,64 @@ export function handleBeforeToolCall(
149
155
  ? profile.risk_paths.some(rp => filePath.includes(rp))
150
156
  : isRisky(relPath, profile.risk_paths);
151
157
 
158
+ // ─────────────────────────────────────────────────────────────────────────────
159
+ // POLICY STEP 2.5: Rule Host Evaluation (Phase 12, D-01/D-03)
160
+ // ─────────────────────────────────────────────────────────────────────────────
161
+ // Inserted between GFI gate and Progressive Gate so principle rules can act
162
+ // before the capability-boundary fallback. Active code implementations run
163
+ // through a constrained vm context with minimal helpers only.
164
+ try {
165
+ const ruleHost = new RuleHost(wctx.stateDir, logger);
166
+ const hostInput: RuleHostInput = {
167
+ action: {
168
+ toolName: event.toolName,
169
+ normalizedPath: relPath,
170
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
171
+ paramsSummary: _extractParamsSummary(event.params),
172
+ },
173
+ workspace: {
174
+ isRiskPath: risky,
175
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
176
+ planStatus: _getPlanStatus(ctx.workspaceDir),
177
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
178
+ hasPlanFile: _hasPlanFile(ctx.workspaceDir),
179
+ },
180
+ session: {
181
+ sessionId: ctx.sessionId,
182
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
183
+ currentGfi: _getCurrentGfi(ctx.sessionId),
184
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
185
+ recentThinking: _hasRecentThinking(ctx.sessionId),
186
+ },
187
+ evolution: {
188
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
189
+ epTier: _getEpTier(wctx.workspaceDir),
190
+ },
191
+ derived: {
192
+ estimatedLineChanges: estimateLineChanges({ toolName: event.toolName, params: event.params }),
193
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
194
+ bashRisk: _getBashRisk(event, profile),
195
+ },
196
+ };
197
+
198
+ const hostResult = ruleHost.evaluate(hostInput);
199
+ if (hostResult?.decision === 'block' || hostResult?.decision === 'requireApproval') {
200
+ const reason = hostResult.decision === 'requireApproval'
201
+ ? `[Rule Host] Approval required: ${hostResult.reason}`
202
+ : hostResult.reason;
203
+ return recordGateBlockAndReturn(wctx, {
204
+ filePath: relPath,
205
+ reason,
206
+ toolName: event.toolName,
207
+ sessionId: ctx.sessionId,
208
+ blockSource: 'rule-host',
209
+ }, logger);
210
+ }
211
+ } catch (hostError: unknown) {
212
+ // D-08: Conservative degradation — log and continue to Progressive Gate
213
+ logger.warn?.(`[PD_GATE:RULE_HOST] Host evaluation failed, degrading conservatively: ${String(hostError)}`);
214
+ }
215
+
152
216
  // ─────────────────────────────────────────────────────────────────────────────
153
217
  // POLICY STEP 3: Progressive Trust Gate (Stage 1-4 access control)
154
218
  // ─────────────────────────────────────────────────────────────────────────────
@@ -207,4 +271,87 @@ export function handleBeforeToolCall(
207
271
 
208
272
  // All checks passed - allow the operation
209
273
  return;
210
- }
274
+ }
275
+
276
+ // ---------------------------------------------------------------------------
277
+ // Private helpers for building RuleHostInput snapshot
278
+ // These are NOT passed to hosted implementations — they only populate the
279
+ // frozen snapshot that implementations receive.
280
+ // ---------------------------------------------------------------------------
281
+
282
+ function _extractParamsSummary(params: Record<string, unknown>): Record<string, unknown> {
283
+ const summary: Record<string, unknown> = {};
284
+ if (params.file_path) summary.file_path = params.file_path;
285
+ if (params.path) summary.path = params.path;
286
+ if (params.command) summary.command = params.command;
287
+ if (params.args) summary.args = params.args;
288
+ if (params.old_string) summary.old_string = params.old_string;
289
+ if (params.new_string) summary.new_string = params.new_string;
290
+ return summary;
291
+ }
292
+
293
+ function _getPlanStatus(workspaceDir: string): 'NONE' | 'DRAFT' | 'READY' | 'UNKNOWN' {
294
+ try {
295
+ const status = getPlanStatus(workspaceDir);
296
+ if (status === 'READY') return 'READY';
297
+ if (status === 'DRAFT') return 'DRAFT';
298
+ if (status === '') return 'NONE';
299
+ return 'UNKNOWN';
300
+ } catch {
301
+ return 'UNKNOWN';
302
+ }
303
+ }
304
+
305
+ function _hasPlanFile(workspaceDir: string): boolean {
306
+ try {
307
+ return fs.existsSync(path.join(workspaceDir, 'PLAN.md'));
308
+ } catch {
309
+ return false;
310
+ }
311
+ }
312
+
313
+ function _getCurrentGfi(sessionId?: string): number {
314
+ if (!sessionId) return 0;
315
+ try {
316
+ return getSession(sessionId)?.currentGfi ?? 0;
317
+ } catch {
318
+ return 0;
319
+ }
320
+ }
321
+
322
+ function _hasRecentThinking(sessionId?: string): boolean {
323
+ if (!sessionId) return false;
324
+ try {
325
+ return hasRecentThinking(sessionId);
326
+ } catch {
327
+ return false;
328
+ }
329
+ }
330
+
331
+ function _getEpTier(workspaceDir: string): number {
332
+ try {
333
+ const engine = getEvolutionEngine(workspaceDir);
334
+ return engine.getTier() as number;
335
+ } catch {
336
+ return 0;
337
+ }
338
+ }
339
+
340
+ /* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars -- Reason: type-only parameter not used at runtime */
341
+ function _getBashRisk(
342
+ event: PluginHookBeforeToolCallEvent,
343
+ _profile: { risk_paths: string[] }
344
+ ): 'safe' | 'normal' | 'dangerous' | 'unknown' {
345
+ /* eslint-enable no-unused-vars, @typescript-eslint/no-unused-vars */
346
+ if (!BASH_TOOLS_SET.has(event.toolName)) return 'unknown';
347
+ try {
348
+ const command = String(event.params.command || event.params.args || '');
349
+ const isDangerous = /\brm\s+-rf\b|\bchmod\b|\bchown\b|>\s*\/dev\//.test(command);
350
+ if (isDangerous) return 'dangerous';
351
+ const isMutation = /(?:>|>>|sed|rm|mv|mkdir|touch|cp|npm|yarn|pnpm|pip|cargo)/.test(command);
352
+ if (isMutation) return 'normal';
353
+ return 'safe';
354
+ } catch {
355
+ return 'unknown';
356
+ }
357
+ }
@@ -24,7 +24,7 @@
24
24
 
25
25
  import { getSession } from '../core/session-tracker.js';
26
26
  import { estimateLineChanges } from '../core/risk-calculator.js';
27
- import { analyzeBashCommand, calculateDynamicThreshold, type DynamicThresholdConfig } from './bash-risk.js';
27
+ import { analyzeBashCommand, calculateDynamicThreshold } from './bash-risk.js';
28
28
  import { BASH_TOOLS_SET, HIGH_RISK_TOOLS, LOW_RISK_WRITE_TOOLS, AGENT_TOOLS } from '../constants/tools.js';
29
29
  import { AGENT_SPAWN_GFI_THRESHOLD } from '../config/index.js';
30
30
  import { recordGateBlockAndReturn } from './gate-block-helper.js';
@@ -47,13 +47,16 @@ export interface GfiGateConfig {
47
47
  /**
48
48
  * Internal helper to call the shared block helper with gfi-gate source tag.
49
49
  */
50
+ /* eslint-disable @typescript-eslint/max-params -- Reason: Helper function requires all params for block recording */
50
51
  function block(
51
52
  wctx: WorkspaceContext,
52
53
  filePath: string,
53
54
  reason: string,
54
55
  toolName: string,
55
56
  sessionId: string | undefined,
57
+ /* eslint-disable no-unused-vars -- Reason: type-only callback params in logger type */
56
58
  logger?: { info?: (message: string) => void; warn?: (message: string) => void; error?: (message: string) => void }
59
+ /* eslint-enable no-unused-vars */
57
60
  ): PluginHookBeforeToolCallResult {
58
61
  return recordGateBlockAndReturn(wctx, {
59
62
  filePath,
@@ -61,15 +64,19 @@ function block(
61
64
  toolName,
62
65
  sessionId,
63
66
  blockSource: 'gfi-gate',
64
- }, logger || { warn: () => {}, error: () => {} });
67
+ }, logger || // eslint-disable-next-line @typescript-eslint/no-empty-function -- empty warn/error no-op
68
+ { warn: () => {}, error: () => {} } as const);
65
69
  }
66
70
 
71
+ /* eslint-disable @typescript-eslint/max-params -- Reason: Gate function requires all params for comprehensive checks */
67
72
  export function checkGfiGate(
68
73
  event: PluginHookBeforeToolCallEvent,
69
74
  wctx: WorkspaceContext,
70
75
  sessionId: string | undefined,
71
76
  config: GfiGateConfig,
77
+ /* eslint-disable no-unused-vars -- Reason: type-only callback params in logger type */
72
78
  logger?: { info?: (message: string) => void; warn?: (message: string) => void }
79
+ /* eslint-enable no-unused-vars */
73
80
  ): PluginHookBeforeToolCallResult | undefined {
74
81
  if (!config || config.enabled === false || !sessionId) {
75
82
  return undefined;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Lifecycle Routing Hook — Natural Language Intent Detection
3
+ * ==========================================================
4
+ *
5
+ * PURPOSE: Detect natural language intent for promotion, disable, and rollback
6
+ * of implementations. Supports both English and Chinese phrases.
7
+ *
8
+ * PATTERN: Extends the existing rollback natural language detection pattern
9
+ * from rollback.ts.
10
+ */
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Natural Language Patterns
14
+ // ---------------------------------------------------------------------------
15
+
16
+ const PROMOTE_PATTERNS_CN = [
17
+ /促[进推]/,
18
+ /启[用用]/,
19
+ /激活/,
20
+ /设为活动/,
21
+ /启用.*实现/,
22
+ ];
23
+
24
+ const PROMOTE_PATTERNS_EN = [
25
+ /promote\s+(this|the|implementation)/i,
26
+ /activate\s+(this|the|implementation)/i,
27
+ /enable\s+(this|the|implementation)/i,
28
+ /set\s+(as|to)\s+active/i,
29
+ ];
30
+
31
+ const DISABLE_PATTERNS_CN = [
32
+ /禁[用止]/,
33
+ /关闭.*实现/,
34
+ /停止.*实现/,
35
+ /停用/,
36
+ ];
37
+
38
+ const DISABLE_PATTERNS_EN = [
39
+ /disable\s+(this|the|implementation)/i,
40
+ /turn\s+off\s+(this|the|implementation)/i,
41
+ /deactivate\s+(this|the|implementation)/i,
42
+ /stop\s+(this|the|implementation)/i,
43
+ ];
44
+
45
+ const ROLLBACK_PATTERNS_CN = [
46
+ /回滚/,
47
+ /撤销.*实现/,
48
+ /恢复.*实现/,
49
+ /退回.*实现/,
50
+ ];
51
+
52
+ const ROLLBACK_PATTERNS_EN = [
53
+ /rollback\s+(this|the|implementation)/i,
54
+ /revert\s+(this|the|implementation)/i,
55
+ /undo\s+(this|the|implementation)/i,
56
+ /restore\s+(previous|last|implementation)/i,
57
+ ];
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Intent Detection
61
+ // ---------------------------------------------------------------------------
62
+
63
+ export type LifecycleIntent = 'promote' | 'disable' | 'rollback' | null;
64
+
65
+ /**
66
+ * Detect implementation lifecycle intent from user message.
67
+ * Returns the detected intent type or null.
68
+ */
69
+ export function detectLifecycleIntent(message: string): LifecycleIntent {
70
+ // Check promote patterns
71
+ for (const p of PROMOTE_PATTERNS_EN) {
72
+ if (p.test(message)) return 'promote';
73
+ }
74
+ for (const p of PROMOTE_PATTERNS_CN) {
75
+ if (p.test(message)) return 'promote';
76
+ }
77
+
78
+ // Check disable patterns
79
+ for (const p of DISABLE_PATTERNS_EN) {
80
+ if (p.test(message)) return 'disable';
81
+ }
82
+ for (const p of DISABLE_PATTERNS_CN) {
83
+ if (p.test(message)) return 'disable';
84
+ }
85
+
86
+ // Check rollback patterns
87
+ for (const p of ROLLBACK_PATTERNS_EN) {
88
+ if (p.test(message)) return 'rollback';
89
+ }
90
+ for (const p of ROLLBACK_PATTERNS_CN) {
91
+ if (p.test(message)) return 'rollback';
92
+ }
93
+
94
+ return null;
95
+ }
96
+
97
+ /**
98
+ * Route a natural language lifecycle intent to the appropriate command handler.
99
+ * Returns command name and normalized message, or null if no intent detected.
100
+ */
101
+ export function routeLifecycleIntent(
102
+ message: string
103
+ ): { command: string; normalizedMessage: string } | null {
104
+ const intent = detectLifecycleIntent(message);
105
+ if (!intent) return null;
106
+
107
+ switch (intent) {
108
+ case 'promote':
109
+ return {
110
+ command: 'pd-promote-impl',
111
+ normalizedMessage: 'list',
112
+ };
113
+ case 'disable':
114
+ return {
115
+ command: 'pd-disable-impl',
116
+ normalizedMessage: 'list',
117
+ };
118
+ case 'rollback':
119
+ return {
120
+ command: 'pd-rollback-impl',
121
+ normalizedMessage: 'list',
122
+ };
123
+ }
124
+ }
@@ -1,13 +1,12 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import * as readline from 'readline';
4
- import { computePainScore, buildPainFlag, writePainFlag } from '../core/pain.js';
4
+ import { buildPainFlag, writePainFlag } from '../core/pain.js';
5
5
  import { WorkspaceContext } from '../core/workspace-context.js';
6
6
  import { PD_DIRS } from '../core/paths.js';
7
- import {
8
- extractWorkingMemory,
9
- mergeWorkingMemory,
10
- type WorkingMemorySnapshot
7
+ import {
8
+ extractWorkingMemory,
9
+ mergeWorkingMemory,
11
10
  } from '../core/focus-history.js';
12
11
  import type { PluginHookBeforeResetEvent, PluginHookBeforeCompactionEvent, PluginHookAfterCompactionEvent, PluginHookAgentContext } from '../openclaw-sdk.js';
13
12
 
@@ -55,7 +54,7 @@ interface JsonlMessage {
55
54
 
56
55
  export async function extractPainFromSessionFile(sessionFile: string, ctx: PluginHookAgentContext): Promise<void> {
57
56
  const painPoints: string[] = [];
58
- const workspaceDir = ctx.workspaceDir;
57
+ const {workspaceDir} = ctx;
59
58
 
60
59
  if (!workspaceDir) return;
61
60
 
@@ -123,13 +122,13 @@ export async function extractPainFromSessionFile(sessionFile: string, ctx: Plugi
123
122
  try {
124
123
  rl.close();
125
124
  fileStream.destroy();
126
- } catch (_e) {
125
+ } catch (_e) { // eslint-disable-line @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: intentionally unused - cleanup errors ignored
127
126
  // Ignore cleanup errors
128
127
  }
129
128
  }
130
129
 
131
130
  if (painPoints.length > 0) {
132
- const dateStr = new Date().toISOString().split('T')[0];
131
+ const [dateStr] = new Date().toISOString().split('T');
133
132
  const dailyLogPath = path.join(workspaceDir, PD_DIRS.MEMORY, `${dateStr}.md`);
134
133
  const timestamp = new Date().toISOString();
135
134
 
@@ -177,7 +176,7 @@ export async function handleBeforeCompaction(
177
176
  if (!ctx.workspaceDir) return;
178
177
 
179
178
  const wctx = WorkspaceContext.fromHookContext(ctx);
180
- const dateStr = new Date().toISOString().split('T')[0];
179
+ const [dateStr] = new Date().toISOString().split('T');
181
180
  const checkpointPath = path.join(ctx.workspaceDir, PD_DIRS.MEMORY, `${dateStr}.md`);
182
181
  const log =
183
182
  `\n## [${new Date().toISOString()}] Pre-Compaction Checkpoint\n` +
@@ -195,8 +194,9 @@ export async function handleBeforeCompaction(
195
194
  // 提取工作记忆(从 sessionFile)
196
195
  if (event.sessionFile) {
197
196
  await extractPainFromSessionFile(event.sessionFile, ctx);
198
-
197
+
199
198
  // 新增:提取并保存工作记忆
199
+ /* eslint-disable @typescript-eslint/no-use-before-define -- Reason: extractAndSaveWorkingMemory is defined later in this file */
200
200
  await extractAndSaveWorkingMemory(event.sessionFile, ctx, wctx);
201
201
  }
202
202
  }
@@ -314,8 +314,8 @@ export async function handleAfterCompaction(
314
314
  ): Promise<void> {
315
315
  if (!ctx.workspaceDir) return;
316
316
 
317
- const dateStr = new Date().toISOString().split('T')[0];
318
- const checkpointPath = path.join(ctx.workspaceDir, PD_DIRS.MEMORY, `${dateStr}.md`);
317
+ const [dateStrPost] = new Date().toISOString().split('T');
318
+ const checkpointPath = path.join(ctx.workspaceDir, PD_DIRS.MEMORY, `${dateStrPost}.md`);
319
319
  const log =
320
320
  `- Post-Compaction Complete. Reduced active context to ${event.messageCount} messages.\n`;
321
321