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,193 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { assessDeprecatedReadiness } from '../../../src/core/principle-internalization/deprecated-readiness.js';
3
+ import type {
4
+ PrincipleLifecycleEvidence,
5
+ RuleLifecycleEvidence,
6
+ } from '../../../src/core/principle-internalization/lifecycle-read-model.js';
7
+
8
+ function createRuleEvidence(
9
+ id: string,
10
+ overrides: Partial<RuleLifecycleEvidence> = {},
11
+ ): RuleLifecycleEvidence {
12
+ return {
13
+ rule: {
14
+ id,
15
+ version: 1,
16
+ name: id,
17
+ description: id,
18
+ type: 'hook',
19
+ triggerCondition: 'tool=delete',
20
+ enforcement: 'block',
21
+ action: 'block unsafe delete',
22
+ principleId: 'P-001',
23
+ status: 'enforced',
24
+ coverageRate: 0,
25
+ falsePositiveRate: 0,
26
+ implementationIds: [`IMPL-${id}`],
27
+ createdAt: '2026-04-08T00:00:00.000Z',
28
+ updatedAt: '2026-04-08T00:00:00.000Z',
29
+ },
30
+ implementations: [],
31
+ replayEvidence: {
32
+ reportCount: 1,
33
+ latestReports: [],
34
+ painNegative: { total: 4, passed: 4, failed: 0 },
35
+ successPositive: { total: 4, passed: 4, failed: 0 },
36
+ principleAnchor: { total: 4, passed: 4, failed: 0 },
37
+ passingImplementationIds: [`IMPL-${id}`],
38
+ failingImplementationIds: [],
39
+ needsReviewImplementationIds: [],
40
+ },
41
+ liveEvidence: {
42
+ activeCount: 1,
43
+ candidateCount: 0,
44
+ disabledCount: 0,
45
+ archivedCount: 0,
46
+ durablePenaltyCount: 0,
47
+ rollbackEvidenceCount: 0,
48
+ hasActiveImplementation: true,
49
+ hasPassingActiveImplementation: true,
50
+ },
51
+ lineageEvidence: {
52
+ records: [],
53
+ distinctPainSignalCount: 0,
54
+ distinctGateBlockCount: 0,
55
+ repeatedErrorSignal: 0,
56
+ },
57
+ ...overrides,
58
+ };
59
+ }
60
+
61
+ function createPrincipleEvidence(
62
+ rules: RuleLifecycleEvidence[],
63
+ overrides: Partial<PrincipleLifecycleEvidence> = {},
64
+ ): PrincipleLifecycleEvidence {
65
+ return {
66
+ principle: {
67
+ id: 'P-001',
68
+ version: 1,
69
+ text: 'Write before delete',
70
+ triggerPattern: 'delete',
71
+ action: 'write replacement first',
72
+ status: 'active',
73
+ priority: 'P1',
74
+ scope: 'general',
75
+ evaluability: 'deterministic',
76
+ valueScore: 0,
77
+ adherenceRate: 0,
78
+ painPreventedCount: 0,
79
+ derivedFromPainIds: [],
80
+ ruleIds: rules.map((rule) => rule.rule.id),
81
+ conflictsWithPrincipleIds: [],
82
+ createdAt: '2026-04-08T00:00:00.000Z',
83
+ updatedAt: '2026-04-08T00:00:00.000Z',
84
+ },
85
+ rules,
86
+ summary: {
87
+ replayReportCount: rules.reduce((sum, rule) => sum + rule.replayEvidence.reportCount, 0),
88
+ activeImplementationCount: rules.reduce((sum, rule) => sum + rule.liveEvidence.activeCount, 0),
89
+ candidateImplementationCount: rules.reduce((sum, rule) => sum + rule.liveEvidence.candidateCount, 0),
90
+ disabledImplementationCount: rules.reduce((sum, rule) => sum + rule.liveEvidence.disabledCount, 0),
91
+ archivedImplementationCount: rules.reduce((sum, rule) => sum + rule.liveEvidence.archivedCount, 0),
92
+ distinctPainSignalCount: rules.reduce((sum, rule) => sum + rule.lineageEvidence.distinctPainSignalCount, 0),
93
+ distinctGateBlockCount: rules.reduce((sum, rule) => sum + rule.lineageEvidence.distinctGateBlockCount, 0),
94
+ repeatedErrorSignal: rules.reduce((sum, rule) => sum + rule.lineageEvidence.repeatedErrorSignal, 0),
95
+ },
96
+ ...overrides,
97
+ };
98
+ }
99
+
100
+ describe('deprecated-readiness', () => {
101
+ it('marks a principle ready when stable lower-layer implementations absorb every rule', () => {
102
+ const assessment = assessDeprecatedReadiness(
103
+ createPrincipleEvidence([createRuleEvidence('R-001'), createRuleEvidence('R-002')]),
104
+ );
105
+
106
+ expect(assessment.status).toBe('ready');
107
+ expect(assessment.score).toBeGreaterThan(90);
108
+ expect(assessment.blockingReasons).toEqual([]);
109
+ expect(assessment.supportingRuleIds).toEqual(['R-001', 'R-002']);
110
+ });
111
+
112
+ it('keeps a principle on watch when coverage is mixed but the lower layer is partially absorbing it', () => {
113
+ const assessment = assessDeprecatedReadiness(
114
+ createPrincipleEvidence([
115
+ createRuleEvidence('R-001'),
116
+ createRuleEvidence('R-002', {
117
+ replayEvidence: {
118
+ reportCount: 1,
119
+ latestReports: [],
120
+ painNegative: { total: 4, passed: 3, failed: 1 },
121
+ successPositive: { total: 4, passed: 4, failed: 0 },
122
+ principleAnchor: { total: 4, passed: 2, failed: 2 },
123
+ passingImplementationIds: [],
124
+ failingImplementationIds: ['IMPL-R-002'],
125
+ needsReviewImplementationIds: [],
126
+ },
127
+ liveEvidence: {
128
+ activeCount: 1,
129
+ candidateCount: 0,
130
+ disabledCount: 0,
131
+ archivedCount: 0,
132
+ durablePenaltyCount: 0,
133
+ rollbackEvidenceCount: 0,
134
+ hasActiveImplementation: true,
135
+ hasPassingActiveImplementation: false,
136
+ },
137
+ lineageEvidence: {
138
+ records: [],
139
+ distinctPainSignalCount: 1,
140
+ distinctGateBlockCount: 0,
141
+ repeatedErrorSignal: 1,
142
+ },
143
+ }),
144
+ ]),
145
+ );
146
+
147
+ expect(assessment.status).toBe('watch');
148
+ expect(assessment.score).toBeGreaterThanOrEqual(55);
149
+ expect(assessment.blockingReasons).toContain('Repeated related errors have not fallen enough yet.');
150
+ expect(assessment.supportingRuleIds).toEqual(['R-001']);
151
+ });
152
+
153
+ it('returns not-ready with explicit blocking reasons instead of mutating principle status', () => {
154
+ const assessment = assessDeprecatedReadiness(
155
+ createPrincipleEvidence([
156
+ createRuleEvidence('R-001', {
157
+ replayEvidence: {
158
+ reportCount: 1,
159
+ latestReports: [],
160
+ painNegative: { total: 4, passed: 2, failed: 2 },
161
+ successPositive: { total: 4, passed: 1, failed: 3 },
162
+ principleAnchor: { total: 4, passed: 1, failed: 3 },
163
+ passingImplementationIds: [],
164
+ failingImplementationIds: ['IMPL-R-001'],
165
+ needsReviewImplementationIds: [],
166
+ },
167
+ liveEvidence: {
168
+ activeCount: 0,
169
+ candidateCount: 0,
170
+ disabledCount: 1,
171
+ archivedCount: 0,
172
+ durablePenaltyCount: 1,
173
+ rollbackEvidenceCount: 1,
174
+ hasActiveImplementation: false,
175
+ hasPassingActiveImplementation: false,
176
+ },
177
+ lineageEvidence: {
178
+ records: [],
179
+ distinctPainSignalCount: 2,
180
+ distinctGateBlockCount: 1,
181
+ repeatedErrorSignal: 3,
182
+ },
183
+ }),
184
+ ]),
185
+ );
186
+
187
+ expect(assessment.status).toBe('not-ready');
188
+ expect(assessment.blockingReasons).toContain('No active lower-layer implementation is absorbing the principle.');
189
+ expect(assessment.blockingReasons).toContain('False-positive rate remains too high for deprecation readiness.');
190
+ expect(assessment.blockingReasons).toContain('Repeated related errors have not fallen enough yet.');
191
+ expect(assessment.supportingRuleIds).toEqual([]);
192
+ });
193
+ });
@@ -0,0 +1,212 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { recommendInternalizationRoute } from '../../../src/core/principle-internalization/internalization-routing-policy.js';
3
+ import type {
4
+ PrincipleLifecycleEvidence,
5
+ RuleLifecycleEvidence,
6
+ } from '../../../src/core/principle-internalization/lifecycle-read-model.js';
7
+
8
+ function createRuleEvidence(id: string, overrides: Partial<RuleLifecycleEvidence> = {}): RuleLifecycleEvidence {
9
+ return {
10
+ rule: {
11
+ id,
12
+ version: 1,
13
+ name: id,
14
+ description: id,
15
+ type: 'hook',
16
+ triggerCondition: 'tool=write',
17
+ enforcement: 'block',
18
+ action: 'block risky write',
19
+ principleId: 'P-001',
20
+ status: 'enforced',
21
+ coverageRate: 0,
22
+ falsePositiveRate: 0,
23
+ implementationIds: [`IMPL-${id}`],
24
+ createdAt: '2026-04-08T00:00:00.000Z',
25
+ updatedAt: '2026-04-08T00:00:00.000Z',
26
+ },
27
+ implementations: [],
28
+ replayEvidence: {
29
+ reportCount: 0,
30
+ latestReports: [],
31
+ painNegative: { total: 0, passed: 0, failed: 0 },
32
+ successPositive: { total: 0, passed: 0, failed: 0 },
33
+ principleAnchor: { total: 0, passed: 0, failed: 0 },
34
+ passingImplementationIds: [],
35
+ failingImplementationIds: [],
36
+ needsReviewImplementationIds: [],
37
+ },
38
+ liveEvidence: {
39
+ activeCount: 0,
40
+ candidateCount: 0,
41
+ disabledCount: 0,
42
+ archivedCount: 0,
43
+ durablePenaltyCount: 0,
44
+ rollbackEvidenceCount: 0,
45
+ hasActiveImplementation: false,
46
+ hasPassingActiveImplementation: false,
47
+ },
48
+ lineageEvidence: {
49
+ records: [],
50
+ distinctPainSignalCount: 0,
51
+ distinctGateBlockCount: 0,
52
+ repeatedErrorSignal: 0,
53
+ },
54
+ ...overrides,
55
+ };
56
+ }
57
+
58
+ function createPrincipleEvidence(
59
+ rules: RuleLifecycleEvidence[],
60
+ overrides: Partial<PrincipleLifecycleEvidence> = {},
61
+ ): PrincipleLifecycleEvidence {
62
+ return {
63
+ principle: {
64
+ id: 'P-001',
65
+ version: 1,
66
+ text: 'Do the cheapest safe thing first',
67
+ triggerPattern: 'write',
68
+ action: 'prefer cheap safe fixes',
69
+ status: 'active',
70
+ priority: 'P1',
71
+ scope: 'general',
72
+ evaluability: 'weak_heuristic',
73
+ valueScore: 0,
74
+ adherenceRate: 0,
75
+ painPreventedCount: 0,
76
+ derivedFromPainIds: [],
77
+ ruleIds: rules.map((rule) => rule.rule.id),
78
+ conflictsWithPrincipleIds: [],
79
+ createdAt: '2026-04-08T00:00:00.000Z',
80
+ updatedAt: '2026-04-08T00:00:00.000Z',
81
+ },
82
+ rules,
83
+ summary: {
84
+ replayReportCount: rules.reduce((sum, rule) => sum + rule.replayEvidence.reportCount, 0),
85
+ activeImplementationCount: rules.reduce((sum, rule) => sum + rule.liveEvidence.activeCount, 0),
86
+ candidateImplementationCount: rules.reduce((sum, rule) => sum + rule.liveEvidence.candidateCount, 0),
87
+ disabledImplementationCount: rules.reduce((sum, rule) => sum + rule.liveEvidence.disabledCount, 0),
88
+ archivedImplementationCount: rules.reduce((sum, rule) => sum + rule.liveEvidence.archivedCount, 0),
89
+ distinctPainSignalCount: rules.reduce((sum, rule) => sum + rule.lineageEvidence.distinctPainSignalCount, 0),
90
+ distinctGateBlockCount: rules.reduce((sum, rule) => sum + rule.lineageEvidence.distinctGateBlockCount, 0),
91
+ repeatedErrorSignal: rules.reduce((sum, rule) => sum + rule.lineageEvidence.repeatedErrorSignal, 0),
92
+ },
93
+ ...overrides,
94
+ };
95
+ }
96
+
97
+ describe('internalization-routing-policy', () => {
98
+ it('recommends code for deterministic high-risk principles with repeated failures and sufficient replay evidence', () => {
99
+ const recommendation = recommendInternalizationRoute(
100
+ createPrincipleEvidence(
101
+ [
102
+ createRuleEvidence('R-001', {
103
+ replayEvidence: {
104
+ reportCount: 2,
105
+ latestReports: [],
106
+ painNegative: { total: 4, passed: 2, failed: 2 },
107
+ successPositive: { total: 2, passed: 2, failed: 0 },
108
+ principleAnchor: { total: 2, passed: 1, failed: 1 },
109
+ passingImplementationIds: [],
110
+ failingImplementationIds: ['IMPL-R-001'],
111
+ needsReviewImplementationIds: [],
112
+ },
113
+ liveEvidence: {
114
+ activeCount: 1,
115
+ candidateCount: 0,
116
+ disabledCount: 0,
117
+ archivedCount: 0,
118
+ durablePenaltyCount: 0,
119
+ rollbackEvidenceCount: 0,
120
+ hasActiveImplementation: true,
121
+ hasPassingActiveImplementation: false,
122
+ },
123
+ lineageEvidence: {
124
+ records: [],
125
+ distinctPainSignalCount: 2,
126
+ distinctGateBlockCount: 1,
127
+ repeatedErrorSignal: 3,
128
+ },
129
+ }),
130
+ ],
131
+ {
132
+ principle: {
133
+ ...createPrincipleEvidence([]).principle,
134
+ priority: 'P0',
135
+ evaluability: 'deterministic',
136
+ ruleIds: ['R-001'],
137
+ },
138
+ summary: {
139
+ replayReportCount: 2,
140
+ activeImplementationCount: 1,
141
+ candidateImplementationCount: 0,
142
+ disabledImplementationCount: 0,
143
+ archivedImplementationCount: 0,
144
+ distinctPainSignalCount: 2,
145
+ distinctGateBlockCount: 1,
146
+ repeatedErrorSignal: 3,
147
+ },
148
+ },
149
+ ),
150
+ );
151
+
152
+ expect(recommendation.route).toBe('code');
153
+ expect(recommendation.reasonCodes).toContain('deterministic_or_high_risk');
154
+ expect(recommendation.reasonCodes).toContain('repeated_errors_continue');
155
+ expect(recommendation.nextAction).toContain('manual');
156
+ });
157
+
158
+ it('recommends skill when a cheaper non-code path is viable', () => {
159
+ const recommendation = recommendInternalizationRoute(
160
+ createPrincipleEvidence([
161
+ createRuleEvidence('R-001', {
162
+ rule: {
163
+ ...createRuleEvidence('R-001').rule,
164
+ type: 'skill',
165
+ enforcement: 'warn',
166
+ },
167
+ replayEvidence: {
168
+ reportCount: 1,
169
+ latestReports: [],
170
+ painNegative: { total: 3, passed: 2, failed: 1 },
171
+ successPositive: { total: 3, passed: 3, failed: 0 },
172
+ principleAnchor: { total: 1, passed: 1, failed: 0 },
173
+ passingImplementationIds: [],
174
+ failingImplementationIds: [],
175
+ needsReviewImplementationIds: ['IMPL-R-001'],
176
+ },
177
+ liveEvidence: {
178
+ activeCount: 0,
179
+ candidateCount: 0,
180
+ disabledCount: 0,
181
+ archivedCount: 0,
182
+ durablePenaltyCount: 0,
183
+ rollbackEvidenceCount: 0,
184
+ hasActiveImplementation: false,
185
+ hasPassingActiveImplementation: false,
186
+ },
187
+ lineageEvidence: {
188
+ records: [],
189
+ distinctPainSignalCount: 1,
190
+ distinctGateBlockCount: 0,
191
+ repeatedErrorSignal: 1,
192
+ },
193
+ }),
194
+ ]),
195
+ );
196
+
197
+ expect(recommendation.route).toBe('skill');
198
+ expect(recommendation.reasonCodes).toContain('cheapest_viable_skill');
199
+ expect(recommendation.reasonCodes).toContain('no_hard_boundary_required');
200
+ expect(recommendation.nextAction).toContain('skill or prompt-level');
201
+ });
202
+
203
+ it('recommends defer when evidence is too sparse to choose a route confidently', () => {
204
+ const recommendation = recommendInternalizationRoute(
205
+ createPrincipleEvidence([createRuleEvidence('R-001')]),
206
+ );
207
+
208
+ expect(recommendation.route).toBe('defer');
209
+ expect(recommendation.reasonCodes).toEqual(['sparse_evidence']);
210
+ expect(recommendation.nextAction).toContain('Collect more replay evidence');
211
+ });
212
+ });