principles-disciple 1.34.2 → 1.35.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.
- package/.dependency-cruiser.json +19 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +6 -3
- package/src/config/defaults/runtime.ts +100 -24
- package/src/core/event-log.ts +87 -20
- package/src/core/nocturnal-candidate-scoring.ts +6 -6
- package/src/core/nocturnal-trinity-types.ts +94 -0
- package/src/core/nocturnal-trinity.ts +35 -99
- package/src/core/session-tracker.ts +7 -6
- package/src/core/system-logger.ts +104 -12
- package/src/core/workspace-dir-service.ts +40 -6
- package/src/core/workspace-dir-validation.ts +5 -37
- package/src/hooks/trajectory-collector.ts +7 -7
- package/src/index.ts +8 -68
- package/src/service/central-sync-service.ts +3 -8
- package/src/service/correction-observer-workflow-manager.ts +2 -2
- package/src/service/evolution-worker.ts +14 -28
- package/src/service/nocturnal-service.ts +62 -43
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +4 -4
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +4 -4
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +2 -2
- package/src/service/subagent-workflow/types.ts +69 -3
- package/src/utils/shadow-fingerprint.ts +42 -0
- package/src/utils/workspace-resolver.ts +54 -0
- package/tests/core/workspace-dir-validation.test.ts +1 -1
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +3 -3
- package/vitest.config.ts +53 -6
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"forbidden": [
|
|
3
|
+
{
|
|
4
|
+
"name": "no-circular",
|
|
5
|
+
"severity": "error",
|
|
6
|
+
"comment": "Cycles hurt maintainability and testability.",
|
|
7
|
+
"from": {},
|
|
8
|
+
"to": {
|
|
9
|
+
"circular": true
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"options": {
|
|
14
|
+
"tsPreCompilationDeps": true,
|
|
15
|
+
"enhancedResolveOptions": {
|
|
16
|
+
"extensions": [".ts", ".tsx", ".js", ".jsx", ".json"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "principles-disciple",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"description": "Native OpenClaw plugin for Principles Disciple",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/bundle.js",
|
|
@@ -30,8 +30,11 @@
|
|
|
30
30
|
"build:web": "node scripts/build-web.mjs",
|
|
31
31
|
"build:bundle": "node esbuild.config.js && node scripts/build-web.mjs",
|
|
32
32
|
"build:production": "node esbuild.config.js --production && node scripts/build-web.mjs --production && node scripts/verify-build.mjs",
|
|
33
|
-
"test": "vitest run",
|
|
34
|
-
"test:
|
|
33
|
+
"test": "vitest run --project=unit",
|
|
34
|
+
"test:unit": "vitest run --project=unit",
|
|
35
|
+
"test:integration": "vitest run --project=integration",
|
|
36
|
+
"test:coverage": "vitest run --project=unit --coverage",
|
|
37
|
+
"test:all": "vitest run",
|
|
35
38
|
"lint": "eslint src/",
|
|
36
39
|
"bootstrap-rules": "node scripts/bootstrap-rules.mjs",
|
|
37
40
|
"validate-live-path": "tsx scripts/validate-live-path.ts"
|
|
@@ -1,10 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized Runtime Defaults
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* All runtime-related constants that were previously scattered across modules.
|
|
5
5
|
* Centralizing these makes it easier to tune behavior and understand limits.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
// ── Time Constants ──────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
/** Milliseconds per second */
|
|
11
|
+
export const MS_PER_SECOND = 1000;
|
|
12
|
+
|
|
13
|
+
/** Seconds per minute */
|
|
14
|
+
export const SECONDS_PER_MINUTE = 60;
|
|
15
|
+
|
|
16
|
+
/** Minutes per hour */
|
|
17
|
+
export const MINUTES_PER_HOUR = 60;
|
|
18
|
+
|
|
19
|
+
/** Hours per day */
|
|
20
|
+
export const HOURS_PER_DAY = 24;
|
|
21
|
+
|
|
22
|
+
/** Days per week */
|
|
23
|
+
export const DAYS_PER_WEEK = 7;
|
|
24
|
+
|
|
25
|
+
/** One minute in milliseconds */
|
|
26
|
+
export const ONE_MINUTE_MS = SECONDS_PER_MINUTE * MS_PER_SECOND;
|
|
27
|
+
|
|
28
|
+
/** One hour in milliseconds */
|
|
29
|
+
export const ONE_HOUR_MS = MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND;
|
|
30
|
+
|
|
31
|
+
/** One day in milliseconds */
|
|
32
|
+
export const ONE_DAY_MS = HOURS_PER_DAY * ONE_HOUR_MS;
|
|
33
|
+
|
|
34
|
+
/** One week in milliseconds */
|
|
35
|
+
export const ONE_WEEK_MS = DAYS_PER_WEEK * ONE_DAY_MS;
|
|
36
|
+
|
|
37
|
+
// ── Workflow TTL & Timeouts ────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/** Default TTL for helper workflows (5 minutes) */
|
|
40
|
+
export const WORKFLOW_TTL_MS = 5 * ONE_MINUTE_MS;
|
|
41
|
+
|
|
42
|
+
/** Default workflow timeout (15 minutes) */
|
|
43
|
+
export const WORKFLOW_TIMEOUT_MS = 15 * ONE_MINUTE_MS;
|
|
44
|
+
|
|
45
|
+
/** Default workflow sweep interval (30 minutes) */
|
|
46
|
+
export const WORKFLOW_SWEEP_MS = 30 * ONE_MINUTE_MS;
|
|
47
|
+
|
|
48
|
+
// ── Trajectory Gate Block Retry Settings ──────────────────────────────────────
|
|
49
|
+
|
|
8
50
|
/**
|
|
9
51
|
* Trajectory gate block retry settings
|
|
10
52
|
* Used when trajectory recording fails and needs to retry
|
|
@@ -12,40 +54,74 @@
|
|
|
12
54
|
export const TRAJECTORY_GATE_BLOCK_RETRY_DELAY_MS = 250;
|
|
13
55
|
export const TRAJECTORY_GATE_BLOCK_MAX_RETRIES = 3;
|
|
14
56
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export const THINKING_CHECKPOINT_WINDOW_MS = 5 * 60 * 1000; // 5 minutes
|
|
57
|
+
// ── Thinking Checkpoint Defaults (P-10) ───────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
export const THINKING_CHECKPOINT_WINDOW_MS = 5 * ONE_MINUTE_MS;
|
|
19
60
|
export const THINKING_CHECKPOINT_DEFAULT_HIGH_RISK_TOOLS = [
|
|
20
61
|
'run_shell_command',
|
|
21
|
-
'delete_file',
|
|
62
|
+
'delete_file',
|
|
22
63
|
'move_file',
|
|
23
64
|
] as const;
|
|
24
65
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*/
|
|
66
|
+
// ── GFI Gate Thresholds ───────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
/** Large change threshold for GFI gate adjustments */
|
|
28
69
|
export const GFI_LARGE_CHANGE_LINES = 50;
|
|
29
70
|
|
|
30
|
-
/**
|
|
31
|
-
* Agent spawn GFI threshold (critically high = no spawn)
|
|
32
|
-
*/
|
|
71
|
+
/** Agent spawn GFI threshold (critically high = no spawn) */
|
|
33
72
|
export const AGENT_SPAWN_GFI_THRESHOLD = 90;
|
|
34
73
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*/
|
|
38
|
-
export const EVOLUTION_WORKER_POLL_INTERVAL_MS = 15 *
|
|
74
|
+
// ── Evolution Worker Settings ───────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
/** Evolution worker polling interval (15 minutes) */
|
|
77
|
+
export const EVOLUTION_WORKER_POLL_INTERVAL_MS = 15 * ONE_MINUTE_MS;
|
|
78
|
+
|
|
79
|
+
/** Evolution queue batch size */
|
|
39
80
|
export const EVOLUTION_QUEUE_BATCH_SIZE = 10;
|
|
40
81
|
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
82
|
+
/** Pain queue dedup window (30 minutes) */
|
|
83
|
+
export const PAIN_QUEUE_DEDUP_WINDOW_MS = 30 * ONE_MINUTE_MS;
|
|
84
|
+
|
|
85
|
+
// ── Session Tracker Settings ───────────────────────────────────────────────────
|
|
86
|
+
|
|
44
87
|
export const SESSION_TOKEN_WARNING_THRESHOLD = 8000;
|
|
45
|
-
export const SESSION_MAX_IDLE_MS = 30 *
|
|
88
|
+
export const SESSION_MAX_IDLE_MS = 30 * ONE_MINUTE_MS;
|
|
89
|
+
|
|
90
|
+
// ── Event Log Buffer Settings ───────────────────────────────────────────────────
|
|
46
91
|
|
|
47
|
-
/**
|
|
48
|
-
* Event log buffer settings
|
|
49
|
-
*/
|
|
50
92
|
export const EVENT_LOG_BUFFER_SIZE = 20;
|
|
51
|
-
export const EVENT_LOG_FLUSH_INTERVAL_MS = 30 *
|
|
93
|
+
export const EVENT_LOG_FLUSH_INTERVAL_MS = 30 * ONE_MINUTE_MS;
|
|
94
|
+
|
|
95
|
+
// ── Default Busy Timeout ───────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
/** Default busy timeout for SQLite operations (5 seconds) */
|
|
98
|
+
export const DEFAULT_BUSY_TIMEOUT_MS = 5 * MS_PER_SECOND;
|
|
99
|
+
|
|
100
|
+
// ── Nocturnal Runtime Settings ─────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
/** Idle threshold (30 minutes) */
|
|
103
|
+
export const DEFAULT_IDLE_THRESHOLD_MS = 30 * ONE_MINUTE_MS;
|
|
104
|
+
|
|
105
|
+
/** Quota window (24 hours) */
|
|
106
|
+
export const DEFAULT_QUOTA_WINDOW_MS = ONE_DAY_MS;
|
|
107
|
+
|
|
108
|
+
/** Cool down period (30 minutes) */
|
|
109
|
+
export const DEFAULT_COOLDOWN_MS = 30 * ONE_MINUTE_MS;
|
|
110
|
+
|
|
111
|
+
// ── String & Size Limits ────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
/** Max string length for trajectory/event logs */
|
|
114
|
+
export const MAX_STRING_LENGTH = 1000;
|
|
115
|
+
|
|
116
|
+
/** Default max file size (10MB) */
|
|
117
|
+
export const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
118
|
+
|
|
119
|
+
// ── Workflow TTL Settings ───────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
/** Deep-reflect workflow TTL (10 minutes) */
|
|
122
|
+
export const DEEP_REFLECT_TTL_MS = 10 * ONE_MINUTE_MS;
|
|
123
|
+
|
|
124
|
+
// ── Time Window Constants ───────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
/** Two hours in milliseconds */
|
|
127
|
+
export const TWO_HOURS_MS = 2 * ONE_HOUR_MS;
|
package/src/core/event-log.ts
CHANGED
|
@@ -23,31 +23,96 @@ import type { PluginLogger } from '../openclaw-sdk.js';
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* EventLog - Structured event logging with daily statistics aggregation.
|
|
26
|
+
*
|
|
27
|
+
* Log files are date-stamped: events_YYYY-MM-DD.jsonl
|
|
28
|
+
* Old event files are automatically cleaned up based on retention policy.
|
|
26
29
|
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Event log retention in days.
|
|
33
|
+
* Files older than this are deleted on cleanup.
|
|
34
|
+
*/
|
|
35
|
+
const EVENT_LOG_RETENTION_DAYS = 7;
|
|
36
|
+
|
|
27
37
|
export class EventLog {
|
|
28
|
-
private readonly
|
|
38
|
+
private readonly logsDir: string;
|
|
29
39
|
private readonly statsFile: string;
|
|
30
40
|
private readonly logger?: PluginLogger;
|
|
31
|
-
|
|
41
|
+
|
|
32
42
|
private readonly statsCache: Map<string, DailyStats> = new Map();
|
|
33
43
|
private eventBuffer: EventLogEntry[] = [];
|
|
34
44
|
private readonly maxBufferSize = 20;
|
|
35
45
|
private readonly flushIntervalMs = 30000;
|
|
36
46
|
private flushTimer?: ReturnType<typeof setInterval>;
|
|
37
|
-
|
|
47
|
+
|
|
48
|
+
// Cached event file path for current date
|
|
49
|
+
private currentEventsFile: string | undefined;
|
|
50
|
+
private currentDate: string | undefined;
|
|
51
|
+
|
|
38
52
|
constructor(stateDir: string, logger?: PluginLogger) {
|
|
39
|
-
|
|
40
|
-
if (!fs.existsSync(logsDir)) {
|
|
41
|
-
fs.mkdirSync(logsDir, { recursive: true });
|
|
53
|
+
this.logsDir = path.join(stateDir, 'logs');
|
|
54
|
+
if (!fs.existsSync(this.logsDir)) {
|
|
55
|
+
fs.mkdirSync(this.logsDir, { recursive: true });
|
|
42
56
|
}
|
|
43
|
-
|
|
44
|
-
this.
|
|
45
|
-
this.statsFile = path.join(logsDir, 'daily-stats.json');
|
|
57
|
+
|
|
58
|
+
this.statsFile = path.join(this.logsDir, 'daily-stats.json');
|
|
46
59
|
this.logger = logger;
|
|
47
|
-
|
|
60
|
+
|
|
48
61
|
this.loadStats();
|
|
49
62
|
this.startFlushTimer();
|
|
50
63
|
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get the event file path for a given date.
|
|
67
|
+
*/
|
|
68
|
+
private getEventsFile(date: string): string {
|
|
69
|
+
return path.join(this.logsDir, `events_${date}.jsonl`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get today's date string (YYYY-MM-DD).
|
|
74
|
+
*/
|
|
75
|
+
private getTodayStr(): string {
|
|
76
|
+
return new Date().toISOString().split('T')[0];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Ensure we have the correct events file for today's date.
|
|
81
|
+
*/
|
|
82
|
+
private ensureEventsFile(): string {
|
|
83
|
+
const today = this.getTodayStr();
|
|
84
|
+
if (this.currentDate !== today || !this.currentEventsFile) {
|
|
85
|
+
this.currentDate = today;
|
|
86
|
+
this.currentEventsFile = this.getEventsFile(today);
|
|
87
|
+
// Run cleanup if date changed
|
|
88
|
+
this.cleanupOldEventFiles(today);
|
|
89
|
+
}
|
|
90
|
+
return this.currentEventsFile;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Clean up event files older than EVENT_LOG_RETENTION_DAYS.
|
|
95
|
+
*/
|
|
96
|
+
private cleanupOldEventFiles(today: string): void {
|
|
97
|
+
if (EVENT_LOG_RETENTION_DAYS <= 0) return;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const cutoffMs = Date.now() - EVENT_LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000;
|
|
101
|
+
const files = fs.readdirSync(this.logsDir);
|
|
102
|
+
|
|
103
|
+
for (const file of files) {
|
|
104
|
+
if (!file.startsWith('events_') || !file.endsWith('.jsonl')) continue;
|
|
105
|
+
|
|
106
|
+
const filePath = path.join(this.logsDir, file);
|
|
107
|
+
const stat = fs.statSync(filePath);
|
|
108
|
+
if (stat.mtimeMs < cutoffMs) {
|
|
109
|
+
fs.unlinkSync(filePath);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// Silently fail cleanup
|
|
114
|
+
}
|
|
115
|
+
}
|
|
51
116
|
|
|
52
117
|
recordToolCall(sessionId: string | undefined, data: ToolCallEventData): void {
|
|
53
118
|
const category = data.error ? 'failure' : 'success';
|
|
@@ -108,7 +173,7 @@ export class EventLog {
|
|
|
108
173
|
}
|
|
109
174
|
|
|
110
175
|
|
|
111
|
-
|
|
176
|
+
|
|
112
177
|
private record(
|
|
113
178
|
type: EventType,
|
|
114
179
|
category: EventCategory,
|
|
@@ -136,7 +201,7 @@ export class EventLog {
|
|
|
136
201
|
}
|
|
137
202
|
|
|
138
203
|
|
|
139
|
-
|
|
204
|
+
|
|
140
205
|
private formatDate(date: Date): string {
|
|
141
206
|
return date.toISOString().split('T')[0];
|
|
142
207
|
}
|
|
@@ -246,7 +311,7 @@ export class EventLog {
|
|
|
246
311
|
}
|
|
247
312
|
|
|
248
313
|
|
|
249
|
-
|
|
314
|
+
|
|
250
315
|
private getEventDedupKey(entry: EventLogEntry): string {
|
|
251
316
|
const eventId = typeof (entry.data as { eventId?: unknown } | undefined)?.eventId === 'string'
|
|
252
317
|
? String((entry.data as { eventId?: string }).eventId)
|
|
@@ -268,10 +333,11 @@ export class EventLog {
|
|
|
268
333
|
}
|
|
269
334
|
|
|
270
335
|
private readPersistedEvents(): EventLogEntry[] {
|
|
271
|
-
|
|
336
|
+
const eventsFile = this.ensureEventsFile();
|
|
337
|
+
if (!fs.existsSync(eventsFile)) return [];
|
|
272
338
|
|
|
273
339
|
try {
|
|
274
|
-
const content = fs.readFileSync(
|
|
340
|
+
const content = fs.readFileSync(eventsFile, 'utf-8');
|
|
275
341
|
return content
|
|
276
342
|
.trim()
|
|
277
343
|
.split('\n')
|
|
@@ -285,7 +351,7 @@ export class EventLog {
|
|
|
285
351
|
})
|
|
286
352
|
.filter((entry): entry is EventLogEntry => entry !== null);
|
|
287
353
|
} catch (e) {
|
|
288
|
-
if (this.logger) this.logger.error(`[PD] Failed to read events
|
|
354
|
+
if (this.logger) this.logger.error(`[PD] Failed to read events file: ${String(e)}`);
|
|
289
355
|
return [];
|
|
290
356
|
}
|
|
291
357
|
}
|
|
@@ -300,13 +366,14 @@ export class EventLog {
|
|
|
300
366
|
|
|
301
367
|
private flushEvents(): void {
|
|
302
368
|
if (this.eventBuffer.length === 0) return;
|
|
303
|
-
|
|
369
|
+
|
|
370
|
+
const eventsFile = this.ensureEventsFile();
|
|
304
371
|
const lines = this.eventBuffer.map(e => JSON.stringify(e)).join('\n') + '\n';
|
|
305
372
|
try {
|
|
306
|
-
fs.appendFileSync(
|
|
373
|
+
fs.appendFileSync(eventsFile, lines, 'utf-8');
|
|
307
374
|
this.eventBuffer = [];
|
|
308
375
|
} catch (e) {
|
|
309
|
-
if (this.logger) this.logger.error(`[PD] Failed to flush events
|
|
376
|
+
if (this.logger) this.logger.error(`[PD] Failed to flush events: ${String(e)}`);
|
|
310
377
|
}
|
|
311
378
|
}
|
|
312
379
|
|
|
@@ -464,7 +531,7 @@ export class EventLog {
|
|
|
464
531
|
* Returns the rolled back score, or 0 if event not found.
|
|
465
532
|
*/
|
|
466
533
|
|
|
467
|
-
|
|
534
|
+
|
|
468
535
|
rollbackEmpathyEvent(eventId: string, sessionId: string | undefined, reason: string, triggeredBy: 'user_command' | 'natural_language' | 'system'): number {
|
|
469
536
|
const allEvents = this.getMergedEvents();
|
|
470
537
|
let foundEvent: { entry: EventLogEntry; data: PainSignalEventData } | null = null;
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* PHASE 6 ONLY — No real training, no automatic deployment
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import type { DreamerCandidate, PhilosopherJudgment } from './nocturnal-trinity.js';
|
|
26
|
+
import type { DreamerCandidate, PhilosopherJudgment } from './nocturnal-trinity-types.js';
|
|
27
27
|
import type { ThresholdValues } from './adaptive-thresholds.js';
|
|
28
28
|
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
@@ -293,7 +293,7 @@ export function validateCandidateDiversity(
|
|
|
293
293
|
|
|
294
294
|
for (let i = 0; i < candidates.length; i++) {
|
|
295
295
|
for (let j = i + 1; j < candidates.length; j++) {
|
|
296
|
-
|
|
296
|
+
|
|
297
297
|
const overlap = computeKeywordOverlap(
|
|
298
298
|
candidates[i].betterDecision ?? '',
|
|
299
299
|
candidates[j].betterDecision ?? '',
|
|
@@ -335,9 +335,9 @@ export function validateCandidateDiversity(
|
|
|
335
335
|
* Returns value between 0 and 1.
|
|
336
336
|
*/
|
|
337
337
|
function computeKeywordOverlap(textA: string, textB: string): number {
|
|
338
|
-
|
|
338
|
+
|
|
339
339
|
const wordsA = extractKeywords(textA);
|
|
340
|
-
|
|
340
|
+
|
|
341
341
|
const wordsB = extractKeywords(textB);
|
|
342
342
|
|
|
343
343
|
if (wordsA.length === 0 && wordsB.length === 0) return 0;
|
|
@@ -376,7 +376,7 @@ function extractKeywords(text: string): string[] {
|
|
|
376
376
|
* @returns All scored and ranked candidates
|
|
377
377
|
*/
|
|
378
378
|
|
|
379
|
-
|
|
379
|
+
|
|
380
380
|
export function rankCandidates(
|
|
381
381
|
candidates: DreamerCandidate[],
|
|
382
382
|
judgments: PhilosopherJudgment[],
|
|
@@ -464,7 +464,7 @@ export function rankCandidates(
|
|
|
464
464
|
* @returns Tournament result with winner
|
|
465
465
|
*/
|
|
466
466
|
|
|
467
|
-
|
|
467
|
+
|
|
468
468
|
export function runTournament(
|
|
469
469
|
candidates: DreamerCandidate[],
|
|
470
470
|
judgments: PhilosopherJudgment[],
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nocturnal Trinity Shared Types
|
|
3
|
+
*
|
|
4
|
+
* Types shared between nocturnal-trinity.ts and nocturnal-candidate-scoring.ts.
|
|
5
|
+
* Extracted to break circular dependency.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Dreamer Types
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Individual candidate from Dreamer (alternative decision).
|
|
14
|
+
* Each candidate represents an alternative "better decision" approach.
|
|
15
|
+
*/
|
|
16
|
+
export interface DreamerCandidate {
|
|
17
|
+
/** Unique index for this candidate within the Dreamer output */
|
|
18
|
+
candidateIndex: number;
|
|
19
|
+
/** The bad decision this candidate addresses */
|
|
20
|
+
badDecision: string;
|
|
21
|
+
/** The alternative/better decision */
|
|
22
|
+
betterDecision: string;
|
|
23
|
+
/** Why this alternative is better (brief) */
|
|
24
|
+
rationale: string;
|
|
25
|
+
/** Confidence that this candidate is valid (0-1) */
|
|
26
|
+
confidence: number;
|
|
27
|
+
/** Risk level of this candidate's approach -- LLM-judged per D-02 */
|
|
28
|
+
riskLevel?: "low" | "medium" | "high";
|
|
29
|
+
/** Which strategic perspective this candidate embodies per D-01 */
|
|
30
|
+
strategicPerspective?: "conservative_fix" | "structural_improvement" | "paradigm_shift";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface DreamerOutput {
|
|
34
|
+
/** Whether Dreamer succeeded */
|
|
35
|
+
valid: boolean;
|
|
36
|
+
/** List of candidate corrections */
|
|
37
|
+
candidates: DreamerCandidate[];
|
|
38
|
+
/** Why Dreamer could not generate (if valid === false) */
|
|
39
|
+
reason?: string;
|
|
40
|
+
/** Timestamp of generation */
|
|
41
|
+
generatedAt: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Philosopher Types
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
export interface PhilosopherRiskAssessment {
|
|
49
|
+
/** Estimated probability that this candidate is a false positive (0-1) */
|
|
50
|
+
falsePositiveEstimate: number;
|
|
51
|
+
/** How complex is this candidate to implement */
|
|
52
|
+
implementationComplexity: 'low' | 'medium' | 'high';
|
|
53
|
+
/** Whether implementing this candidate risks breaking existing functionality */
|
|
54
|
+
breakingChangeRisk: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface Philosopher6DScores {
|
|
58
|
+
principleAlignment: number;
|
|
59
|
+
specificity: number;
|
|
60
|
+
actionability: number;
|
|
61
|
+
executability: number;
|
|
62
|
+
safetyImpact: number;
|
|
63
|
+
uxImpact: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PhilosopherJudgment {
|
|
67
|
+
/** Index of the judged candidate (references DreamerCandidate.candidateIndex) */
|
|
68
|
+
candidateIndex: number;
|
|
69
|
+
/** Principle-grounded critique of this candidate */
|
|
70
|
+
critique: string;
|
|
71
|
+
/** Whether this candidate aligns with the target principle */
|
|
72
|
+
principleAligned: boolean;
|
|
73
|
+
/** Ranking score (higher = better, 0-1) */
|
|
74
|
+
score: number;
|
|
75
|
+
/** Rank among all candidates (1 = best) */
|
|
76
|
+
rank: number;
|
|
77
|
+
/** Per-dimension scores (6D evaluation) — informational, not used for tournament ranking */
|
|
78
|
+
scores?: Philosopher6DScores;
|
|
79
|
+
/** Risk assessment for this candidate — informational, consumed by Scribe (Phase 37) */
|
|
80
|
+
risks?: PhilosopherRiskAssessment;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface PhilosopherOutput {
|
|
84
|
+
/** Whether Philosopher succeeded */
|
|
85
|
+
valid: boolean;
|
|
86
|
+
/** Judgments for each candidate */
|
|
87
|
+
judgments: PhilosopherJudgment[];
|
|
88
|
+
/** Overall assessment of the candidate set */
|
|
89
|
+
overallAssessment: string;
|
|
90
|
+
/** Why Philosopher could not judge (if valid === false) */
|
|
91
|
+
reason?: string;
|
|
92
|
+
/** Timestamp of generation */
|
|
93
|
+
generatedAt: string;
|
|
94
|
+
}
|