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,128 @@
1
+ /**
2
+ * PD Task Manager Types
3
+ *
4
+ * Type definitions for the PD Task Manager — a declarative cron task
5
+ * management system that reconciles PD task declarations with OpenClaw's
6
+ * cron/jobs.json using safe file operations (lock + atomic write).
7
+ */
8
+
9
+ // =========================================================================
10
+ // PDTaskSpec — Declaration Schema
11
+ // =========================================================================
12
+
13
+ /** Cron schedule for PD tasks (only "every" kind supported for now) */
14
+ export interface PDTaskSchedule {
15
+ kind: 'every';
16
+ everyMs: number;
17
+ }
18
+
19
+ /** Execution configuration for a PD task */
20
+ export interface PDTaskExecution {
21
+ /** Which prompt builder to use */
22
+ promptTemplate: string;
23
+ /** Execution timeout in seconds (default: 120) */
24
+ timeoutSeconds?: number;
25
+ /** Use lightweight context to save tokens */
26
+ lightContext?: boolean;
27
+ /** Restrict available tools */
28
+ toolsAllow?: string[];
29
+ }
30
+
31
+ /** Delivery configuration for task results */
32
+ export interface PDTaskDelivery {
33
+ mode: 'none' | 'announce';
34
+ channel?: string;
35
+ to?: string;
36
+ }
37
+
38
+ /** Metadata — not synced to cron, used for health tracking */
39
+ export interface PDTaskMeta {
40
+ /** When this task was first declared */
41
+ createdAtMs?: number;
42
+ /** Last successful reconcile timestamp */
43
+ lastSyncedAtMs?: number;
44
+ /** The cron job ID from last sync */
45
+ lastSyncedJobId?: string;
46
+ /** Last sync status */
47
+ lastSyncStatus?: 'ok' | 'error';
48
+ /** Last sync error message */
49
+ lastSyncError?: string;
50
+ /** Consecutive failure count (from CronJobState.consecutiveErrors) */
51
+ consecutiveFailCount?: number;
52
+ /** Timestamp of last failure */
53
+ lastFailedAtMs?: number;
54
+ /** Whether this task was auto-disabled due to health issues */
55
+ autoDisabled?: boolean;
56
+ /** When the task was auto-disabled */
57
+ autoDisabledAt?: number;
58
+ /** Reason for auto-disable */
59
+ autoDisabledReason?: string;
60
+ /** Last manual trigger timestamp */
61
+ lastTriggeredAtMs?: number;
62
+ /** Last manual trigger status */
63
+ lastTriggerStatus?: 'succeeded' | 'failed' | 'pending';
64
+ }
65
+
66
+ /**
67
+ * PDTaskSpec — A declarative specification for a PD background task.
68
+ *
69
+ * This is the source of truth. The reconciler translates these into
70
+ * CronJob entries in OpenClaw's cron/jobs.json.
71
+ */
72
+ export interface PDTaskSpec {
73
+ /** Stable unique ID — never changes across versions */
74
+ id: string;
75
+ /** Human-readable name — becomes the CronJob name (must start with "PD ") */
76
+ name: string;
77
+ /** Description shown to users */
78
+ description: string;
79
+ /** Whether this task should be active */
80
+ enabled: boolean;
81
+ /** Schema version — bumped when prompt/config changes require re-sync */
82
+ version: string;
83
+ /** Cron schedule (only "every" kind supported for now) */
84
+ schedule: PDTaskSchedule;
85
+ /** OpenClaw agent ID to run under (default: "main") */
86
+ agentId?: string;
87
+ /** Execution configuration */
88
+ execution: PDTaskExecution;
89
+ /** Delivery configuration */
90
+ delivery: PDTaskDelivery;
91
+ /** Metadata — not synced to cron */
92
+ meta?: PDTaskMeta;
93
+ }
94
+
95
+ // =========================================================================
96
+ // Builtin PD Tasks
97
+ // =========================================================================
98
+
99
+ /**
100
+ * Built-in PD tasks declared by the plugin.
101
+ *
102
+ * These are reconciled on plugin startup. Adding a new task here
103
+ * automatically creates the corresponding cron job on next restart.
104
+ */
105
+ export const BUILTIN_PD_TASKS: PDTaskSpec[] = [
106
+ {
107
+ id: 'empathy-optimizer',
108
+ name: 'PD Empathy Optimizer',
109
+ description:
110
+ 'Analyzes recent user messages to discover new frustration expressions and optimize keyword weights.',
111
+ enabled: true,
112
+ version: '1.0.1', // Bumped to force cron job settings update
113
+ schedule: {
114
+ kind: 'every',
115
+ everyMs: 5 * 60 * 1000, // 5 minutes (testing); increase to 6h once stable
116
+ },
117
+ agentId: 'main',
118
+ execution: {
119
+ promptTemplate: 'empathy-optimizer',
120
+ timeoutSeconds: 300, // 5 min — needs time to scan events.jsonl
121
+ lightContext: true,
122
+ toolsAllow: ['read_file', 'write_file', 'search_file_content'],
123
+ },
124
+ delivery: {
125
+ mode: 'none',
126
+ },
127
+ },
128
+ ];
@@ -0,0 +1,91 @@
1
+ import type { PrincipleLifecycleEvidence } from './lifecycle-read-model.js';
2
+ import { computePrincipleAdherence, computeRuleMetrics, type PrincipleAdherenceResult, type RuleMetricResult } from './lifecycle-metrics.js';
3
+
4
+ export type DeprecatedReadinessStatus = 'ready' | 'watch' | 'not-ready';
5
+
6
+ export interface DeprecatedReadinessAssessment {
7
+ status: DeprecatedReadinessStatus;
8
+ score: number;
9
+ blockingReasons: string[];
10
+ supportingRuleIds: string[];
11
+ evidence: {
12
+ adherenceRate: number;
13
+ averageRuleCoverage: number;
14
+ averageFalsePositiveRate: number;
15
+ repeatedErrorReductionScore: number;
16
+ repeatedErrorSignal: number;
17
+ stableRuleCount: number;
18
+ activeImplementationCount: number;
19
+ };
20
+ }
21
+
22
+ function clampScore(value: number): number {
23
+ if (!Number.isFinite(value)) {
24
+ return 0;
25
+ }
26
+
27
+ return Math.max(0, Math.min(100, Number(value.toFixed(2))));
28
+ }
29
+
30
+ export function assessDeprecatedReadiness(
31
+ principle: PrincipleLifecycleEvidence,
32
+ precomputedRuleMetrics?: Record<string, RuleMetricResult>,
33
+ precomputedAdherence?: PrincipleAdherenceResult,
34
+ ): DeprecatedReadinessAssessment {
35
+ const ruleMetrics = precomputedRuleMetrics ?? Object.fromEntries(
36
+ principle.rules.map((rule) => [rule.rule.id, computeRuleMetrics(rule)]),
37
+ );
38
+ const adherence = precomputedAdherence ?? computePrincipleAdherence(principle, ruleMetrics);
39
+ const blockingReasons: string[] = [];
40
+ const stableCoverageRatio =
41
+ principle.rules.length > 0 ? adherence.stableRuleIds.length / principle.rules.length : 0;
42
+
43
+ if (principle.rules.length === 0) {
44
+ blockingReasons.push('No material rules are attached to this principle yet.');
45
+ }
46
+ if (principle.summary.activeImplementationCount === 0) {
47
+ blockingReasons.push('No active lower-layer implementation is absorbing the principle.');
48
+ }
49
+ if (adherence.averageRuleCoverage < 75) {
50
+ blockingReasons.push('Rule coverage is not yet stable enough to absorb the principle.');
51
+ }
52
+ if (adherence.averageFalsePositiveRate > 20) {
53
+ blockingReasons.push('False-positive rate remains too high for deprecation readiness.');
54
+ }
55
+ if (adherence.repeatedErrorSignal > 0 || adherence.repeatedErrorReductionScore < 70) {
56
+ blockingReasons.push('Repeated related errors have not fallen enough yet.');
57
+ }
58
+
59
+ const score = clampScore(
60
+ adherence.adherenceRate * 0.45 +
61
+ adherence.averageRuleCoverage * 0.25 +
62
+ (100 - adherence.averageFalsePositiveRate) * 0.15 +
63
+ adherence.repeatedErrorReductionScore * 0.15,
64
+ );
65
+
66
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in all if/else branches
67
+ let status: DeprecatedReadinessStatus;
68
+ if (blockingReasons.length === 0 && stableCoverageRatio === 1) {
69
+ status = 'ready';
70
+ } else if (score >= 55 && principle.summary.activeImplementationCount > 0 && stableCoverageRatio >= 0.5) {
71
+ status = 'watch';
72
+ } else {
73
+ status = 'not-ready';
74
+ }
75
+
76
+ return {
77
+ status,
78
+ score,
79
+ blockingReasons,
80
+ supportingRuleIds: adherence.stableRuleIds,
81
+ evidence: {
82
+ adherenceRate: adherence.adherenceRate,
83
+ averageRuleCoverage: adherence.averageRuleCoverage,
84
+ averageFalsePositiveRate: adherence.averageFalsePositiveRate,
85
+ repeatedErrorReductionScore: adherence.repeatedErrorReductionScore,
86
+ repeatedErrorSignal: adherence.repeatedErrorSignal,
87
+ stableRuleCount: adherence.stableRuleIds.length,
88
+ activeImplementationCount: principle.summary.activeImplementationCount,
89
+ },
90
+ };
91
+ }
@@ -0,0 +1,208 @@
1
+ import type { PrinciplePriority, PrincipleEvaluability, RuleType } from '../../types/principle-tree-schema.js';
2
+ import type { PrincipleLifecycleEvidence } from './lifecycle-read-model.js';
3
+ import {
4
+ computePrincipleAdherence,
5
+ computeRuleMetrics,
6
+ type PrincipleAdherenceResult,
7
+ type RuleMetricResult,
8
+ } from './lifecycle-metrics.js';
9
+
10
+ export type InternalizationRoute = 'skill' | 'code' | 'defer';
11
+
12
+ export interface InternalizationRouteEvidenceSummary {
13
+ replayReportCount: number;
14
+ activeImplementationCount: number;
15
+ candidateImplementationCount: number;
16
+ repeatedErrorSignal: number;
17
+ averageRuleCoverage: number;
18
+ averageFalsePositiveRate: number;
19
+ highestRuleCoverageGap: number;
20
+ dominantRuleType: RuleType | 'mixed';
21
+ }
22
+
23
+ export interface InternalizationRouteRecommendation {
24
+ principleId: string;
25
+ route: InternalizationRoute;
26
+ confidence: number;
27
+ reasonCodes: string[];
28
+ evidenceSummary: InternalizationRouteEvidenceSummary;
29
+ nextAction: string;
30
+ }
31
+
32
+ function clampToPercentage(value: number): number {
33
+ if (!Number.isFinite(value)) {
34
+ return 0;
35
+ }
36
+
37
+ return Math.max(0, Math.min(100, Number(value.toFixed(2))));
38
+ }
39
+
40
+ function dominantRuleType(principle: PrincipleLifecycleEvidence): RuleType | 'mixed' {
41
+ if (principle.rules.length === 0) {
42
+ return 'mixed';
43
+ }
44
+
45
+ const distinctTypes = Array.from(new Set(principle.rules.map((rule) => rule.rule.type)));
46
+ return distinctTypes.length === 1 ? distinctTypes[0] : 'mixed';
47
+ }
48
+
49
+ function averageCoverageGap(ruleMetrics: Record<string, RuleMetricResult>): number {
50
+ const coverageGaps = Object.values(ruleMetrics).map((metrics) => Math.max(0, 100 - metrics.coverageRate));
51
+
52
+ if (coverageGaps.length === 0) {
53
+ return 0;
54
+ }
55
+
56
+ return clampToPercentage(
57
+ coverageGaps.reduce((sum, gap) => sum + gap, 0) / coverageGaps.length,
58
+ );
59
+ }
60
+
61
+ function isHighRisk(priority: PrinciplePriority, evaluability: PrincipleEvaluability): boolean {
62
+ return priority === 'P0' || evaluability === 'deterministic';
63
+ }
64
+
65
+ function supportsSkillRoute(principle: PrincipleLifecycleEvidence): boolean {
66
+ return principle.rules.every((rule) =>
67
+ rule.rule.enforcement !== 'block' || ['skill', 'prompt', 'test'].includes(rule.rule.type),
68
+ );
69
+ }
70
+
71
+ function buildEvidenceSummary(
72
+ principle: PrincipleLifecycleEvidence,
73
+ adherence: PrincipleAdherenceResult,
74
+ ruleMetrics: Record<string, RuleMetricResult>,
75
+ ): InternalizationRouteEvidenceSummary {
76
+ return {
77
+ replayReportCount: principle.summary.replayReportCount,
78
+ activeImplementationCount: principle.summary.activeImplementationCount,
79
+ candidateImplementationCount: principle.summary.candidateImplementationCount,
80
+ repeatedErrorSignal: principle.summary.repeatedErrorSignal,
81
+ averageRuleCoverage: adherence.averageRuleCoverage,
82
+ averageFalsePositiveRate: adherence.averageFalsePositiveRate,
83
+ highestRuleCoverageGap: averageCoverageGap(ruleMetrics),
84
+ dominantRuleType: dominantRuleType(principle),
85
+ };
86
+ }
87
+
88
+ export function recommendInternalizationRoute(
89
+ principle: PrincipleLifecycleEvidence,
90
+ precomputedRuleMetrics?: Record<string, RuleMetricResult>,
91
+ precomputedAdherence?: PrincipleAdherenceResult,
92
+ ): InternalizationRouteRecommendation {
93
+ const ruleMetrics = precomputedRuleMetrics ?? Object.fromEntries(
94
+ principle.rules.map((rule) => [rule.rule.id, computeRuleMetrics(rule)]),
95
+ );
96
+ const adherence = precomputedAdherence ?? computePrincipleAdherence(principle, ruleMetrics);
97
+ const evidenceSummary = buildEvidenceSummary(principle, adherence, ruleMetrics);
98
+ const reasonCodes: string[] = [];
99
+
100
+ // ── Axiom-based heuristic modifiers ──
101
+ const axiomId = principle.principle.coreAxiomId;
102
+ let codeBoost = 0;
103
+ let skillBoost = 0;
104
+ if (axiomId === 'T-05' || axiomId === 'T-08') {
105
+ codeBoost = 15;
106
+ reasonCodes.push('axiom_governance_enforcement');
107
+ } else if (axiomId === 'T-01' || axiomId === 'T-03' || axiomId === 'T-04') {
108
+ skillBoost = 15;
109
+ reasonCodes.push('axiom_knowledge_guidance');
110
+ }
111
+
112
+ const highRisk = isHighRisk(principle.principle.priority, principle.principle.evaluability);
113
+ const hasSparseEvidence =
114
+ principle.summary.replayReportCount < Math.max(1, principle.rules.length) &&
115
+ principle.summary.activeImplementationCount === 0 &&
116
+ principle.summary.candidateImplementationCount === 0 &&
117
+ principle.summary.repeatedErrorSignal < 2;
118
+ const stableEnoughToWait =
119
+ adherence.averageRuleCoverage >= 80 &&
120
+ adherence.averageFalsePositiveRate <= 15 &&
121
+ principle.summary.repeatedErrorSignal === 0;
122
+
123
+ if (principle.rules.length === 0) {
124
+ reasonCodes.push('no_material_rules');
125
+ return {
126
+ principleId: principle.principle.id,
127
+ route: 'defer',
128
+ confidence: 95,
129
+ reasonCodes,
130
+ evidenceSummary,
131
+ nextAction: 'Define at least one concrete rule before choosing an internalization route.',
132
+ };
133
+ }
134
+
135
+ if (hasSparseEvidence) {
136
+ reasonCodes.push('sparse_evidence');
137
+ return {
138
+ principleId: principle.principle.id,
139
+ route: 'defer',
140
+ confidence: clampToPercentage(78 - principle.summary.repeatedErrorSignal * 8),
141
+ reasonCodes,
142
+ evidenceSummary,
143
+ nextAction: 'Collect more replay evidence or live violations before committing to a heavier implementation path.',
144
+ };
145
+ }
146
+
147
+ if (stableEnoughToWait) {
148
+ reasonCodes.push('already_absorbing');
149
+ return {
150
+ principleId: principle.principle.id,
151
+ route: 'defer',
152
+ confidence: clampToPercentage(70 + adherence.averageRuleCoverage * 0.2),
153
+ reasonCodes,
154
+ evidenceSummary,
155
+ nextAction: 'Defer new implementation work and keep monitoring until the lower layer proves it needs another route.',
156
+ };
157
+ }
158
+
159
+ const prefersSkillRoute =
160
+ supportsSkillRoute(principle) &&
161
+ (
162
+ (!highRisk &&
163
+ principle.principle.evaluability !== 'deterministic' &&
164
+ principle.summary.repeatedErrorSignal <= 2) ||
165
+ (skillBoost > 0 && codeBoost === 0)
166
+ ) && codeBoost === 0;
167
+
168
+ if (prefersSkillRoute) {
169
+ reasonCodes.push('cheapest_viable_skill');
170
+ if (principle.summary.activeImplementationCount === 0) {
171
+ reasonCodes.push('no_hard_boundary_required');
172
+ }
173
+
174
+ return {
175
+ principleId: principle.principle.id,
176
+ route: 'skill',
177
+ confidence: clampToPercentage(
178
+ 62 + adherence.repeatedErrorReductionScore * 0.12 - adherence.averageFalsePositiveRate * 0.15 + skillBoost,
179
+ ),
180
+ reasonCodes,
181
+ evidenceSummary,
182
+ nextAction: 'Prefer a skill or prompt-level intervention first, then replay again before escalating to code.',
183
+ };
184
+ }
185
+
186
+ reasonCodes.push('deterministic_or_high_risk');
187
+ if (principle.summary.replayReportCount > 0) {
188
+ reasonCodes.push('replay_evidence_sufficient');
189
+ }
190
+ if (principle.summary.repeatedErrorSignal > 0) {
191
+ reasonCodes.push('repeated_errors_continue');
192
+ }
193
+
194
+ return {
195
+ principleId: principle.principle.id,
196
+ route: 'code',
197
+ confidence: clampToPercentage(
198
+ 58 +
199
+ evidenceSummary.highestRuleCoverageGap * 0.2 +
200
+ principle.summary.repeatedErrorSignal * 6 +
201
+ (highRisk ? 8 : 0) +
202
+ codeBoost,
203
+ ),
204
+ reasonCodes,
205
+ evidenceSummary,
206
+ nextAction: 'Prepare a code implementation candidate and keep promotion manual after replay validation.',
207
+ };
208
+ }
@@ -0,0 +1,149 @@
1
+ import type { PrincipleLifecycleEvidence, RuleLifecycleEvidence } from './lifecycle-read-model.js';
2
+
3
+ export interface RuleMetricResult {
4
+ coverageRate: number;
5
+ falsePositiveRate: number;
6
+ painNegativeHitRate: number;
7
+ principleAnchorPassRate: number;
8
+ implementationStabilityScore: number;
9
+ replayFalsePositiveRate: number;
10
+ livePenaltyRate: number;
11
+ }
12
+
13
+ export interface PrincipleAdherenceResult {
14
+ adherenceRate: number;
15
+ averageRuleCoverage: number;
16
+ averageFalsePositiveRate: number;
17
+ repeatedErrorReductionScore: number;
18
+ repeatedErrorSignal: number;
19
+ stableRuleIds: string[];
20
+ unstableRuleIds: string[];
21
+ }
22
+
23
+ function clampRate(value: number): number {
24
+ if (!Number.isFinite(value)) {
25
+ return 0;
26
+ }
27
+
28
+ return Math.max(0, Math.min(100, Number(value.toFixed(2))));
29
+ }
30
+
31
+ function ratio(passed: number, total: number): number {
32
+ if (total <= 0) {
33
+ return 0;
34
+ }
35
+
36
+ return (passed / total) * 100;
37
+ }
38
+
39
+ function computeImplementationStabilityScore(rule: RuleLifecycleEvidence): number {
40
+ const { liveEvidence, replayEvidence } = rule;
41
+
42
+ let score = 0;
43
+
44
+ if (liveEvidence.hasActiveImplementation) {
45
+ score += 35;
46
+ }
47
+ if (liveEvidence.hasPassingActiveImplementation) {
48
+ score += 30;
49
+ } else if (replayEvidence.passingImplementationIds.length > 0) {
50
+ score += 20;
51
+ }
52
+ if (liveEvidence.candidateCount > 0) {
53
+ score += 10;
54
+ }
55
+ if (replayEvidence.reportCount > 0) {
56
+ score += 15;
57
+ }
58
+ if (
59
+ liveEvidence.hasActiveImplementation &&
60
+ liveEvidence.durablePenaltyCount === 0 &&
61
+ liveEvidence.rollbackEvidenceCount === 0
62
+ ) {
63
+ score += 10;
64
+ }
65
+
66
+ score -= liveEvidence.durablePenaltyCount * 12;
67
+ score -= liveEvidence.rollbackEvidenceCount * 8;
68
+
69
+ return clampRate(score);
70
+ }
71
+
72
+ export function computeRuleMetrics(rule: RuleLifecycleEvidence): RuleMetricResult {
73
+ const painNegativeHitRate = ratio(rule.replayEvidence.painNegative.passed, rule.replayEvidence.painNegative.total);
74
+ const principleAnchorPassRate = ratio(
75
+ rule.replayEvidence.principleAnchor.passed,
76
+ rule.replayEvidence.principleAnchor.total,
77
+ );
78
+ const implementationStabilityScore = computeImplementationStabilityScore(rule);
79
+ const replayFalsePositiveRate = ratio(
80
+ rule.replayEvidence.successPositive.failed,
81
+ rule.replayEvidence.successPositive.total,
82
+ );
83
+ const livePenaltyRate = Math.min(
84
+ 15,
85
+ rule.liveEvidence.durablePenaltyCount * 5 + rule.liveEvidence.rollbackEvidenceCount * 3,
86
+ );
87
+
88
+ return {
89
+ coverageRate: clampRate(
90
+ painNegativeHitRate * 0.5 + principleAnchorPassRate * 0.3 + implementationStabilityScore * 0.2,
91
+ ),
92
+ falsePositiveRate: clampRate(replayFalsePositiveRate + livePenaltyRate),
93
+ painNegativeHitRate: clampRate(painNegativeHitRate),
94
+ principleAnchorPassRate: clampRate(principleAnchorPassRate),
95
+ implementationStabilityScore,
96
+ replayFalsePositiveRate: clampRate(replayFalsePositiveRate),
97
+ livePenaltyRate: clampRate(livePenaltyRate),
98
+ };
99
+ }
100
+
101
+ export function computePrincipleAdherence(
102
+ principle: PrincipleLifecycleEvidence,
103
+ precomputedRuleMetrics?: Record<string, RuleMetricResult>,
104
+ ): PrincipleAdherenceResult {
105
+ const metricsByRuleId = precomputedRuleMetrics ?? Object.fromEntries(
106
+ principle.rules.map((rule) => [rule.rule.id, computeRuleMetrics(rule)]),
107
+ );
108
+
109
+ if (principle.rules.length === 0) {
110
+ return {
111
+ adherenceRate: 0,
112
+ averageRuleCoverage: 0,
113
+ averageFalsePositiveRate: 0,
114
+ repeatedErrorReductionScore: 0,
115
+ repeatedErrorSignal: principle.summary.repeatedErrorSignal,
116
+ stableRuleIds: [],
117
+ unstableRuleIds: [],
118
+ };
119
+ }
120
+
121
+ const ruleMetrics = principle.rules.map((rule) => ({
122
+ ruleId: rule.rule.id,
123
+ metrics: metricsByRuleId[rule.rule.id] ?? computeRuleMetrics(rule),
124
+ }));
125
+ const averageRuleCoverage = ruleMetrics.reduce((sum, entry) => sum + entry.metrics.coverageRate, 0) / ruleMetrics.length;
126
+ const averageFalsePositiveRate =
127
+ ruleMetrics.reduce((sum, entry) => sum + entry.metrics.falsePositiveRate, 0) / ruleMetrics.length;
128
+
129
+ const stableRuleIds = ruleMetrics
130
+ .filter((entry) => entry.metrics.coverageRate >= 70 && entry.metrics.falsePositiveRate <= 25)
131
+ .map((entry) => entry.ruleId);
132
+ const unstableRuleIds = ruleMetrics
133
+ .filter((entry) => !stableRuleIds.includes(entry.ruleId))
134
+ .map((entry) => entry.ruleId);
135
+
136
+ const repeatedErrorPressure =
137
+ principle.summary.repeatedErrorSignal * 10 + unstableRuleIds.length * 12 + averageFalsePositiveRate * 0.4;
138
+ const repeatedErrorReductionScore = clampRate(100 - repeatedErrorPressure);
139
+
140
+ return {
141
+ adherenceRate: clampRate(averageRuleCoverage * 0.7 + repeatedErrorReductionScore * 0.3),
142
+ averageRuleCoverage: clampRate(averageRuleCoverage),
143
+ averageFalsePositiveRate: clampRate(averageFalsePositiveRate),
144
+ repeatedErrorReductionScore,
145
+ repeatedErrorSignal: principle.summary.repeatedErrorSignal,
146
+ stableRuleIds,
147
+ unstableRuleIds,
148
+ };
149
+ }