principles-disciple 1.7.6 → 1.7.8

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 (106) hide show
  1. package/dist/commands/context.js +5 -15
  2. package/dist/commands/evolution-status.js +2 -9
  3. package/dist/commands/export.js +61 -8
  4. package/dist/commands/nocturnal-review.d.ts +24 -0
  5. package/dist/commands/nocturnal-review.js +265 -0
  6. package/dist/commands/nocturnal-rollout.d.ts +27 -0
  7. package/dist/commands/nocturnal-rollout.js +671 -0
  8. package/dist/commands/nocturnal-train.d.ts +25 -0
  9. package/dist/commands/nocturnal-train.js +919 -0
  10. package/dist/commands/pain.js +8 -21
  11. package/dist/constants/tools.d.ts +2 -2
  12. package/dist/constants/tools.js +1 -1
  13. package/dist/core/adaptive-thresholds.d.ts +186 -0
  14. package/dist/core/adaptive-thresholds.js +300 -0
  15. package/dist/core/config.d.ts +2 -38
  16. package/dist/core/config.js +6 -61
  17. package/dist/core/event-log.d.ts +1 -2
  18. package/dist/core/event-log.js +0 -3
  19. package/dist/core/evolution-engine.js +1 -21
  20. package/dist/core/evolution-reducer.d.ts +7 -1
  21. package/dist/core/evolution-reducer.js +56 -4
  22. package/dist/core/evolution-types.d.ts +61 -9
  23. package/dist/core/evolution-types.js +31 -9
  24. package/dist/core/external-training-contract.d.ts +276 -0
  25. package/dist/core/external-training-contract.js +269 -0
  26. package/dist/core/local-worker-routing.d.ts +175 -0
  27. package/dist/core/local-worker-routing.js +525 -0
  28. package/dist/core/model-deployment-registry.d.ts +218 -0
  29. package/dist/core/model-deployment-registry.js +503 -0
  30. package/dist/core/model-training-registry.d.ts +295 -0
  31. package/dist/core/model-training-registry.js +475 -0
  32. package/dist/core/nocturnal-arbiter.d.ts +159 -0
  33. package/dist/core/nocturnal-arbiter.js +534 -0
  34. package/dist/core/nocturnal-candidate-scoring.d.ts +137 -0
  35. package/dist/core/nocturnal-candidate-scoring.js +266 -0
  36. package/dist/core/nocturnal-compliance.d.ts +175 -0
  37. package/dist/core/nocturnal-compliance.js +824 -0
  38. package/dist/core/nocturnal-dataset.d.ts +224 -0
  39. package/dist/core/nocturnal-dataset.js +443 -0
  40. package/dist/core/nocturnal-executability.d.ts +85 -0
  41. package/dist/core/nocturnal-executability.js +331 -0
  42. package/dist/core/nocturnal-export.d.ts +124 -0
  43. package/dist/core/nocturnal-export.js +275 -0
  44. package/dist/core/nocturnal-paths.d.ts +124 -0
  45. package/dist/core/nocturnal-paths.js +214 -0
  46. package/dist/core/nocturnal-trajectory-extractor.d.ts +242 -0
  47. package/dist/core/nocturnal-trajectory-extractor.js +307 -0
  48. package/dist/core/nocturnal-trinity.d.ts +311 -0
  49. package/dist/core/nocturnal-trinity.js +880 -0
  50. package/dist/core/paths.d.ts +6 -0
  51. package/dist/core/paths.js +6 -0
  52. package/dist/core/principle-training-state.d.ts +121 -0
  53. package/dist/core/principle-training-state.js +321 -0
  54. package/dist/core/promotion-gate.d.ts +238 -0
  55. package/dist/core/promotion-gate.js +529 -0
  56. package/dist/core/session-tracker.d.ts +10 -0
  57. package/dist/core/session-tracker.js +14 -0
  58. package/dist/core/shadow-observation-registry.d.ts +217 -0
  59. package/dist/core/shadow-observation-registry.js +308 -0
  60. package/dist/core/training-program.d.ts +233 -0
  61. package/dist/core/training-program.js +433 -0
  62. package/dist/core/trajectory.d.ts +95 -1
  63. package/dist/core/trajectory.js +220 -6
  64. package/dist/core/workspace-context.d.ts +0 -6
  65. package/dist/core/workspace-context.js +0 -12
  66. package/dist/hooks/bash-risk.d.ts +6 -6
  67. package/dist/hooks/bash-risk.js +8 -8
  68. package/dist/hooks/gate-block-helper.js +1 -1
  69. package/dist/hooks/gate.d.ts +1 -1
  70. package/dist/hooks/gate.js +2 -2
  71. package/dist/hooks/gfi-gate.d.ts +3 -3
  72. package/dist/hooks/gfi-gate.js +15 -14
  73. package/dist/hooks/pain.js +6 -9
  74. package/dist/hooks/progressive-trust-gate.d.ts +21 -49
  75. package/dist/hooks/progressive-trust-gate.js +51 -204
  76. package/dist/hooks/prompt.d.ts +11 -11
  77. package/dist/hooks/prompt.js +158 -72
  78. package/dist/hooks/subagent.js +43 -6
  79. package/dist/i18n/commands.js +8 -8
  80. package/dist/index.js +129 -28
  81. package/dist/service/evolution-worker.d.ts +42 -4
  82. package/dist/service/evolution-worker.js +321 -13
  83. package/dist/service/nocturnal-runtime.d.ts +183 -0
  84. package/dist/service/nocturnal-runtime.js +352 -0
  85. package/dist/service/nocturnal-service.d.ts +163 -0
  86. package/dist/service/nocturnal-service.js +787 -0
  87. package/dist/service/nocturnal-target-selector.d.ts +145 -0
  88. package/dist/service/nocturnal-target-selector.js +315 -0
  89. package/dist/service/phase3-input-filter.d.ts +2 -23
  90. package/dist/service/phase3-input-filter.js +3 -27
  91. package/dist/service/runtime-summary-service.d.ts +0 -10
  92. package/dist/service/runtime-summary-service.js +1 -54
  93. package/dist/tools/deep-reflect.js +2 -1
  94. package/dist/types/event-types.d.ts +2 -10
  95. package/dist/types/runtime-summary.d.ts +1 -8
  96. package/dist/types.d.ts +0 -3
  97. package/dist/types.js +0 -2
  98. package/openclaw.plugin.json +1 -1
  99. package/package.json +1 -1
  100. package/templates/langs/en/skills/pd-mentor/SKILL.md +5 -5
  101. package/templates/langs/zh/skills/pd-mentor/SKILL.md +5 -5
  102. package/templates/pain_settings.json +0 -6
  103. package/dist/commands/trust.d.ts +0 -4
  104. package/dist/commands/trust.js +0 -78
  105. package/dist/core/trust-engine.d.ts +0 -96
  106. package/dist/core/trust-engine.js +0 -286
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Nocturnal Target Selector — Principle + Session Selection for Sleep-Mode Reflection
3
+ * ================================================================================
4
+ *
5
+ * PURPOSE: Select one target principle and one violating session for nocturnal
6
+ * reflection processing. This module is a PURE SELECTOR — it does NOT execute
7
+ * reflection or write any artifacts.
8
+ *
9
+ * SELECTION LOGIC:
10
+ * 1. Filter principles to evaluable candidates (not manual_only, not cooldown)
11
+ * 2. Score candidates by: compliance health, violation trend, sample scarcity
12
+ * 3. Select top principle
13
+ * 4. Find violating sessions for that principle
14
+ * 5. Select best violating session by violation density
15
+ *
16
+ * SKIP CONDITIONS (return no-op cleanly):
17
+ * - no_evaluable_principles: No principles with auto-trainable evaluability
18
+ * - all_targets_in_cooldown: All candidates are in cooldown
19
+ * - no_violating_sessions: No sessions show violation signals for selected principle
20
+ * - workspace_not_idle: Workspace is active, nocturnal run not allowed
21
+ * - quota_exhausted: Max runs per window reached
22
+ * - insufficient_snapshot_data: Sessions exist but lack tool calls
23
+ *
24
+ * DESIGN CONSTRAINTS:
25
+ * - This is a PURE SELECTOR — no reflection execution
26
+ * - No raw text exposure
27
+ * - No artifact writing
28
+ * - No真实 subagent 调用
29
+ * - Deterministic based on inputs (no random selection)
30
+ *
31
+ * OUTPUT: NocturnalSelectionResult with:
32
+ * - decision: 'selected' | 'skip'
33
+ * - selectedPrincipleId?: string
34
+ * - selectedSessionId?: string
35
+ * - skipReason?: SkipReason
36
+ * - diagnostics: scoring and filtering details
37
+ */
38
+ import { NocturnalTrajectoryExtractor } from '../core/nocturnal-trajectory-extractor.js';
39
+ import { type IdleCheckResult } from './nocturnal-runtime.js';
40
+ export type SkipReason = 'no_evaluable_principles' | 'all_targets_in_cooldown' | 'no_violating_sessions' | 'workspace_not_idle' | 'quota_exhausted' | 'insufficient_snapshot_data' | 'global_cooldown_active';
41
+ export interface SelectionDiagnostics {
42
+ /** Total evaluable principles found */
43
+ totalEvaluablePrinciples: number;
44
+ /** Principles filtered out by cooldown */
45
+ filteredByCooldown: number;
46
+ /** Principles that passed all filters */
47
+ passedPrinciples: string[];
48
+ /** Violating sessions found for selected principle */
49
+ violatingSessionCount: number;
50
+ /** Violation density of selected session (failures / total tool calls) */
51
+ selectedSessionViolationDensity: number | null;
52
+ /** Score of selected principle */
53
+ selectedPrincipleScore: number | null;
54
+ /** Score breakdown */
55
+ scoringBreakdown: Record<string, number>;
56
+ /** Whether workspace idle check passed */
57
+ idleCheckPassed: boolean;
58
+ /** Whether cooldown check passed */
59
+ cooldownCheckPassed: boolean;
60
+ /** Whether quota check passed */
61
+ quotaCheckPassed: boolean;
62
+ /** Recent pain context used for ranking bias (if available) */
63
+ painContext?: {
64
+ recentPainCount: number;
65
+ recentMaxPainScore: number;
66
+ hasRecentPain: boolean;
67
+ painSource?: string;
68
+ };
69
+ }
70
+ export interface NocturnalSelectionResult {
71
+ /** Whether a target was selected */
72
+ decision: 'selected' | 'skip';
73
+ /** Selected principle ID (if decision === 'selected') */
74
+ selectedPrincipleId?: string;
75
+ /** Selected violating session ID (if decision === 'selected') */
76
+ selectedSessionId?: string;
77
+ /** Reason for skip (if decision === 'skip') */
78
+ skipReason?: SkipReason;
79
+ /** Detailed diagnostics for observability */
80
+ diagnostics: SelectionDiagnostics;
81
+ }
82
+ export interface NocturnalTargetSelectorOptions {
83
+ /**
84
+ * Minimum violation density threshold for a session to be considered "violating".
85
+ * Default: 0.1 (at least 10% of tool calls are violation signals)
86
+ */
87
+ minViolationDensity?: number;
88
+ /**
89
+ * Maximum number of recent sessions to consider for violation matching.
90
+ * Default: 50
91
+ */
92
+ maxSessionCandidates?: number;
93
+ /**
94
+ * Idle threshold override (ms).
95
+ * Default: from nocturnal-runtime DEFAULT_IDLE_THRESHOLD_MS
96
+ */
97
+ idleThresholdMs?: number;
98
+ /**
99
+ * Override idle check result (for testing).
100
+ * If provided, this result is used instead of calling checkWorkspaceIdle.
101
+ */
102
+ idleCheckOverride?: IdleCheckResult;
103
+ /**
104
+ * Recent pain context from evolution worker.
105
+ * When provided, principles related to recent pain signals get ranking bias.
106
+ * This threads recent pain into sleep_reflection targeting without merging task kinds.
107
+ */
108
+ recentPainContext?: {
109
+ mostRecent: {
110
+ score: number;
111
+ source: string;
112
+ reason: string;
113
+ timestamp: string;
114
+ } | null;
115
+ recentPainCount: number;
116
+ recentMaxPainScore: number;
117
+ };
118
+ }
119
+ /**
120
+ * Nocturnal Target Selector.
121
+ *
122
+ * Selects one target principle and one violating session for nocturnal reflection.
123
+ * This is a PURE FUNCTION — no side effects, no artifact writing.
124
+ */
125
+ export declare class NocturnalTargetSelector {
126
+ private readonly extractor;
127
+ private readonly stateDir;
128
+ private readonly workspaceDir;
129
+ private readonly opts;
130
+ private readonly idleCheckOverride?;
131
+ private readonly recentPainContext?;
132
+ constructor(workspaceDir: string, stateDir: string, extractor: NocturnalTrajectoryExtractor, options?: NocturnalTargetSelectorOptions);
133
+ /**
134
+ * Select a target principle and violating session.
135
+ *
136
+ * @returns NocturnalSelectionResult with either selected targets or skip reason
137
+ */
138
+ select(): NocturnalSelectionResult;
139
+ }
140
+ /**
141
+ * Select a nocturnal target (principle + session) with default options.
142
+ *
143
+ * This is a convenience wrapper for the common case.
144
+ */
145
+ export declare function selectNocturnalTarget(workspaceDir: string, stateDir: string, extractor: NocturnalTrajectoryExtractor, options?: NocturnalTargetSelectorOptions): NocturnalSelectionResult;
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Nocturnal Target Selector — Principle + Session Selection for Sleep-Mode Reflection
3
+ * ================================================================================
4
+ *
5
+ * PURPOSE: Select one target principle and one violating session for nocturnal
6
+ * reflection processing. This module is a PURE SELECTOR — it does NOT execute
7
+ * reflection or write any artifacts.
8
+ *
9
+ * SELECTION LOGIC:
10
+ * 1. Filter principles to evaluable candidates (not manual_only, not cooldown)
11
+ * 2. Score candidates by: compliance health, violation trend, sample scarcity
12
+ * 3. Select top principle
13
+ * 4. Find violating sessions for that principle
14
+ * 5. Select best violating session by violation density
15
+ *
16
+ * SKIP CONDITIONS (return no-op cleanly):
17
+ * - no_evaluable_principles: No principles with auto-trainable evaluability
18
+ * - all_targets_in_cooldown: All candidates are in cooldown
19
+ * - no_violating_sessions: No sessions show violation signals for selected principle
20
+ * - workspace_not_idle: Workspace is active, nocturnal run not allowed
21
+ * - quota_exhausted: Max runs per window reached
22
+ * - insufficient_snapshot_data: Sessions exist but lack tool calls
23
+ *
24
+ * DESIGN CONSTRAINTS:
25
+ * - This is a PURE SELECTOR — no reflection execution
26
+ * - No raw text exposure
27
+ * - No artifact writing
28
+ * - No真实 subagent 调用
29
+ * - Deterministic based on inputs (no random selection)
30
+ *
31
+ * OUTPUT: NocturnalSelectionResult with:
32
+ * - decision: 'selected' | 'skip'
33
+ * - selectedPrincipleId?: string
34
+ * - selectedSessionId?: string
35
+ * - skipReason?: SkipReason
36
+ * - diagnostics: scoring and filtering details
37
+ */
38
+ import { listEvaluablePrinciples, } from '../core/principle-training-state.js';
39
+ import { checkWorkspaceIdle, checkCooldown, DEFAULT_IDLE_THRESHOLD_MS, } from './nocturnal-runtime.js';
40
+ import { detectViolation } from '../core/nocturnal-compliance.js';
41
+ /**
42
+ * Score a principle for nocturnal targeting.
43
+ * Higher score = better candidate for reflection.
44
+ *
45
+ * Scoring dimensions:
46
+ * 1. Compliance health (higher compliance = more worth teaching)
47
+ * 2. Violation trend (worsening trend = higher priority)
48
+ * 3. Sample scarcity (fewer samples = more valuable)
49
+ * 4. Cooldown penalty (in cooldown = lower priority)
50
+ * 5. Pain context bias (recent pain = higher priority for related principles)
51
+ *
52
+ * Score range: 0-100 (normalized)
53
+ */
54
+ function scorePrinciple(state, cooldownActive, recentPainContext) {
55
+ let score = 50; // Base score
56
+ // Compliance contribution: 0-25 points
57
+ // High compliance = well-understood principle = good teaching target
58
+ // But very low compliance = confused principle = also needs work
59
+ const complianceContribution = Math.round(state.complianceRate * 25);
60
+ score += complianceContribution;
61
+ // Trend contribution: -10 to +10 points
62
+ // worsening trend (+1) = higher priority = +10
63
+ // improving trend (-1) = lower priority = -10
64
+ // stable (0) = neutral = 0
65
+ score += state.violationTrend * 10;
66
+ // Sample scarcity contribution: 0-15 points
67
+ // Fewer samples generated = more valuable new data
68
+ // Max at 0 samples, decreases as samples accumulate
69
+ const scarcityPenalty = Math.min(state.generatedSampleCount / 10, 1) * 15;
70
+ score += Math.round(15 - scarcityPenalty);
71
+ // Cooldown penalty: -30 points
72
+ if (cooldownActive) {
73
+ score -= 30;
74
+ }
75
+ // Pain context bias: up to +25 points
76
+ // Bias toward principles related to recent pain signals
77
+ if (recentPainContext && recentPainContext.recentPainCount > 0) {
78
+ // Most recent pain score contributes up to 15 points (pain scores are 1-10)
79
+ const mostRecentPainScore = recentPainContext.mostRecent?.score ?? 0;
80
+ const painScoreContribution = Math.round((mostRecentPainScore / 10) * 15);
81
+ score += painScoreContribution;
82
+ // Additional pain count adds up to 5 more points
83
+ const additionalPainContribution = Math.min(recentPainContext.recentPainCount - 1, 5) * 1;
84
+ score += additionalPainContribution;
85
+ // High pain scores (> 7) get extra 5-point boost
86
+ if (recentPainContext.recentMaxPainScore > 7) {
87
+ score += 5;
88
+ }
89
+ }
90
+ return Math.max(0, Math.min(100, score));
91
+ }
92
+ /**
93
+ * Compute violation signal density for a session.
94
+ * Returns 0-1 score (higher = more violation evidence).
95
+ */
96
+ function computeViolationDensity(session) {
97
+ const total = session.toolCallCount;
98
+ if (total === 0)
99
+ return 0;
100
+ // Violation signals: failures, pain events, gate blocks
101
+ const violationSignals = session.failureCount + session.painEventCount * 0.5 + session.gateBlockCount * 0.3;
102
+ return Math.min(violationSignals / total, 1);
103
+ }
104
+ /**
105
+ * Nocturnal Target Selector.
106
+ *
107
+ * Selects one target principle and one violating session for nocturnal reflection.
108
+ * This is a PURE FUNCTION — no side effects, no artifact writing.
109
+ */
110
+ export class NocturnalTargetSelector {
111
+ extractor;
112
+ stateDir;
113
+ workspaceDir;
114
+ opts;
115
+ idleCheckOverride;
116
+ recentPainContext;
117
+ constructor(workspaceDir, stateDir, extractor, options = {}) {
118
+ this.workspaceDir = workspaceDir;
119
+ this.stateDir = stateDir;
120
+ this.extractor = extractor;
121
+ // Destructure so they are NOT included in opts (stored separately)
122
+ const { idleCheckOverride, recentPainContext, ...restOptions } = options;
123
+ this.idleCheckOverride = idleCheckOverride;
124
+ this.recentPainContext = recentPainContext;
125
+ this.opts = {
126
+ minViolationDensity: restOptions.minViolationDensity ?? 0.1,
127
+ maxSessionCandidates: restOptions.maxSessionCandidates ?? 50,
128
+ idleThresholdMs: restOptions.idleThresholdMs ?? DEFAULT_IDLE_THRESHOLD_MS,
129
+ };
130
+ }
131
+ /**
132
+ * Select a target principle and violating session.
133
+ *
134
+ * @returns NocturnalSelectionResult with either selected targets or skip reason
135
+ */
136
+ select() {
137
+ const diagnostics = {
138
+ totalEvaluablePrinciples: 0,
139
+ filteredByCooldown: 0,
140
+ passedPrinciples: [],
141
+ violatingSessionCount: 0,
142
+ selectedSessionViolationDensity: null,
143
+ selectedPrincipleScore: null,
144
+ scoringBreakdown: {},
145
+ idleCheckPassed: false,
146
+ cooldownCheckPassed: false,
147
+ quotaCheckPassed: false,
148
+ painContext: this.recentPainContext ? {
149
+ recentPainCount: this.recentPainContext.recentPainCount,
150
+ recentMaxPainScore: this.recentPainContext.recentMaxPainScore,
151
+ hasRecentPain: this.recentPainContext.recentPainCount > 0,
152
+ painSource: this.recentPainContext.mostRecent?.source,
153
+ } : undefined,
154
+ };
155
+ // Step 1: Idle check — if workspace is not idle, skip
156
+ const idleResult = this.idleCheckOverride ?? checkWorkspaceIdle(this.workspaceDir, {
157
+ idleThresholdMs: this.opts.idleThresholdMs,
158
+ });
159
+ diagnostics.idleCheckPassed = idleResult.isIdle;
160
+ if (!idleResult.isIdle) {
161
+ return {
162
+ decision: 'skip',
163
+ skipReason: 'workspace_not_idle',
164
+ diagnostics,
165
+ };
166
+ }
167
+ // Step 2: Cooldown and quota check
168
+ const cooldownResult = checkCooldown(this.stateDir);
169
+ diagnostics.cooldownCheckPassed = !cooldownResult.globalCooldownActive;
170
+ diagnostics.quotaCheckPassed = !cooldownResult.quotaExhausted;
171
+ if (cooldownResult.globalCooldownActive) {
172
+ return {
173
+ decision: 'skip',
174
+ skipReason: 'global_cooldown_active',
175
+ diagnostics,
176
+ };
177
+ }
178
+ if (cooldownResult.quotaExhausted) {
179
+ return {
180
+ decision: 'skip',
181
+ skipReason: 'quota_exhausted',
182
+ diagnostics,
183
+ };
184
+ }
185
+ // Step 3: Get evaluable principles
186
+ const evaluablePrinciples = listEvaluablePrinciples(this.stateDir);
187
+ diagnostics.totalEvaluablePrinciples = evaluablePrinciples.length;
188
+ if (evaluablePrinciples.length === 0) {
189
+ return {
190
+ decision: 'skip',
191
+ skipReason: 'no_evaluable_principles',
192
+ diagnostics,
193
+ };
194
+ }
195
+ // Step 4: Score and filter candidates
196
+ const scoredPrinciples = [];
197
+ for (const state of evaluablePrinciples) {
198
+ const cooldownCheck = checkCooldown(this.stateDir, state.principleId);
199
+ const cooldownActive = cooldownCheck.principleCooldownActive;
200
+ const score = scorePrinciple(state, cooldownActive, this.recentPainContext);
201
+ if (cooldownActive) {
202
+ diagnostics.filteredByCooldown++;
203
+ }
204
+ scoredPrinciples.push({
205
+ principleId: state.principleId,
206
+ state,
207
+ score,
208
+ violationDensity: state.applicableOpportunityCount > 0
209
+ ? state.observedViolationCount / state.applicableOpportunityCount
210
+ : 0,
211
+ cooldownActive,
212
+ });
213
+ diagnostics.scoringBreakdown[state.principleId] = score;
214
+ }
215
+ // Filter out principles in cooldown (score already penalized but keep them in diagnostics)
216
+ const activeCandidates = scoredPrinciples.filter((p) => !p.cooldownActive);
217
+ if (activeCandidates.length === 0) {
218
+ diagnostics.passedPrinciples = [];
219
+ return {
220
+ decision: 'skip',
221
+ skipReason: 'all_targets_in_cooldown',
222
+ diagnostics,
223
+ };
224
+ }
225
+ // Sort by score descending
226
+ activeCandidates.sort((a, b) => b.score - a.score);
227
+ diagnostics.passedPrinciples = activeCandidates.map((p) => p.principleId);
228
+ // Select the top candidate
229
+ const selected = activeCandidates[0];
230
+ diagnostics.selectedPrincipleScore = selected.score;
231
+ // Step 5: Find violating sessions for the selected principle
232
+ const recentSessions = this.extractor.listRecentNocturnalCandidateSessions({
233
+ limit: this.opts.maxSessionCandidates,
234
+ minToolCalls: 1,
235
+ });
236
+ if (recentSessions.length === 0) {
237
+ return {
238
+ decision: 'skip',
239
+ skipReason: 'insufficient_snapshot_data',
240
+ diagnostics,
241
+ };
242
+ }
243
+ // Compute violation signals for each session
244
+ const violatingSessions = recentSessions.map((session) => {
245
+ const violationDensity = computeViolationDensity(session);
246
+ const snapshot = this.extractor.getNocturnalSessionSnapshot(session.sessionId);
247
+ // Use nocturnal-compliance to detect violations for the selected principle
248
+ let hasViolation = false;
249
+ if (snapshot) {
250
+ const violationResult = detectViolation(selected.principleId, {
251
+ sessionId: session.sessionId,
252
+ toolCalls: snapshot.toolCalls.map((tc) => ({
253
+ toolName: tc.toolName,
254
+ filePath: tc.filePath ?? undefined,
255
+ outcome: tc.outcome,
256
+ errorMessage: tc.errorMessage ?? undefined,
257
+ })),
258
+ painSignals: snapshot.painEvents.map((pe) => ({
259
+ source: pe.source,
260
+ score: pe.score,
261
+ reason: pe.reason ?? undefined,
262
+ })),
263
+ gateBlocks: snapshot.gateBlocks.map((gb) => ({
264
+ toolName: gb.toolName,
265
+ reason: gb.reason,
266
+ })),
267
+ userCorrections: [],
268
+ planApprovals: [],
269
+ });
270
+ hasViolation = violationResult.violated;
271
+ }
272
+ return {
273
+ sessionId: session.sessionId,
274
+ hasViolation,
275
+ violationSeverity: violationDensity,
276
+ failureCount: session.failureCount,
277
+ painEventCount: session.painEventCount,
278
+ gateBlockCount: session.gateBlockCount,
279
+ toolCallCount: session.toolCallCount,
280
+ };
281
+ });
282
+ // Filter to sessions with violations above threshold
283
+ const violating = violatingSessions.filter((s) => s.hasViolation && s.violationSeverity >= this.opts.minViolationDensity);
284
+ diagnostics.violatingSessionCount = violating.length;
285
+ if (violating.length === 0) {
286
+ return {
287
+ decision: 'skip',
288
+ skipReason: 'no_violating_sessions',
289
+ diagnostics,
290
+ };
291
+ }
292
+ // Sort by violation severity descending (most violating first)
293
+ violating.sort((a, b) => b.violationSeverity - a.violationSeverity);
294
+ const selectedSession = violating[0];
295
+ diagnostics.selectedSessionViolationDensity = selectedSession.violationSeverity;
296
+ return {
297
+ decision: 'selected',
298
+ selectedPrincipleId: selected.principleId,
299
+ selectedSessionId: selectedSession.sessionId,
300
+ diagnostics,
301
+ };
302
+ }
303
+ }
304
+ // ---------------------------------------------------------------------------
305
+ // Convenience function
306
+ // ---------------------------------------------------------------------------
307
+ /**
308
+ * Select a nocturnal target (principle + session) with default options.
309
+ *
310
+ * This is a convenience wrapper for the common case.
311
+ */
312
+ export function selectNocturnalTarget(workspaceDir, stateDir, extractor, options = {}) {
313
+ const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor, options);
314
+ return selector.select();
315
+ }
@@ -6,7 +6,6 @@
6
6
  *
7
7
  * Phase 3 eligibility depends ONLY on:
8
8
  * - Queue truth (valid evolution samples from queue)
9
- * - Trust input (frozen trust scorecard)
10
9
  *
11
10
  * Any directive file is ignored for eligibility decisions.
12
11
  *
@@ -23,13 +22,6 @@
23
22
  * - rejected: Invalid or corrupt data
24
23
  */
25
24
  export type Phase3Classification = 'authoritative' | 'reference_only' | 'rejected';
26
- /**
27
- * Classification for trust input state.
28
- * - authoritative: Frozen trust with valid score
29
- * - unknown: Missing trust score (not silently coerced to 0)
30
- * - rejected: Unfrozen trust or invalid schema
31
- */
32
- export type TrustClassification = 'authoritative' | 'unknown' | 'rejected';
33
25
  export interface Phase3EvolutionInput {
34
26
  id?: string | null;
35
27
  status?: string | null;
@@ -37,11 +29,6 @@ export interface Phase3EvolutionInput {
37
29
  completed_at?: string | null;
38
30
  resolution?: string | null;
39
31
  }
40
- export interface Phase3TrustInput {
41
- score?: number | null;
42
- frozen?: boolean | null;
43
- lastUpdated?: string | null;
44
- }
45
32
  export interface Phase3EvolutionSample {
46
33
  taskId: string;
47
34
  status: 'pending' | 'in_progress' | 'completed';
@@ -59,24 +46,17 @@ export interface Phase3RejectedEvolutionSample {
59
46
  status: string | null;
60
47
  reasons: string[];
61
48
  }
62
- export interface Phase3TrustResult {
63
- eligible: boolean;
64
- classification: TrustClassification;
65
- rejectedReasons: string[];
66
- }
67
49
  export interface Phase3InputFilterResult {
68
50
  queueTruthReady: boolean;
69
- trustInputReady: boolean;
70
51
  phase3ShadowEligible: boolean;
71
52
  evolution: {
72
53
  eligible: Phase3EvolutionSample[];
73
54
  referenceOnly: Phase3ReferenceOnlySample[];
74
55
  rejected: Phase3RejectedEvolutionSample[];
75
56
  };
76
- trust: Phase3TrustResult;
77
57
  }
78
58
  /**
79
- * Evaluates Phase 3 readiness based on queue and trust inputs.
59
+ * Evaluates Phase 3 readiness based on queue inputs.
80
60
  *
81
61
  * IMPORTANT: Does NOT use evolution_directive.json.
82
62
  * Directive is compatibility-only display artifact, not a truth source.
@@ -88,7 +68,6 @@ export interface Phase3InputFilterResult {
88
68
  * - rejected: Invalid, corrupt, or policy-prohibited input
89
69
  *
90
70
  * @param queue - Evolution queue items to validate
91
- * @param trust - Trust input (frozen scorecard)
92
71
  * @returns Phase 3 eligibility results
93
72
  */
94
- export declare function evaluatePhase3Inputs(queue: Phase3EvolutionInput[], trust: Phase3TrustInput): Phase3InputFilterResult;
73
+ export declare function evaluatePhase3Inputs(queue: Phase3EvolutionInput[]): Phase3InputFilterResult;
@@ -6,7 +6,6 @@
6
6
  *
7
7
  * Phase 3 eligibility depends ONLY on:
8
8
  * - Queue truth (valid evolution samples from queue)
9
- * - Trust input (frozen trust scorecard)
10
9
  *
11
10
  * Any directive file is ignored for eligibility decisions.
12
11
  *
@@ -61,7 +60,7 @@ function isTimeoutOnlyOutcome(item) {
61
60
  return resolution === 'auto_completed_timeout';
62
61
  }
63
62
  /**
64
- * Evaluates Phase 3 readiness based on queue and trust inputs.
63
+ * Evaluates Phase 3 readiness based on queue inputs.
65
64
  *
66
65
  * IMPORTANT: Does NOT use evolution_directive.json.
67
66
  * Directive is compatibility-only display artifact, not a truth source.
@@ -73,10 +72,9 @@ function isTimeoutOnlyOutcome(item) {
73
72
  * - rejected: Invalid, corrupt, or policy-prohibited input
74
73
  *
75
74
  * @param queue - Evolution queue items to validate
76
- * @param trust - Trust input (frozen scorecard)
77
75
  * @returns Phase 3 eligibility results
78
76
  */
79
- export function evaluatePhase3Inputs(queue, trust) {
77
+ export function evaluatePhase3Inputs(queue) {
80
78
  const eligible = [];
81
79
  const referenceOnly = [];
82
80
  const rejected = [];
@@ -154,22 +152,6 @@ export function evaluatePhase3Inputs(queue, trust) {
154
152
  completedAt,
155
153
  });
156
154
  }
157
- // Trust classification logic
158
- const trustRejectedReasons = [];
159
- const score = typeof trust.score === 'number' && Number.isFinite(trust.score) ? trust.score : null;
160
- let trustClassification = 'authoritative';
161
- if (trust.frozen !== true) {
162
- trustRejectedReasons.push('legacy_or_unfrozen_trust_schema');
163
- trustClassification = 'rejected';
164
- }
165
- if (score === null) {
166
- trustRejectedReasons.push('missing_trust_score');
167
- // Missing score = unknown (unless already rejected for unfrozen)
168
- if (trustClassification !== 'rejected') {
169
- trustClassification = 'unknown';
170
- }
171
- }
172
- const trustInputReady = trustRejectedReasons.length === 0;
173
155
  // Queue is ready when:
174
156
  // 1. Queue has items
175
157
  // 2. No invalid/corrupt items (rejected is empty)
@@ -177,20 +159,14 @@ export function evaluatePhase3Inputs(queue, trust) {
177
159
  // Note: referenceOnly (timeout outcomes) is valid data, just not positive evidence
178
160
  const hasValidData = eligible.length > 0 || referenceOnly.length > 0;
179
161
  const queueTruthReady = queue.length > 0 && rejected.length === 0 && hasValidData;
180
- const phase3ShadowEligible = queueTruthReady && trustInputReady && eligible.length > 0;
162
+ const phase3ShadowEligible = queueTruthReady && eligible.length > 0;
181
163
  return {
182
164
  queueTruthReady,
183
- trustInputReady,
184
165
  phase3ShadowEligible,
185
166
  evolution: {
186
167
  eligible,
187
168
  referenceOnly,
188
169
  rejected,
189
170
  },
190
- trust: {
191
- eligible: trustInputReady,
192
- classification: trustClassification,
193
- rejectedReasons: trustRejectedReasons,
194
- },
195
171
  };
196
172
  }
@@ -31,13 +31,6 @@ export interface RuntimeSummary {
31
31
  sources: RuntimeSummarySource[];
32
32
  dataQuality: RuntimeDataQuality;
33
33
  };
34
- legacyTrust: {
35
- score: number | null;
36
- stage: 1 | 2 | 3 | 4 | null;
37
- frozen: true;
38
- lastUpdated: string | null;
39
- rewardPolicy: RuntimeRewardPolicy;
40
- };
41
34
  evolution: {
42
35
  queue: {
43
36
  pending: number;
@@ -54,14 +47,12 @@ export interface RuntimeSummary {
54
47
  };
55
48
  phase3: {
56
49
  queueTruthReady: boolean;
57
- trustInputReady: boolean;
58
50
  phase3ShadowEligible: boolean;
59
51
  evolutionEligible: number;
60
52
  evolutionReferenceOnly: number;
61
53
  evolutionReferenceOnlyReasons: string[];
62
54
  evolutionRejected: number;
63
55
  evolutionRejectedReasons: string[];
64
- trustRejectedReasons: string[];
65
56
  legacyDirectiveFilePresent: boolean;
66
57
  directiveStatus: 'compatibility-only' | 'missing' | 'present';
67
58
  directiveIgnoredReason: string;
@@ -104,7 +95,6 @@ export declare class RuntimeSummaryService {
104
95
  * Queue is the only authoritative execution truth source.
105
96
  */
106
97
  private static buildDirectiveSummary;
107
- private static readLegacyTrust;
108
98
  private static readEvents;
109
99
  private static buildGfiSources;
110
100
  private static findLastPainSignal;