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.
- package/CHANGELOG.md +26 -0
- package/README.md +66 -0
- package/esbuild.config.js +1 -1
- package/openclaw.plugin.json +4 -4
- package/package.json +2 -3
- package/run-nocturnal.mjs +30 -0
- package/scripts/db-migrate.mjs +170 -0
- package/scripts/sync-plugin.mjs +250 -6
- package/src/commands/archive-impl.ts +136 -0
- package/src/commands/capabilities.ts +4 -2
- package/src/commands/context.ts +5 -1
- package/src/commands/disable-impl.ts +151 -0
- package/src/commands/evolution-status.ts +64 -19
- package/src/commands/export.ts +8 -6
- package/src/commands/focus.ts +8 -20
- package/src/commands/nocturnal-review.ts +5 -7
- package/src/commands/nocturnal-rollout.ts +1 -12
- package/src/commands/nocturnal-train.ts +21 -47
- package/src/commands/pain.ts +10 -5
- package/src/commands/principle-rollback.ts +4 -2
- package/src/commands/promote-impl.ts +274 -0
- package/src/commands/rollback-impl.ts +234 -0
- package/src/commands/rollback.ts +6 -3
- package/src/commands/samples.ts +2 -0
- package/src/commands/thinking-os.ts +3 -4
- package/src/commands/workflow-debug.ts +2 -1
- package/src/config/errors.ts +1 -0
- package/src/core/AGENTS.md +34 -0
- package/src/core/adaptive-thresholds.ts +4 -3
- package/src/core/code-implementation-storage.ts +241 -0
- package/src/core/config.ts +5 -2
- package/src/core/control-ui-db.ts +29 -10
- package/src/core/detection-funnel.ts +12 -7
- package/src/core/diagnostician-task-store.ts +156 -0
- package/src/core/dictionary.ts +4 -4
- package/src/core/empathy-keyword-matcher.ts +7 -3
- package/src/core/empathy-types.ts +13 -2
- package/src/core/event-log.ts +14 -6
- package/src/core/evolution-engine.ts +27 -31
- package/src/core/evolution-logger.ts +3 -2
- package/src/core/evolution-reducer.ts +110 -31
- package/src/core/evolution-types.ts +10 -0
- package/src/core/external-training-contract.ts +1 -0
- package/src/core/focus-history.ts +38 -24
- package/src/core/hygiene/tracker.ts +10 -6
- package/src/core/init.ts +5 -2
- package/src/core/migration.ts +3 -3
- package/src/core/model-deployment-registry.ts +6 -4
- package/src/core/model-training-registry.ts +5 -3
- package/src/core/nocturnal-arbiter.ts +13 -14
- package/src/core/nocturnal-artifact-lineage.ts +117 -0
- package/src/core/nocturnal-artificer.ts +257 -0
- package/src/core/nocturnal-candidate-scoring.ts +4 -2
- package/src/core/nocturnal-compliance.ts +67 -19
- package/src/core/nocturnal-dataset.ts +95 -2
- package/src/core/nocturnal-executability.ts +2 -3
- package/src/core/nocturnal-export.ts +6 -3
- package/src/core/nocturnal-rule-implementation-validator.ts +245 -0
- package/src/core/nocturnal-trajectory-extractor.ts +10 -3
- package/src/core/nocturnal-trinity.ts +319 -61
- package/src/core/pain-context-extractor.ts +29 -15
- package/src/core/pain.ts +7 -5
- package/src/core/path-resolver.ts +16 -15
- package/src/core/paths.ts +2 -1
- package/src/core/pd-task-reconciler.ts +463 -0
- package/src/core/pd-task-service.ts +42 -0
- package/src/core/pd-task-store.ts +77 -0
- package/src/core/pd-task-types.ts +128 -0
- package/src/core/principle-internalization/deprecated-readiness.ts +91 -0
- package/src/core/principle-internalization/internalization-routing-policy.ts +208 -0
- package/src/core/principle-internalization/lifecycle-metrics.ts +149 -0
- package/src/core/principle-internalization/lifecycle-read-model.ts +243 -0
- package/src/core/principle-internalization/lifecycle-refresh.ts +11 -0
- package/src/core/principle-internalization/principle-lifecycle-service.ts +167 -0
- package/src/core/principle-training-state.ts +95 -370
- package/src/core/principle-tree-ledger.ts +733 -0
- package/src/core/principle-tree-migration.ts +195 -0
- package/src/core/profile.ts +3 -1
- package/src/core/promotion-gate.ts +14 -18
- package/src/core/replay-engine.ts +562 -0
- package/src/core/risk-calculator.ts +6 -4
- package/src/core/rule-host-helpers.ts +39 -0
- package/src/core/rule-host-types.ts +82 -0
- package/src/core/rule-host.ts +245 -0
- package/src/core/rule-implementation-runtime.ts +38 -0
- package/src/core/schema/db-types.ts +16 -0
- package/src/core/schema/index.ts +26 -0
- package/src/core/schema/migration-runner.ts +207 -0
- package/src/core/schema/migrations/001-init-trajectory.ts +211 -0
- package/src/core/schema/migrations/002-init-central.ts +122 -0
- package/src/core/schema/migrations/003-init-workflow.ts +55 -0
- package/src/core/schema/migrations/004-add-thinking-and-gfi.ts +74 -0
- package/src/core/schema/migrations/index.ts +31 -0
- package/src/core/schema/schema-definitions.ts +650 -0
- package/src/core/session-tracker.ts +6 -4
- package/src/core/shadow-observation-registry.ts +6 -3
- package/src/core/system-logger.ts +2 -2
- package/src/core/thinking-models.ts +182 -46
- package/src/core/thinking-os-parser.ts +156 -0
- package/src/core/training-program.ts +7 -7
- package/src/core/trajectory.ts +42 -36
- package/src/core/workspace-context.ts +77 -11
- package/src/core/workspace-dir-validation.ts +152 -0
- package/src/hooks/AGENTS.md +31 -0
- package/src/hooks/bash-risk.ts +3 -1
- package/src/hooks/edit-verification.ts +9 -5
- package/src/hooks/gate-block-helper.ts +5 -1
- package/src/hooks/gate.ts +152 -5
- package/src/hooks/gfi-gate.ts +9 -2
- package/src/hooks/lifecycle-routing.ts +124 -0
- package/src/hooks/lifecycle.ts +12 -12
- package/src/hooks/llm.ts +17 -109
- package/src/hooks/message-sanitize.ts +5 -3
- package/src/hooks/pain.ts +19 -15
- package/src/hooks/progressive-trust-gate.ts +7 -1
- package/src/hooks/prompt.ts +169 -60
- package/src/hooks/subagent.ts +5 -4
- package/src/hooks/thinking-checkpoint.ts +2 -0
- package/src/hooks/trajectory-collector.ts +15 -12
- package/src/http/principles-console-route.ts +31 -68
- package/src/i18n/commands.ts +2 -2
- package/src/index.ts +130 -40
- package/src/service/central-database.ts +131 -43
- package/src/service/central-health-service.ts +47 -0
- package/src/service/central-overview-service.ts +135 -0
- package/src/service/central-sync-service.ts +87 -0
- package/src/service/control-ui-query-service.ts +46 -36
- package/src/service/event-log-auditor.ts +261 -0
- package/src/service/evolution-query-service.ts +23 -22
- package/src/service/evolution-worker.ts +565 -261
- package/src/service/health-query-service.ts +213 -36
- package/src/service/nocturnal-runtime.ts +8 -4
- package/src/service/nocturnal-service.ts +503 -59
- package/src/service/nocturnal-target-selector.ts +5 -7
- package/src/service/runtime-summary-service.ts +2 -1
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +25 -336
- package/src/service/subagent-workflow/dynamic-timeout.ts +30 -0
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +48 -386
- package/src/service/subagent-workflow/index.ts +2 -0
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +169 -284
- package/src/service/subagent-workflow/runtime-direct-driver.ts +114 -16
- package/src/service/subagent-workflow/subagent-error-utils.ts +25 -0
- package/src/service/subagent-workflow/types.ts +9 -4
- package/src/service/subagent-workflow/workflow-manager-base.ts +573 -0
- package/src/service/subagent-workflow/workflow-store.ts +71 -11
- package/src/service/trajectory-service.ts +2 -1
- package/src/tools/critique-prompt.ts +1 -1
- package/src/tools/deep-reflect.ts +175 -209
- package/src/tools/model-index.ts +2 -1
- package/src/types/event-types.ts +2 -2
- package/src/types/principle-tree-schema.ts +29 -23
- package/src/utils/file-lock.ts +5 -3
- package/src/utils/io.ts +5 -2
- package/src/utils/nlp.ts +5 -46
- package/src/utils/node-vm-polyfill.ts +11 -0
- package/src/utils/plugin-logger.ts +2 -0
- package/src/utils/retry.ts +572 -0
- package/src/utils/subagent-probe.ts +1 -1
- package/templates/langs/en/core/AGENTS.md +0 -13
- package/templates/langs/en/core/SOUL.md +1 -31
- package/templates/langs/en/core/TOOLS.md +0 -4
- package/templates/langs/en/principles/THINKING_OS.md +77 -0
- package/templates/langs/en/skills/admin/SKILL.md +0 -1
- package/templates/langs/en/skills/evolution-framework-update/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +18 -5
- package/templates/langs/zh/core/AGENTS.md +0 -22
- package/templates/langs/zh/core/SOUL.md +1 -31
- package/templates/langs/zh/core/TOOLS.md +0 -4
- package/templates/langs/zh/principles/THINKING_OS.md +77 -0
- package/templates/langs/zh/skills/admin/SKILL.md +0 -1
- package/templates/langs/zh/skills/evolution-framework-update/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +25 -4
- package/tests/commands/evolution-status.test.ts +119 -0
- package/tests/commands/implementation-lifecycle.test.ts +362 -0
- package/tests/core/code-implementation-storage.test.ts +398 -0
- package/tests/core/evolution-reducer.detector-metadata.test.ts +28 -28
- package/tests/core/nocturnal-artifact-lineage.test.ts +53 -0
- package/tests/core/nocturnal-artificer.test.ts +241 -0
- package/tests/core/nocturnal-compliance-p-principles.test.ts +133 -0
- package/tests/core/nocturnal-rule-implementation-validator.test.ts +127 -0
- package/tests/core/pd-task-store.test.ts +126 -0
- package/tests/core/principle-internalization/deprecated-readiness.test.ts +193 -0
- package/tests/core/principle-internalization/internalization-routing-policy.test.ts +212 -0
- package/tests/core/principle-internalization/lifecycle-metrics.test.ts +350 -0
- package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +211 -0
- package/tests/core/principle-training-state.test.ts +228 -1
- package/tests/core/principle-tree-ledger.test.ts +423 -0
- package/tests/core/regression-v1-9-1.test.ts +265 -0
- package/tests/core/replay-engine.test.ts +234 -0
- package/tests/core/rule-host-helpers.test.ts +120 -0
- package/tests/core/rule-host.test.ts +389 -0
- package/tests/core/rule-implementation-runtime.test.ts +64 -0
- package/tests/core/workspace-context.test.ts +53 -0
- package/tests/core/workspace-dir-validation.test.ts +272 -0
- package/tests/hooks/gate-rule-host-pipeline.test.ts +385 -0
- package/tests/hooks/pain.test.ts +74 -10
- package/tests/hooks/prompt.test.ts +63 -1
- package/tests/integration/principle-lifecycle.e2e.test.ts +197 -0
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +211 -0
- package/tests/service/data-endpoints-regression.test.ts +834 -0
- package/tests/service/evolution-worker.test.ts +0 -123
- package/tests/service/nocturnal-service-code-candidate.test.ts +330 -0
- package/tests/utils/nlp.test.ts +1 -19
- package/tests/utils/retry.test.ts +327 -0
- package/ui/src/App.tsx +1 -1
- package/ui/src/api.ts +4 -0
- package/ui/src/charts.tsx +366 -0
- package/ui/src/components/WorkspaceConfig.tsx +107 -75
- package/ui/src/i18n/ui.ts +89 -31
- package/ui/src/pages/EvolutionPage.tsx +1 -1
- package/ui/src/pages/OverviewPage.tsx +441 -81
- package/ui/src/pages/ThinkingModelsPage.tsx +287 -69
- package/ui/src/styles.css +43 -0
- package/ui/src/types.ts +17 -1
- package/src/agents/nocturnal-dreamer.md +0 -152
- package/src/agents/nocturnal-philosopher.md +0 -138
- package/src/agents/nocturnal-reflector.md +0 -126
- package/src/agents/nocturnal-scribe.md +0 -164
- package/templates/workspace/.principles/00-kernel.md +0 -51
- package/templates/workspace/.principles/DECISION_POLICY.json +0 -44
- package/templates/workspace/.principles/PRINCIPLES.md +0 -20
- package/templates/workspace/.principles/PROFILE.json +0 -54
- package/templates/workspace/.principles/PROFILE.schema.json +0 -56
- package/templates/workspace/.principles/THINKING_OS.md +0 -64
- package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +0 -7
- package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +0 -9
- package/templates/workspace/.principles/models/_INDEX.md +0 -27
- package/templates/workspace/.principles/models/first_principles.md +0 -62
- package/templates/workspace/.principles/models/marketing_4p.md +0 -52
- package/templates/workspace/.principles/models/porter_five.md +0 -63
- package/templates/workspace/.principles/models/swot.md +0 -60
- package/templates/workspace/.principles/models/user_story_map.md +0 -63
- package/templates/workspace/.state/WORKBOARD.json +0 -4
package/src/core/migration.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import type { OpenClawPluginApi } from '../openclaw-sdk.js';
|
|
4
|
-
import {
|
|
4
|
+
import type { PD_FILES} from './paths.js';
|
|
5
|
+
import { resolvePdPath } from './paths.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Handles migration of Principles Disciple files from legacy directories
|
|
@@ -13,11 +14,10 @@ export function migrateDirectoryStructure(api: OpenClawPluginApi, workspaceDir:
|
|
|
13
14
|
const legacyStateDir = path.join(workspaceDir, 'memory', '.state');
|
|
14
15
|
|
|
15
16
|
// Comprehensive migration map covering ALL legacy locations
|
|
16
|
-
const migrationMap:
|
|
17
|
+
const migrationMap: { legacy: string; newKey: keyof typeof PD_FILES }[] = [
|
|
17
18
|
// From docs/
|
|
18
19
|
{ legacy: path.join(legacyDocsDir, 'PRINCIPLES.md'), newKey: 'PRINCIPLES' },
|
|
19
20
|
{ legacy: path.join(legacyDocsDir, 'THINKING_OS.md'), newKey: 'THINKING_OS' },
|
|
20
|
-
{ legacy: path.join(legacyDocsDir, '00-kernel.md'), newKey: 'KERNEL' },
|
|
21
21
|
{ legacy: path.join(legacyDocsDir, 'DECISION_POLICY.json'), newKey: 'DECISION_POLICY' },
|
|
22
22
|
{ legacy: path.join(legacyDocsDir, 'PLAN.md'), newKey: 'PLAN' },
|
|
23
23
|
{ legacy: path.join(legacyDocsDir, 'evolution_queue.json'), newKey: 'EVOLUTION_QUEUE' },
|
|
@@ -228,7 +228,7 @@ function readRegistry(stateDir: string): ModelDeploymentRegistry {
|
|
|
228
228
|
return registry;
|
|
229
229
|
} catch (err) {
|
|
230
230
|
if (err instanceof SyntaxError || err instanceof Error) {
|
|
231
|
-
throw new Error(`Failed to read deployment registry from "${registryPath}": ${err.message}
|
|
231
|
+
throw new Error(`Failed to read deployment registry from "${registryPath}": ${err.message}`, { cause: err });
|
|
232
232
|
}
|
|
233
233
|
throw err;
|
|
234
234
|
}
|
|
@@ -249,9 +249,10 @@ function writeRegistry(stateDir: string, registry: ModelDeploymentRegistry): voi
|
|
|
249
249
|
/**
|
|
250
250
|
* Execute a read-modify-write under an exclusive file lock.
|
|
251
251
|
*/
|
|
252
|
+
/* eslint-disable no-unused-vars -- Reason: _registry is a type signature parameter */
|
|
252
253
|
function withDeploymentRegistryLock<T>(
|
|
253
254
|
stateDir: string,
|
|
254
|
-
fn: (
|
|
255
|
+
fn: (_registry: ModelDeploymentRegistry) => T
|
|
255
256
|
): T {
|
|
256
257
|
const registryPath = getRegistryPath(stateDir);
|
|
257
258
|
return withLock(registryPath, () => {
|
|
@@ -348,6 +349,7 @@ export function assertPromotionGatePassed(stateDir: string, checkpointId: string
|
|
|
348
349
|
* @throws Error if checkpoint is not found or not deployable
|
|
349
350
|
* @throws Error if checkpoint's targetModelFamily violates profile constraints
|
|
350
351
|
*/
|
|
352
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- Reason: checkpoint binding requires state + profile + checkpoint - refactoring would break API
|
|
351
353
|
export function bindCheckpointToWorkerProfile(
|
|
352
354
|
stateDir: string,
|
|
353
355
|
workerProfile: WorkerProfile,
|
|
@@ -451,7 +453,7 @@ export function listDeployments(
|
|
|
451
453
|
}
|
|
452
454
|
): Deployment[] {
|
|
453
455
|
const registry = readRegistry(stateDir);
|
|
454
|
-
let deployments = registry
|
|
456
|
+
let {deployments} = registry;
|
|
455
457
|
|
|
456
458
|
if (filter?.workerProfile) {
|
|
457
459
|
deployments = deployments.filter((d) => d.workerProfile === filter.workerProfile);
|
|
@@ -711,7 +713,7 @@ export function getDeploymentRegistryStats(
|
|
|
711
713
|
profilesWithRoutingEnabled: number;
|
|
712
714
|
} {
|
|
713
715
|
const registry = readRegistry(stateDir);
|
|
714
|
-
const deployments = registry
|
|
716
|
+
const {deployments} = registry;
|
|
715
717
|
|
|
716
718
|
return {
|
|
717
719
|
totalDeployments: deployments.length,
|
|
@@ -249,7 +249,8 @@ function writeRegistry(stateDir: string, registry: ModelTrainingRegistry): void
|
|
|
249
249
|
*/
|
|
250
250
|
function withRegistryLock<T>(
|
|
251
251
|
stateDir: string,
|
|
252
|
-
|
|
252
|
+
// eslint-disable-next-line no-unused-vars -- parameter type annotation requires name
|
|
253
|
+
fn: (_: ModelTrainingRegistry) => T
|
|
253
254
|
): T {
|
|
254
255
|
const registryPath = getRegistryPath(stateDir);
|
|
255
256
|
return withLock(registryPath, () => {
|
|
@@ -320,6 +321,7 @@ export function registerTrainingRun(
|
|
|
320
321
|
*
|
|
321
322
|
* @throws Error if run not found or transition is invalid
|
|
322
323
|
*/
|
|
324
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- Reason: status update requires state + runId + status - refactoring would break API
|
|
323
325
|
export function updateTrainingRunStatus(
|
|
324
326
|
stateDir: string,
|
|
325
327
|
trainRunId: string,
|
|
@@ -498,7 +500,7 @@ export function listCheckpoints(
|
|
|
498
500
|
}
|
|
499
501
|
): Checkpoint[] {
|
|
500
502
|
const registry = readRegistry(stateDir);
|
|
501
|
-
let checkpoints = registry
|
|
503
|
+
let {checkpoints} = registry;
|
|
502
504
|
|
|
503
505
|
if (filter?.trainRunId) {
|
|
504
506
|
checkpoints = checkpoints.filter((c) => c.trainRunId === filter.trainRunId);
|
|
@@ -795,7 +797,7 @@ export function getTrainingRegistryStats(
|
|
|
795
797
|
const registry = readRegistry(stateDir);
|
|
796
798
|
|
|
797
799
|
const runs = registry.trainingRuns;
|
|
798
|
-
const checkpoints = registry
|
|
800
|
+
const {checkpoints} = registry;
|
|
799
801
|
const evals = registry.evalSummaries;
|
|
800
802
|
|
|
801
803
|
return {
|
|
@@ -22,8 +22,6 @@
|
|
|
22
22
|
* - No LLM involvement — all checks are algorithmic
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import type { NocturnalSessionSnapshot } from './nocturnal-trajectory-extractor.js';
|
|
26
|
-
|
|
27
25
|
// ---------------------------------------------------------------------------
|
|
28
26
|
// Types
|
|
29
27
|
// ---------------------------------------------------------------------------
|
|
@@ -218,7 +216,7 @@ export function validateDreamerOutput(output: unknown): TrinityStageValidationRe
|
|
|
218
216
|
});
|
|
219
217
|
|
|
220
218
|
// Check for duplicate candidateIndices
|
|
221
|
-
const indices = (obj.candidates as
|
|
219
|
+
const indices = (obj.candidates as Record<string, unknown>[])
|
|
222
220
|
.map((c) => c.candidateIndex)
|
|
223
221
|
.filter((i) => typeof i === 'number');
|
|
224
222
|
const uniqueIndices = new Set(indices);
|
|
@@ -287,7 +285,7 @@ export function validatePhilosopherOutput(output: unknown): TrinityStageValidati
|
|
|
287
285
|
});
|
|
288
286
|
|
|
289
287
|
// Check ranks are unique and sequential (1, 2, 3...)
|
|
290
|
-
const ranks = (obj.judgments as
|
|
288
|
+
const ranks = (obj.judgments as Record<string, unknown>[])
|
|
291
289
|
.map((j) => j.rank)
|
|
292
290
|
.filter((r) => typeof r === 'number')
|
|
293
291
|
.sort((a, b) => a - b);
|
|
@@ -495,7 +493,7 @@ export function validateArtifact(
|
|
|
495
493
|
}
|
|
496
494
|
|
|
497
495
|
// Rule 3: Required string fields must be present and non-empty
|
|
498
|
-
const requiredFields:
|
|
496
|
+
const requiredFields: { key: keyof RawReflectionArtifact; label: string }[] = [
|
|
499
497
|
{ key: 'artifactId', label: 'artifactId' },
|
|
500
498
|
{ key: 'sessionId', label: 'sessionId' },
|
|
501
499
|
{ key: 'principleId', label: 'principleId' },
|
|
@@ -522,7 +520,7 @@ export function validateArtifact(
|
|
|
522
520
|
|
|
523
521
|
// Rule 4: Cross-validate principleId
|
|
524
522
|
if (options.expectedPrincipleId !== undefined) {
|
|
525
|
-
const principleId = obj
|
|
523
|
+
const {principleId} = obj;
|
|
526
524
|
if (!isNonEmptyString(principleId)) {
|
|
527
525
|
failures.push({ reason: 'principleId is required but missing', field: 'principleId' });
|
|
528
526
|
} else if (String(principleId) !== options.expectedPrincipleId) {
|
|
@@ -535,7 +533,7 @@ export function validateArtifact(
|
|
|
535
533
|
|
|
536
534
|
// Rule 5: Cross-validate sessionId
|
|
537
535
|
if (options.expectedSessionId !== undefined) {
|
|
538
|
-
const sessionId = obj
|
|
536
|
+
const {sessionId} = obj;
|
|
539
537
|
if (!isNonEmptyString(sessionId)) {
|
|
540
538
|
failures.push({ reason: 'sessionId is required but missing', field: 'sessionId' });
|
|
541
539
|
} else if (String(sessionId) !== options.expectedSessionId) {
|
|
@@ -547,7 +545,7 @@ export function validateArtifact(
|
|
|
547
545
|
}
|
|
548
546
|
|
|
549
547
|
// Rule 6: Check for placeholder values in text fields
|
|
550
|
-
const textFields:
|
|
548
|
+
const textFields: (keyof RawReflectionArtifact)[] = [
|
|
551
549
|
'badDecision',
|
|
552
550
|
'betterDecision',
|
|
553
551
|
'rationale',
|
|
@@ -569,14 +567,14 @@ export function validateArtifact(
|
|
|
569
567
|
}
|
|
570
568
|
|
|
571
569
|
// Rule 7: Check sourceSnapshotRef if present
|
|
572
|
-
const sourceSnapshotRef = obj
|
|
570
|
+
const {sourceSnapshotRef} = obj;
|
|
573
571
|
if (sourceSnapshotRef !== undefined && !isNonEmptyString(sourceSnapshotRef)) {
|
|
574
572
|
failures.push({ reason: 'Field "sourceSnapshotRef" must be a non-empty string if present', field: 'sourceSnapshotRef' });
|
|
575
573
|
}
|
|
576
574
|
|
|
577
575
|
// Rule 8: badDecision should not be identical to betterDecision
|
|
578
|
-
const badDecision = obj
|
|
579
|
-
const betterDecision = obj
|
|
576
|
+
const {badDecision} = obj;
|
|
577
|
+
const {betterDecision} = obj;
|
|
580
578
|
if (isNonEmptyString(badDecision) && isNonEmptyString(betterDecision)) {
|
|
581
579
|
if (String(badDecision).trim() === String(betterDecision).trim()) {
|
|
582
580
|
failures.push({
|
|
@@ -587,7 +585,7 @@ export function validateArtifact(
|
|
|
587
585
|
}
|
|
588
586
|
|
|
589
587
|
// Rule 9: Rationale should not be too short (needs explanation)
|
|
590
|
-
const rationale = obj
|
|
588
|
+
const {rationale} = obj;
|
|
591
589
|
if (isNonEmptyString(rationale) && String(rationale).trim().length < 20) {
|
|
592
590
|
failures.push({
|
|
593
591
|
reason: 'rationale is too short — must provide meaningful explanation',
|
|
@@ -596,7 +594,7 @@ export function validateArtifact(
|
|
|
596
594
|
}
|
|
597
595
|
|
|
598
596
|
// Rule 10: Validate optional reflection quality metrics (if present)
|
|
599
|
-
const thinkingModelDelta = obj
|
|
597
|
+
const {thinkingModelDelta} = obj;
|
|
600
598
|
if (thinkingModelDelta !== undefined && typeof thinkingModelDelta !== 'number') {
|
|
601
599
|
failures.push({
|
|
602
600
|
reason: 'thinkingModelDelta must be a number if present',
|
|
@@ -609,7 +607,7 @@ export function validateArtifact(
|
|
|
609
607
|
});
|
|
610
608
|
}
|
|
611
609
|
|
|
612
|
-
const planningRatioGain = obj
|
|
610
|
+
const {planningRatioGain} = obj;
|
|
613
611
|
if (planningRatioGain !== undefined && typeof planningRatioGain !== 'number') {
|
|
614
612
|
failures.push({
|
|
615
613
|
reason: 'planningRatioGain must be a number if present',
|
|
@@ -691,6 +689,7 @@ export function parseAndValidateArtifact(
|
|
|
691
689
|
options: ArbiterOptions = {}
|
|
692
690
|
): ArbiterResult {
|
|
693
691
|
// Step 1: Parse JSON
|
|
692
|
+
// eslint-disable-next-line @typescript-eslint/init-declarations -- Reason: assigned in try block immediately after declaration
|
|
694
693
|
let parsed: unknown;
|
|
695
694
|
try {
|
|
696
695
|
parsed = JSON.parse(jsonString);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { resolveNocturnalDir } from './nocturnal-paths.js';
|
|
4
|
+
import { withLock } from '../utils/file-lock.js';
|
|
5
|
+
|
|
6
|
+
export type ArtifactKind = 'behavioral-sample' | 'rule-implementation-candidate';
|
|
7
|
+
|
|
8
|
+
export interface ArtifactLineageRecord {
|
|
9
|
+
artifactKind: ArtifactKind;
|
|
10
|
+
artifactId: string;
|
|
11
|
+
principleId: string;
|
|
12
|
+
ruleId: string | null;
|
|
13
|
+
sessionId: string;
|
|
14
|
+
sourceSnapshotRef: string;
|
|
15
|
+
sourcePainIds: string[];
|
|
16
|
+
sourceGateBlockIds: string[];
|
|
17
|
+
storagePath: string;
|
|
18
|
+
implementationId: string | null;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CandidateArtifactLineageInput {
|
|
23
|
+
artifactId: string;
|
|
24
|
+
principleId: string;
|
|
25
|
+
ruleId: string;
|
|
26
|
+
sessionId: string;
|
|
27
|
+
sourceSnapshotRef: string;
|
|
28
|
+
sourcePainIds: string[];
|
|
29
|
+
sourceGateBlockIds: string[];
|
|
30
|
+
storagePath: string;
|
|
31
|
+
implementationId: string;
|
|
32
|
+
createdAt?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getLineageRegistryPath(workspaceDir: string): string {
|
|
36
|
+
return path.join(resolveNocturnalDir(workspaceDir, 'ROOT'), 'artifact-lineage.json');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readArtifactLineageRegistry(workspaceDir: string): ArtifactLineageRecord[] {
|
|
40
|
+
const registryPath = getLineageRegistryPath(workspaceDir);
|
|
41
|
+
if (!fs.existsSync(registryPath)) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const content = fs.readFileSync(registryPath, 'utf-8');
|
|
47
|
+
return JSON.parse(content) as ArtifactLineageRecord[];
|
|
48
|
+
} catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeArtifactLineageRegistry(
|
|
54
|
+
workspaceDir: string,
|
|
55
|
+
records: ArtifactLineageRecord[]
|
|
56
|
+
): void {
|
|
57
|
+
const registryPath = getLineageRegistryPath(workspaceDir);
|
|
58
|
+
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
|
|
59
|
+
const tmpPath = `${registryPath}.tmp`;
|
|
60
|
+
fs.writeFileSync(tmpPath, JSON.stringify(records, null, 2), 'utf-8');
|
|
61
|
+
fs.renameSync(tmpPath, registryPath);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function appendArtifactLineageRecord(
|
|
65
|
+
workspaceDir: string,
|
|
66
|
+
record: ArtifactLineageRecord
|
|
67
|
+
): ArtifactLineageRecord {
|
|
68
|
+
const registryPath = getLineageRegistryPath(workspaceDir);
|
|
69
|
+
|
|
70
|
+
return withLock(registryPath, () => {
|
|
71
|
+
const records = readArtifactLineageRegistry(workspaceDir);
|
|
72
|
+
const nextRecord: ArtifactLineageRecord = {
|
|
73
|
+
...record,
|
|
74
|
+
sourcePainIds: [...record.sourcePainIds],
|
|
75
|
+
sourceGateBlockIds: [...record.sourceGateBlockIds],
|
|
76
|
+
storagePath: path.normalize(record.storagePath),
|
|
77
|
+
};
|
|
78
|
+
records.push(nextRecord);
|
|
79
|
+
writeArtifactLineageRegistry(workspaceDir, records);
|
|
80
|
+
return nextRecord;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function listArtifactLineageRecords(
|
|
85
|
+
workspaceDir: string,
|
|
86
|
+
artifactKind?: ArtifactKind
|
|
87
|
+
): ArtifactLineageRecord[] {
|
|
88
|
+
const records = readArtifactLineageRegistry(workspaceDir);
|
|
89
|
+
const filtered =
|
|
90
|
+
artifactKind === undefined
|
|
91
|
+
? records
|
|
92
|
+
: records.filter((record) => record.artifactKind === artifactKind);
|
|
93
|
+
|
|
94
|
+
return filtered.sort(
|
|
95
|
+
(left, right) =>
|
|
96
|
+
new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime()
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function appendCandidateArtifactLineageRecord(
|
|
101
|
+
workspaceDir: string,
|
|
102
|
+
input: CandidateArtifactLineageInput
|
|
103
|
+
): ArtifactLineageRecord {
|
|
104
|
+
return appendArtifactLineageRecord(workspaceDir, {
|
|
105
|
+
artifactKind: 'rule-implementation-candidate',
|
|
106
|
+
artifactId: input.artifactId,
|
|
107
|
+
principleId: input.principleId,
|
|
108
|
+
ruleId: input.ruleId,
|
|
109
|
+
sessionId: input.sessionId,
|
|
110
|
+
sourceSnapshotRef: input.sourceSnapshotRef,
|
|
111
|
+
sourcePainIds: input.sourcePainIds,
|
|
112
|
+
sourceGateBlockIds: input.sourceGateBlockIds,
|
|
113
|
+
storagePath: input.storagePath,
|
|
114
|
+
implementationId: input.implementationId,
|
|
115
|
+
createdAt: input.createdAt ?? new Date().toISOString(),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import type { NocturnalSessionSnapshot } from './nocturnal-trajectory-extractor.js';
|
|
2
|
+
import {
|
|
3
|
+
getPrincipleSubtree,
|
|
4
|
+
listImplementationsForRule,
|
|
5
|
+
listRuleImplementationsByState,
|
|
6
|
+
} from './principle-tree-ledger.js';
|
|
7
|
+
import type { LedgerRule } from './principle-tree-ledger.js';
|
|
8
|
+
|
|
9
|
+
export type ArtificerArtifactKind = 'rule-implementation-candidate';
|
|
10
|
+
|
|
11
|
+
export interface ArtificerLineageMetadata {
|
|
12
|
+
artifactKind: ArtificerArtifactKind;
|
|
13
|
+
sourceSnapshotRef: string;
|
|
14
|
+
sourcePainIds: string[];
|
|
15
|
+
sourceGateBlockIds: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ArtificerInput {
|
|
19
|
+
principleId: string;
|
|
20
|
+
ruleId: string;
|
|
21
|
+
snapshot: NocturnalSessionSnapshot;
|
|
22
|
+
scribeArtifact: {
|
|
23
|
+
sessionId: string;
|
|
24
|
+
badDecision: string;
|
|
25
|
+
betterDecision: string;
|
|
26
|
+
rationale: string;
|
|
27
|
+
sourceSnapshotRef: string;
|
|
28
|
+
};
|
|
29
|
+
lineage: ArtificerLineageMetadata;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ArtificerOutput {
|
|
33
|
+
ruleId: string;
|
|
34
|
+
implementationType: 'code';
|
|
35
|
+
candidateSource: string;
|
|
36
|
+
helperUsage: string[];
|
|
37
|
+
expectedDecision: 'allow' | 'block' | 'requireApproval';
|
|
38
|
+
rationale: string;
|
|
39
|
+
lineage: ArtificerLineageMetadata;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ArtificerTargetRuleScore {
|
|
43
|
+
ruleId: string;
|
|
44
|
+
score: number;
|
|
45
|
+
matchedSignals: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type ArtificerTargetRuleResolution =
|
|
49
|
+
| {
|
|
50
|
+
status: 'selected';
|
|
51
|
+
ruleId: string;
|
|
52
|
+
reason: 'single-rule' | 'evidence-winner';
|
|
53
|
+
scores: ArtificerTargetRuleScore[];
|
|
54
|
+
}
|
|
55
|
+
| {
|
|
56
|
+
status: 'skip';
|
|
57
|
+
reason:
|
|
58
|
+
| 'principle-not-found'
|
|
59
|
+
| 'no-rules'
|
|
60
|
+
| 'ambiguous-target-rule'
|
|
61
|
+
| 'no-deterministic-signal';
|
|
62
|
+
scores: ArtificerTargetRuleScore[];
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export interface TrinityArtificerContext {
|
|
66
|
+
principleId: string;
|
|
67
|
+
resolution: ArtificerTargetRuleResolution;
|
|
68
|
+
eligible: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function tokenize(value: string | undefined): string[] {
|
|
72
|
+
if (!value) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return value
|
|
77
|
+
.toLowerCase()
|
|
78
|
+
.split(/[^a-z0-9]+/i)
|
|
79
|
+
.map((token) => token.trim())
|
|
80
|
+
.filter((token) => token.length >= 3);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function countMatches(tokens: string[], haystacks: string[]): number {
|
|
84
|
+
return tokens.reduce((total, token) => {
|
|
85
|
+
if (haystacks.some((haystack) => haystack.includes(token))) {
|
|
86
|
+
return total + 1;
|
|
87
|
+
}
|
|
88
|
+
return total;
|
|
89
|
+
}, 0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function scoreRule(
|
|
93
|
+
stateDir: string,
|
|
94
|
+
rule: LedgerRule,
|
|
95
|
+
snapshot: NocturnalSessionSnapshot
|
|
96
|
+
): ArtificerTargetRuleScore {
|
|
97
|
+
const toolNames = snapshot.toolCalls.map((toolCall) => toolCall.toolName.toLowerCase());
|
|
98
|
+
const painReasons = snapshot.painEvents
|
|
99
|
+
.map((painEvent) => painEvent.reason?.toLowerCase())
|
|
100
|
+
.filter((reason): reason is string => typeof reason === 'string' && reason.length > 0);
|
|
101
|
+
const gateReasons = snapshot.gateBlocks.map((gateBlock) => gateBlock.reason.toLowerCase());
|
|
102
|
+
const ruleTokens = [
|
|
103
|
+
...tokenize(rule.name),
|
|
104
|
+
...tokenize(rule.description),
|
|
105
|
+
...tokenize(rule.triggerCondition),
|
|
106
|
+
...tokenize(rule.action),
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const gateMatches = countMatches(ruleTokens, gateReasons);
|
|
110
|
+
const painMatches = countMatches(ruleTokens, painReasons);
|
|
111
|
+
const toolMatches = countMatches(ruleTokens, toolNames);
|
|
112
|
+
const candidateImplCount = listRuleImplementationsByState(stateDir, rule.id, 'candidate').length;
|
|
113
|
+
const totalImplCount = listImplementationsForRule(stateDir, rule.id).length;
|
|
114
|
+
const implementationSignal = Math.max(totalImplCount - candidateImplCount, 0);
|
|
115
|
+
const score = gateMatches * 1000 + painMatches * 100 + toolMatches * 10 + implementationSignal;
|
|
116
|
+
const matchedSignals: string[] = [];
|
|
117
|
+
|
|
118
|
+
if (gateMatches > 0) {
|
|
119
|
+
matchedSignals.push(`gate:${gateMatches}`);
|
|
120
|
+
}
|
|
121
|
+
if (painMatches > 0) {
|
|
122
|
+
matchedSignals.push(`pain:${painMatches}`);
|
|
123
|
+
}
|
|
124
|
+
if (toolMatches > 0) {
|
|
125
|
+
matchedSignals.push(`tool:${toolMatches}`);
|
|
126
|
+
}
|
|
127
|
+
if (implementationSignal > 0) {
|
|
128
|
+
matchedSignals.push(`impl:${implementationSignal}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
ruleId: rule.id,
|
|
133
|
+
score,
|
|
134
|
+
matchedSignals,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function resolveArtificerTargetRule(
|
|
139
|
+
stateDir: string,
|
|
140
|
+
principleId: string,
|
|
141
|
+
snapshot: NocturnalSessionSnapshot
|
|
142
|
+
): ArtificerTargetRuleResolution {
|
|
143
|
+
const subtree = getPrincipleSubtree(stateDir, principleId);
|
|
144
|
+
if (!subtree) {
|
|
145
|
+
return {
|
|
146
|
+
status: 'skip',
|
|
147
|
+
reason: 'principle-not-found',
|
|
148
|
+
scores: [],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const eligibleRules = subtree.rules
|
|
153
|
+
.map((entry) => entry.rule)
|
|
154
|
+
.filter((rule) => rule.status !== 'retired');
|
|
155
|
+
|
|
156
|
+
if (eligibleRules.length === 0) {
|
|
157
|
+
return {
|
|
158
|
+
status: 'skip',
|
|
159
|
+
reason: 'no-rules',
|
|
160
|
+
scores: [],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (eligibleRules.length === 1) {
|
|
165
|
+
return {
|
|
166
|
+
status: 'selected',
|
|
167
|
+
ruleId: eligibleRules[0].id,
|
|
168
|
+
reason: 'single-rule',
|
|
169
|
+
scores: [
|
|
170
|
+
{
|
|
171
|
+
ruleId: eligibleRules[0].id,
|
|
172
|
+
score: 1,
|
|
173
|
+
matchedSignals: ['single-rule'],
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const scores = eligibleRules.map((rule) => scoreRule(stateDir, rule, snapshot));
|
|
180
|
+
const sorted = [...scores].sort((left, right) => {
|
|
181
|
+
if (right.score !== left.score) {
|
|
182
|
+
return right.score - left.score;
|
|
183
|
+
}
|
|
184
|
+
return left.ruleId.localeCompare(right.ruleId);
|
|
185
|
+
});
|
|
186
|
+
const [winner, runnerUp] = sorted;
|
|
187
|
+
|
|
188
|
+
if (!winner || winner.score === 0) {
|
|
189
|
+
return {
|
|
190
|
+
status: 'skip',
|
|
191
|
+
reason: 'no-deterministic-signal',
|
|
192
|
+
scores: sorted,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (runnerUp && runnerUp.score === winner.score) {
|
|
197
|
+
return {
|
|
198
|
+
status: 'skip',
|
|
199
|
+
reason: 'ambiguous-target-rule',
|
|
200
|
+
scores: sorted,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
status: 'selected',
|
|
206
|
+
ruleId: winner.ruleId,
|
|
207
|
+
reason: 'evidence-winner',
|
|
208
|
+
scores: sorted,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function shouldRunArtificer(
|
|
213
|
+
snapshot: NocturnalSessionSnapshot,
|
|
214
|
+
resolution: ArtificerTargetRuleResolution,
|
|
215
|
+
minimumSignalCount = 2
|
|
216
|
+
): boolean {
|
|
217
|
+
if (resolution.status !== 'selected') {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const signalCount =
|
|
222
|
+
snapshot.stats.totalPainEvents +
|
|
223
|
+
snapshot.stats.totalGateBlocks +
|
|
224
|
+
snapshot.stats.failureCount;
|
|
225
|
+
|
|
226
|
+
return signalCount >= minimumSignalCount;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function parseArtificerOutput(payload: string): ArtificerOutput | null {
|
|
230
|
+
try {
|
|
231
|
+
const parsed = JSON.parse(payload) as Partial<ArtificerOutput>;
|
|
232
|
+
if (
|
|
233
|
+
typeof parsed.ruleId !== 'string' ||
|
|
234
|
+
typeof parsed.candidateSource !== 'string' ||
|
|
235
|
+
!Array.isArray(parsed.helperUsage) ||
|
|
236
|
+
typeof parsed.expectedDecision !== 'string' ||
|
|
237
|
+
typeof parsed.rationale !== 'string' ||
|
|
238
|
+
!parsed.lineage
|
|
239
|
+
) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
ruleId: parsed.ruleId,
|
|
245
|
+
implementationType: 'code',
|
|
246
|
+
candidateSource: parsed.candidateSource,
|
|
247
|
+
helperUsage: parsed.helperUsage.filter(
|
|
248
|
+
(value): value is string => typeof value === 'string'
|
|
249
|
+
),
|
|
250
|
+
expectedDecision: parsed.expectedDecision,
|
|
251
|
+
rationale: parsed.rationale,
|
|
252
|
+
lineage: parsed.lineage,
|
|
253
|
+
};
|
|
254
|
+
} catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -158,7 +158,7 @@ export function scoreCandidate(
|
|
|
158
158
|
let boundedness = 0.5;
|
|
159
159
|
// Specific: mentions specific targets (files, tools, etc.)
|
|
160
160
|
const betterDecisionStr = candidate.betterDecision ?? '';
|
|
161
|
-
const hasSpecificTarget = /[a-zA-Z0-9_
|
|
161
|
+
const hasSpecificTarget = /[a-zA-Z0-9_.]+\.(ts|js|json|md|yml|yaml|sh|py|go|rs)/.test(betterDecisionStr);
|
|
162
162
|
if (hasSpecificTarget) boundedness += 0.2;
|
|
163
163
|
// Not too generic
|
|
164
164
|
const genericPatterns = [
|
|
@@ -241,6 +241,7 @@ export function checkThresholds(
|
|
|
241
241
|
* @param weights - Scoring weights
|
|
242
242
|
* @returns All scored and ranked candidates
|
|
243
243
|
*/
|
|
244
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- Reason: candidate ranking requires candidates + judgments + thresholds + weights - refactoring would break API
|
|
244
245
|
export function rankCandidates(
|
|
245
246
|
candidates: DreamerCandidate[],
|
|
246
247
|
judgments: PhilosopherJudgment[],
|
|
@@ -327,6 +328,7 @@ export function rankCandidates(
|
|
|
327
328
|
* @param weights - Scoring weights
|
|
328
329
|
* @returns Tournament result with winner
|
|
329
330
|
*/
|
|
331
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- Reason: tournament requires candidates + judgments + thresholds + weights - refactoring would break API
|
|
330
332
|
export function runTournament(
|
|
331
333
|
candidates: DreamerCandidate[],
|
|
332
334
|
judgments: PhilosopherJudgment[],
|
|
@@ -376,7 +378,7 @@ export function runTournament(
|
|
|
376
378
|
}
|
|
377
379
|
|
|
378
380
|
// Winner is rank 1
|
|
379
|
-
const winner = eligible
|
|
381
|
+
const [winner] = eligible;
|
|
380
382
|
|
|
381
383
|
trace.push({
|
|
382
384
|
step: 'Winner Selected',
|