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
@@ -222,10 +222,12 @@ function writeRegistry(stateDir: string, registry: ShadowRegistry): void {
222
222
  /**
223
223
  * Execute a read-modify-write under an exclusive file lock.
224
224
  */
225
+ /* eslint-disable no-unused-vars -- Reason: registry param name in type signature intentionally unused - actual function uses different param name */
225
226
  function withShadowRegistryLock<T>(
226
227
  stateDir: string,
227
- fn: (registry: ShadowRegistry) => T
228
+ fn: (_registry: ShadowRegistry) => T
228
229
  ): T {
230
+ /* eslint-enable no-unused-vars */
229
231
  const registryPath = getRegistryPath(stateDir);
230
232
  return withLock(registryPath, () => {
231
233
  const registry = readRegistry(stateDir);
@@ -339,6 +341,7 @@ export function completeShadowObservation(
339
341
  * @param failureSignals - Runtime failure signals
340
342
  * @returns The updated ShadowObservation, or null if not found
341
343
  */
344
+ // eslint-disable-next-line @typescript-eslint/max-params -- Reason: shadow observation completion requires all 4 params - refactoring would break API
342
345
  export function completeShadowObservationByTask(
343
346
  stateDir: string,
344
347
  taskFingerprint: string,
@@ -355,7 +358,7 @@ export function completeShadowObservationByTask(
355
358
  return null;
356
359
  }
357
360
 
358
- const observation = pendingObs[0];
361
+ const [observation] = pendingObs;
359
362
  observation.completedAt = new Date().toISOString();
360
363
  observation.outcome = outcome;
361
364
  observation.failureSignals = failureSignals ?? {
@@ -477,7 +480,7 @@ export function computeShadowStats(
477
480
  export function queryShadowObservations(
478
481
  stateDir: string,
479
482
  checkpointId: string,
480
- limit: number = 100
483
+ limit = 100
481
484
  ): ShadowObservation[] {
482
485
  return withShadowRegistryLock(stateDir, (registry) => {
483
486
  return registry.observations
@@ -25,10 +25,10 @@ export const SystemLogger = {
25
25
  const logEntry = `[${timestamp}] [${eventType.padEnd(15)}] ${message}\n`;
26
26
 
27
27
  // Use fire-and-forget async append to prevent blocking
28
- fs.appendFile(logFile, logEntry, 'utf8', (err) => {
28
+ fs.appendFile(logFile, logEntry, 'utf8', (_err) => { // eslint-disable-line @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: fire-and-forget, errors silently dropped
29
29
  // Silently drop errors (e.g. disk full) to not crash the gateway
30
30
  });
31
- } catch (e) {
31
+ } catch (e) { // eslint-disable-line @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: intentionally unused - silently fail if we can't setup the log
32
32
  // Silently fail if we can't setup the log
33
33
  }
34
34
  }
@@ -1,7 +1,22 @@
1
+ /**
2
+ * Thinking Models — Detection Engine
3
+ *
4
+ * THINKING_OS.md is the single source of truth for model definitions (id, name, description).
5
+ * Detection patterns are carefully tuned regexes that match AI output text.
6
+ *
7
+ * Flow:
8
+ * THINKING_OS.md (authority) → id, name, description, antiPattern
9
+ * BUILTIN_PATTERNS (engine) → detection regexes per model id
10
+ * THINKING_MODELS (merged) → full definitions with patterns
11
+ */
12
+
13
+ import { loadThinkingOsFromWorkspace, generateDetectionPatterns } from './thinking-os-parser.js';
14
+
1
15
  export interface ThinkingModelDefinition {
2
16
  id: string;
3
17
  name: string;
4
18
  description: string;
19
+ antiPattern?: string;
5
20
  patterns: RegExp[];
6
21
  baselineScenarios: string[];
7
22
  }
@@ -12,146 +27,250 @@ export interface ThinkingModelMatch {
12
27
  }
13
28
 
14
29
  export interface ThinkingScenarioContext {
15
- recentToolCalls?: Array<{
30
+ recentToolCalls?: {
16
31
  toolName: string;
17
32
  outcome: 'success' | 'failure' | 'blocked';
18
33
  errorType?: string | null;
19
- }>;
20
- recentPainEvents?: Array<{
34
+ }[];
35
+ recentPainEvents?: {
21
36
  source: string;
22
37
  score: number;
23
- }>;
24
- recentGateBlocks?: Array<{
38
+ }[];
39
+ recentGateBlocks?: {
25
40
  toolName: string;
26
41
  reason: string;
27
- }>;
28
- recentUserCorrections?: Array<{
42
+ }[];
43
+ recentUserCorrections?: {
29
44
  correctionCue?: string | null;
30
- }>;
31
- recentPrincipleEvents?: Array<{
45
+ }[];
46
+ recentPrincipleEvents?: {
32
47
  eventType: string;
33
48
  principleId?: string | null;
34
- }>;
49
+ }[];
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Detection patterns — carefully tuned for AI output text matching
54
+ // These must be manually updated when THINKING_OS.md adds new directives.
55
+ // ---------------------------------------------------------------------------
56
+
57
+ interface BuiltinPatternEntry {
58
+ id: string;
59
+ patterns: RegExp[];
60
+ baselineScenarios: string[];
35
61
  }
36
62
 
37
- const THINKING_MODELS: ThinkingModelDefinition[] = [
63
+ const BUILTIN_PATTERNS: BuiltinPatternEntry[] = [
38
64
  {
39
65
  id: 'T-01',
40
- name: 'Survey Before Acting',
41
- description: 'Understand the structure first before making changes.',
42
- baselineScenarios: ['exploration', 'discovery'],
43
66
  patterns: [
44
67
  /let me (first )?(understand|map|outline|survey|review the (structure|architecture|dependencies))/i,
45
68
  /before (changing|editing|touching) anything/i,
46
69
  /让我先(梳理|理解|看看|盘点).*(结构|架构|依赖|全貌)/i,
70
+ /在执行任何.*/i,
47
71
  ],
72
+ baselineScenarios: ['exploration', 'discovery'],
48
73
  },
49
74
  {
50
75
  id: 'T-02',
51
- name: 'Respect Constraints',
52
- description: 'Explicitly reason about contracts, tests, schemas, and requirements.',
53
- baselineScenarios: ['constraint-check', 'contract-verification'],
54
76
  patterns: [
55
77
  /(type|test|contract|schema|interface) (constraint|requirement|check|validation)/i,
56
- /we (must|need to) (respect|follow|adhere to) the/i,
57
78
  /(必须|需要).*(遵守|符合|满足).*(类型|测试|契约|接口|规范)/i,
58
79
  ],
80
+ baselineScenarios: ['constraint-check', 'contract-verification'],
59
81
  },
60
82
  {
61
83
  id: 'T-03',
62
- name: 'Evidence Over Assumption',
63
- description: 'Use logs, code, and outputs before inferring causes.',
64
- baselineScenarios: ['evidence-gathering', 'verification'],
65
84
  patterns: [
66
85
  /based on (the |this )?(evidence|logs?|output|error|stack trace|test result)/i,
67
86
  /let me (check|verify|confirm|read|look at) (the |)(actual|source|code|file|log)/i,
68
87
  /根据(日志|证据|输出|报错|堆栈|测试结果)/i,
69
88
  ],
89
+ baselineScenarios: ['evidence-gathering', 'verification'],
70
90
  },
71
91
  {
72
92
  id: 'T-04',
73
- name: 'Reversible First',
74
- description: 'Prefer changes that are safe to roll back when risk is high.',
75
- baselineScenarios: ['risk-management', 'reversibility'],
76
93
  patterns: [
77
94
  /this (is|would be) (irreversible|destructive|permanent|not easily undone)/i,
78
95
  /(reversible|can be undone|safely roll back)/i,
79
96
  /(不可逆|破坏性|无法回滚|可以回滚|安全撤销)/i,
80
97
  ],
98
+ baselineScenarios: ['risk-management', 'reversibility'],
81
99
  },
82
100
  {
83
101
  id: 'T-05',
84
- name: 'Safety Rails',
85
- description: 'Call out guardrails, prohibitions, and failure-prevention constraints.',
86
- baselineScenarios: ['guardrails', 'safety-rails'],
87
102
  patterns: [
88
103
  /we (must|should) (not|never|avoid|prevent|ensure we don't)/i,
89
104
  /(critical|important) (not to|that we don't|to avoid)/i,
90
105
  /(绝不能|必须避免|不可|禁止|确保不会)/i,
91
106
  ],
107
+ baselineScenarios: ['guardrails', 'safety-rails'],
92
108
  },
93
109
  {
94
110
  id: 'T-06',
95
- name: 'Simplicity First',
96
- description: 'Prefer the smallest understandable solution over over-engineering.',
97
- baselineScenarios: ['simplification', 'pragmatism'],
98
111
  patterns: [
99
112
  /(simpl(er|est|ify)|minimal|straightforward|lean) (approach|solution|fix|implementation)/i,
100
113
  /(simple is better|keep it simple|no need to over)/i,
101
114
  /(最简|更简单|精简|没必要过度设计)/i,
102
115
  ],
116
+ baselineScenarios: ['simplification', 'pragmatism'],
103
117
  },
104
118
  {
105
119
  id: 'T-07',
106
- name: 'Minimal Change Surface',
107
- description: 'Limit the blast radius and touch only what is necessary.',
108
- baselineScenarios: ['minimal-diff', 'blast-radius-control'],
109
120
  patterns: [
110
121
  /(minimal|smallest|narrowest|least) (change|diff|modification|impact)/i,
111
122
  /only (change|modify|touch|edit) (the |what)/i,
112
123
  /(最小改动|最小变更|只改|只动必要部分)/i,
113
124
  ],
125
+ baselineScenarios: ['minimal-diff', 'blast-radius-control'],
114
126
  },
115
127
  {
116
128
  id: 'T-08',
117
- name: 'Pain As Signal',
118
- description: 'Treat failures and friction as clues to step back and rethink.',
119
- baselineScenarios: ['reflection', 'pain-response'],
120
129
  patterns: [
121
130
  /this (error|failure|issue) (tells us|indicates|signals|suggests|means)/i,
122
131
  /let me (stop|pause|step back|reconsider|rethink)/i,
123
132
  /这个(错误|失败|问题).*(说明|意味着|提示)/i,
124
133
  /让我(停下|暂停|退一步|重新考虑|重新审视)/i,
125
134
  ],
135
+ baselineScenarios: ['reflection', 'pain-response'],
126
136
  },
127
137
  {
128
138
  id: 'T-09',
129
- name: 'Divide And Conquer',
130
- description: 'Split the task into smaller phases before execution.',
131
- baselineScenarios: ['decomposition', 'phased-execution'],
132
139
  patterns: [
133
140
  /(break|split|decompose|divide) (this |the task |it )?(into|down)/i,
134
141
  /(step 1|first,? (we|i|let's)|phase 1)/i,
135
142
  /(拆分|分解|分步|分阶段|第一步)/i,
136
143
  ],
144
+ baselineScenarios: ['decomposition', 'phased-execution'],
145
+ },
146
+ {
147
+ id: 'T-10',
148
+ patterns: [
149
+ /let me (write|save|record|note down|document)/i,
150
+ /memory.*scratchpad|write.*plan\.md|write.*memory|memory.*persist/i,
151
+ /(让我.*写入|写入.*memory|记录.*scratchpad)/i,
152
+ ],
153
+ baselineScenarios: ['memory-persistence', 'state-externalization'],
137
154
  },
138
155
  ];
139
156
 
140
- export const THINKING_MODEL_MAP = new Map(THINKING_MODELS.map((model) => [model.id, model]));
157
+ const BUILTIN_PATTERN_MAP = new Map(BUILTIN_PATTERNS.map((p) => [p.id, p]));
158
+
159
+ // Fallback name/description lookup tables (must be defined before listThinkingModels uses them)
160
+ function getFallbackName(id: string): string {
161
+ const names: Record<string, string> = {
162
+ 'T-01': 'Survey Before Acting',
163
+ 'T-02': 'Respect Constraints',
164
+ 'T-03': 'Evidence Over Assumption',
165
+ 'T-04': 'Reversible First',
166
+ 'T-05': 'Safety Rails',
167
+ 'T-06': 'Simplicity First',
168
+ 'T-07': 'Minimal Change Surface',
169
+ 'T-08': 'Pain As Signal',
170
+ 'T-09': 'Divide And Conquer',
171
+ 'T-10': 'Memory Externalization',
172
+ };
173
+ return names[id] ?? id;
174
+ }
175
+
176
+ function getFallbackDescription(id: string): string {
177
+ const descs: Record<string, string> = {
178
+ 'T-01': 'Understand the structure first before making changes.',
179
+ 'T-02': 'Trust files, not your context window. Write conclusions to files.',
180
+ 'T-03': 'Use logs, code, and outputs before inferring causes.',
181
+ 'T-04': 'Prefer changes that are safe to roll back when risk is high.',
182
+ 'T-05': 'Call out guardrails, prohibitions, and failure-prevention constraints.',
183
+ 'T-06': 'Prefer the smallest understandable solution over over-engineering.',
184
+ 'T-07': 'Limit the blast radius and touch only what is necessary.',
185
+ 'T-08': 'Treat failures and friction as clues to step back and rethink.',
186
+ 'T-09': 'Split the task into smaller phases before execution.',
187
+ 'T-10': 'Write intermediate conclusions to files for persistence.',
188
+ };
189
+ return descs[id] ?? '';
190
+ }
191
+
192
+ // ---------------------------------------------------------------------------
193
+ // Runtime model definitions — merged from THINKING_OS.md + builtin patterns
194
+ // ---------------------------------------------------------------------------
195
+
196
+ let _cachedDefinitions: ThinkingModelDefinition[] | null = null;
197
+ let _cachedWorkspace: string | null = null;
198
+
199
+ /**
200
+ * Load thinking model definitions dynamically from THINKING_OS.md.
201
+ * Falls back to built-in definitions if parsing fails.
202
+ *
203
+ * @param workspaceDir Optional. If provided, loads from that workspace's THINKING_OS.md.
204
+ */
205
+ export function listThinkingModels(workspaceDir?: string): ThinkingModelDefinition[] {
206
+ const cacheKey = workspaceDir ?? '__global__';
207
+ if (_cachedDefinitions && _cachedWorkspace === cacheKey) {
208
+ return _cachedDefinitions.slice();
209
+ }
210
+
211
+ const models: ThinkingModelDefinition[] = [];
212
+
213
+ if (workspaceDir) {
214
+ // Try to load from THINKING_OS.md
215
+ const directives = loadThinkingOsFromWorkspace(workspaceDir);
216
+ if (directives.length > 0) {
217
+ for (const dir of directives) {
218
+ const builtin = BUILTIN_PATTERN_MAP.get(dir.id);
219
+ const patterns = builtin?.patterns ?? generateDetectionPatterns(dir.trigger);
220
+ if (patterns.length === 0) {
221
+ console.warn(`[PD:thinking-models] No detection patterns for ${dir.id}: "${dir.trigger}"`);
222
+ }
223
+ models.push({
224
+ id: dir.id,
225
+ name: dir.name,
226
+ description: dir.must,
227
+ antiPattern: dir.forbidden || undefined,
228
+ patterns,
229
+ baselineScenarios: builtin?.baselineScenarios ?? [],
230
+ });
231
+ }
232
+ _cachedDefinitions = models;
233
+ _cachedWorkspace = cacheKey;
234
+ return models.slice();
235
+ }
236
+ }
237
+
238
+ // Fallback: built-in definitions
239
+ for (const bp of BUILTIN_PATTERNS) {
240
+ models.push({
241
+ id: bp.id,
242
+ name: getFallbackName(bp.id),
243
+ description: getFallbackDescription(bp.id),
244
+ patterns: bp.patterns,
245
+ baselineScenarios: bp.baselineScenarios,
246
+ });
247
+ }
248
+ _cachedDefinitions = models;
249
+ _cachedWorkspace = cacheKey;
250
+ return models.slice();
251
+ }
141
252
 
142
- export function listThinkingModels(): ThinkingModelDefinition[] {
143
- return THINKING_MODELS.slice();
253
+ /**
254
+ * Clear the cached model definitions.
255
+ * Call this when THINKING_OS.md changes.
256
+ */
257
+ export function clearThinkingModelCache(): void {
258
+ _cachedDefinitions = null;
259
+ _cachedWorkspace = null;
144
260
  }
145
261
 
146
- export function getThinkingModel(modelId: string): ThinkingModelDefinition | undefined {
147
- return THINKING_MODEL_MAP.get(modelId);
262
+ export function getThinkingModel(modelId: string, workspaceDir?: string): ThinkingModelDefinition | undefined {
263
+ const models = listThinkingModels(workspaceDir);
264
+ return models.find(m => m.id === modelId);
148
265
  }
149
266
 
150
- export function detectThinkingModelMatches(text: string): ThinkingModelMatch[] {
267
+ export function detectThinkingModelMatches(text: string, workspaceDir?: string): ThinkingModelMatch[] {
151
268
  if (!text) return [];
152
269
 
270
+ const models = listThinkingModels(workspaceDir);
153
271
  const matches: ThinkingModelMatch[] = [];
154
- for (const model of THINKING_MODELS) {
272
+
273
+ for (const model of models) {
155
274
  for (const pattern of model.patterns) {
156
275
  if (pattern.test(text)) {
157
276
  matches.push({
@@ -165,6 +284,23 @@ export function detectThinkingModelMatches(text: string): ThinkingModelMatch[] {
165
284
  return matches;
166
285
  }
167
286
 
287
+ /**
288
+ * Get all model definitions for display purposes (no patterns).
289
+ */
290
+ export function getThinkingModelDefinitions(workspaceDir?: string): {
291
+ modelId: string;
292
+ name: string;
293
+ description: string;
294
+ antiPattern?: string;
295
+ }[] {
296
+ return listThinkingModels(workspaceDir).map(m => ({
297
+ modelId: m.id,
298
+ name: m.id + ': ' + m.name,
299
+ description: m.description,
300
+ antiPattern: m.antiPattern,
301
+ }));
302
+ }
303
+
168
304
  export function deriveThinkingScenarios(
169
305
  modelId: string,
170
306
  context: ThinkingScenarioContext,
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Thinking OS XML Parser
3
+ *
4
+ * Parses THINKING_OS.md to extract directive definitions.
5
+ * THINKING_OS.md is the single source of truth for thinking models.
6
+ *
7
+ * Required XML structure:
8
+ * <directive id="T-01" name="MAP_BEFORE_TERRITORY">
9
+ * <trigger>...</trigger>
10
+ * <must>...</must>
11
+ * <forbidden>...</forbidden>
12
+ * </directive>
13
+ */
14
+
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import { fileURLToPath } from 'url';
18
+ import { resolvePdPath } from './paths.js';
19
+
20
+ export interface ThinkingOsDirective {
21
+ id: string; // "T-01"
22
+ name: string; // "MAP_BEFORE_TERRITORY"
23
+ trigger: string; // <trigger> content — used for detection patterns
24
+ must: string; // <must> content — used as description
25
+ forbidden: string; // <forbidden> content — used as anti-pattern
26
+ }
27
+
28
+ /**
29
+ * Extract a single XML tag's text content from a string.
30
+ */
31
+ function extractTag(content: string, tagName: string): string {
32
+ const regex = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`, 'i');
33
+ const match = content.match(regex);
34
+ if (!match) return '';
35
+ return match[1].trim().replace(/\s+/g, ' ');
36
+ }
37
+
38
+ /**
39
+ * Parse THINKING_OS.md content and extract all <directive> blocks.
40
+ * Returns empty array if no XML directives found.
41
+ */
42
+ export function parseThinkingOsMd(content: string): ThinkingOsDirective[] {
43
+ const directives: ThinkingOsDirective[] = [];
44
+ const directiveRegex = /<directive\s+([^>]*)>([\s\S]*?)<\/directive>/gi;
45
+
46
+ let match: RegExpExecArray | null = null;
47
+
48
+ while ((match = directiveRegex.exec(content)) !== null) {
49
+ const attrs = match[1];
50
+ const body = match[2];
51
+ const idMatch = attrs.match(/id="([^"]+)"/i);
52
+ const nameMatch = attrs.match(/name="([^"]+)"/i);
53
+ if (!idMatch) continue;
54
+
55
+ directives.push({
56
+ id: idMatch[1],
57
+ name: nameMatch ? nameMatch[1] : '',
58
+ trigger: extractTag(body, 'trigger'),
59
+ must: extractTag(body, 'must'),
60
+ forbidden: extractTag(body, 'forbidden'),
61
+ });
62
+ }
63
+
64
+ return directives;
65
+ }
66
+
67
+ /**
68
+ * Load THINKING_OS.md from the workspace.
69
+ * Falls back to plugin templates if workspace file doesn't exist or has no XML directives.
70
+ */
71
+ export function loadThinkingOsFromWorkspace(
72
+ workspaceDir: string,
73
+ language = 'zh',
74
+ ): ThinkingOsDirective[] {
75
+ // Priority 1: workspace THINKING_OS.md
76
+ const workspacePath = resolvePdPath(workspaceDir, 'THINKING_OS');
77
+ if (fs.existsSync(workspacePath)) {
78
+ try {
79
+ const content = fs.readFileSync(workspacePath, 'utf-8');
80
+ const directives = parseThinkingOsMd(content);
81
+ if (directives.length > 0) return directives;
82
+ } catch {
83
+ // Fall through to template
84
+ }
85
+ }
86
+
87
+ // Priority 2: plugin template for the given language
88
+ const templatePath = resolveTemplatePath(language);
89
+ if (templatePath) {
90
+ try {
91
+ const content = fs.readFileSync(templatePath, 'utf-8');
92
+ const directives = parseThinkingOsMd(content);
93
+ if (directives.length > 0) return directives;
94
+ } catch {
95
+ // Fall through to zh template
96
+ }
97
+ }
98
+
99
+ // Priority 3: zh template as ultimate fallback
100
+ const zhPath = resolveTemplatePath('zh');
101
+ if (zhPath) {
102
+ try {
103
+ const content = fs.readFileSync(zhPath, 'utf-8');
104
+ return parseThinkingOsMd(content);
105
+ } catch {
106
+ return [];
107
+ }
108
+ }
109
+
110
+ return [];
111
+ }
112
+
113
+ /**
114
+ * Resolve the THINKING_OS.md template path for a given language.
115
+ */
116
+ function resolveTemplatePath(language: string): string | null {
117
+ const currentDir = path.dirname(fileURLToPath(import.meta.url));
118
+ const templatePath = path.join(
119
+ path.dirname(path.dirname(path.dirname(currentDir))),
120
+ 'templates',
121
+ 'langs',
122
+ language,
123
+ 'principles',
124
+ 'THINKING_OS.md',
125
+ );
126
+ return fs.existsSync(templatePath) ? templatePath : null;
127
+ }
128
+
129
+ /**
130
+ * Extract meaningful detection keywords from a trigger string.
131
+ * Returns an array of regex patterns.
132
+ */
133
+ export function generateDetectionPatterns(trigger: string): RegExp[] {
134
+ if (!trigger) return [];
135
+
136
+ const patterns: string[] = [];
137
+
138
+ // Extract Chinese phrases: 3-8 character sequences
139
+ const chinesePattern = /[\u4e00-\u9fff]{3,8}/g;
140
+ const chineseMatches = trigger.match(chinesePattern) ?? [];
141
+ for (const phrase of chineseMatches) {
142
+ patterns.push(phrase);
143
+ }
144
+
145
+ // Extract English words/phrases
146
+ const englishPattern = /[a-zA-Z]{3,20}(?:\s+[a-zA-Z]{3,20}){0,3}/g;
147
+ const englishMatches = trigger.match(englishPattern) ?? [];
148
+ for (const phrase of englishMatches) {
149
+ const cleaned = phrase.trim();
150
+ if (cleaned.length >= 3) {
151
+ patterns.push(cleaned);
152
+ }
153
+ }
154
+
155
+ return patterns.map(p => new RegExp(p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'));
156
+ }
@@ -30,7 +30,6 @@
30
30
 
31
31
  import * as fs from 'fs';
32
32
  import * as path from 'path';
33
- import * as crypto from 'crypto';
34
33
  import { fileURLToPath } from 'url';
35
34
  import {
36
35
  type TrainingExperimentSpec,
@@ -42,7 +41,6 @@ import {
42
41
  type TrainingBudget,
43
42
  validateTrainerResult,
44
43
  computeConfigFingerprint,
45
- computeDatasetFingerprint,
46
44
  computeCodeHash,
47
45
  generateExperimentId,
48
46
  validateHardwareTier,
@@ -279,7 +277,7 @@ export interface ExecuteTrainerParams {
279
277
  export async function executeTrainer(
280
278
  spec: TrainingExperimentSpec,
281
279
  scriptsDir?: string
282
- ): Promise<import('./external-training-contract.js').TrainingExperimentResult> {
280
+ ): Promise<TrainingExperimentResult> {
283
281
  const baseDir = scriptsDir ?? path.join(REPO_ROOT, TRAINER_SCRIPTS_DIR);
284
282
 
285
283
  // Map backend to script name
@@ -341,7 +339,7 @@ export async function executeTrainer(
341
339
  const MAX_STDOUT_BUFFER = 1 * 1024 * 1024; // 1MB cap
342
340
 
343
341
  const trainerResult = await new Promise<
344
- import('./external-training-contract.js').TrainingExperimentResult
342
+ TrainingExperimentResult
345
343
  >((resolve, reject) => {
346
344
  const proc = spawn(pythonExecutable, [scriptPath, '--spec', specPath, '--output-dir', spec.outputDir]);
347
345
 
@@ -372,7 +370,7 @@ export async function executeTrainer(
372
370
  const trimmed = stdout.trim();
373
371
  if (trimmed) {
374
372
  try {
375
- resolve(JSON.parse(trimmed) as import('./external-training-contract.js').TrainingExperimentResult);
373
+ resolve(JSON.parse(trimmed) as TrainingExperimentResult);
376
374
  return;
377
375
  } catch {
378
376
  // fall through to result file
@@ -382,7 +380,7 @@ export async function executeTrainer(
382
380
  if (fs.existsSync(resultFilePath)) {
383
381
  try {
384
382
  const content = fs.readFileSync(resultFilePath, 'utf-8');
385
- resolve(JSON.parse(content) as import('./external-training-contract.js').TrainingExperimentResult);
383
+ resolve(JSON.parse(content) as TrainingExperimentResult);
386
384
  return;
387
385
  } catch {
388
386
  // fall through to error
@@ -399,7 +397,7 @@ export async function executeTrainer(
399
397
  if (fs.existsSync(resultFilePath)) {
400
398
  try {
401
399
  const content = fs.readFileSync(resultFilePath, 'utf-8');
402
- resolve(JSON.parse(content) as import('./external-training-contract.js').TrainingExperimentResult);
400
+ resolve(JSON.parse(content) as TrainingExperimentResult);
403
401
  } catch {
404
402
  reject(new Error(`Trainer exited with code ${code} and result file was invalid: ${resultFilePath}`));
405
403
  }
@@ -567,7 +565,9 @@ export function processTrainerResult(
567
565
  * ```
568
566
  */
569
567
  export class TrainingProgram {
568
+ /* eslint-disable no-unused-vars -- Reason: stateDir is used via this.stateDir in createExperiment method */
570
569
  constructor(private readonly stateDir: string) {}
570
+ /* eslint-enable no-unused-vars */
571
571
 
572
572
  /**
573
573
  * Create a new training experiment.