principles-disciple 1.72.0 → 1.74.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/INSTALL.md +1 -3
- package/openclaw.plugin.json +10 -5
- package/package.json +17 -19
- package/scripts/acceptance-test.mjs +16 -73
- package/scripts/sync-plugin.mjs +382 -77
- package/src/commands/archive-impl.ts +2 -1
- package/src/commands/capabilities.ts +2 -2
- package/src/commands/context.ts +2 -2
- package/src/commands/disable-impl.ts +2 -1
- package/src/commands/evolution-status.ts +16 -16
- package/src/commands/export.ts +12 -67
- package/src/commands/pain.ts +91 -1
- package/src/commands/principle-rollback.ts +2 -1
- package/src/commands/promote-impl.ts +7 -43
- package/src/commands/rollback-impl.ts +2 -1
- package/src/commands/rollback.ts +2 -1
- package/src/commands/samples.ts +2 -1
- package/src/commands/thinking-os.ts +2 -1
- package/src/config/errors.ts +18 -2
- package/src/constants/diagnostician.ts +2 -2
- package/src/constants/tools.ts +2 -1
- package/src/core/__tests__/focus-history.test.ts +210 -0
- package/src/core/config.ts +1 -1
- package/src/core/correction-cue-learner.ts +2 -136
- package/src/core/correction-types.ts +16 -88
- package/src/core/dictionary.ts +19 -20
- package/src/core/empathy-keyword-matcher.ts +17 -289
- package/src/core/empathy-types.ts +18 -229
- package/src/core/event-log.ts +29 -132
- package/src/core/evolution-reducer.ts +21 -2
- package/src/core/evolution-types.ts +76 -464
- package/src/core/file-store.ts +80 -0
- package/src/core/focus-history.ts +228 -955
- package/src/core/local-worker-routing.ts +34 -314
- package/src/core/merge-gate-audit.ts +0 -195
- package/src/core/migration.ts +0 -1
- package/src/core/pain-diagnostic-gate.ts +154 -0
- package/src/core/pain-signal.ts +21 -138
- package/src/core/pain.ts +15 -88
- package/src/core/path-resolver.ts +0 -1
- package/src/core/paths.ts +0 -1
- package/src/core/pd-task-reconciler.ts +26 -115
- package/src/core/pd-task-service.ts +9 -9
- package/src/core/pd-task-types.ts +23 -127
- package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
- package/src/core/principle-compiler/code-validator.ts +15 -42
- package/src/core/principle-compiler/compiler.ts +100 -15
- package/src/core/principle-compiler/index.ts +5 -2
- package/src/core/principle-compiler/template-generator.ts +4 -104
- package/src/core/principle-injection.ts +10 -202
- package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
- package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
- package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
- package/src/core/principle-tree-ledger-adapter.ts +145 -0
- package/src/core/principle-tree-ledger.ts +8 -6
- package/src/core/reflection/reflection-context.ts +14 -109
- package/src/core/replay-engine.ts +8 -500
- package/src/core/rule-host-helpers.ts +5 -35
- package/src/core/rule-host-types.ts +10 -82
- package/src/core/rule-host.ts +6 -63
- package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
- package/src/core/session-tracker.ts +87 -101
- package/src/core/shadow-observation-registry.ts +19 -48
- package/src/core/trajectory.ts +3 -1
- package/src/core/workflow-funnel-loader.ts +62 -68
- package/src/core/workspace-context.ts +46 -0
- package/src/core/workspace-dir-service.ts +1 -1
- package/src/core/workspace-dir-validation.ts +18 -9
- package/src/hooks/AGENTS.md +1 -1
- package/src/hooks/gate-block-helper.ts +71 -64
- package/src/hooks/gate.ts +183 -31
- package/src/hooks/lifecycle.ts +30 -32
- package/src/hooks/llm.ts +60 -32
- package/src/hooks/pain.ts +297 -103
- package/src/hooks/prompt.ts +400 -440
- package/src/hooks/subagent.ts +2 -29
- package/src/i18n/commands.ts +2 -10
- package/src/index.ts +95 -85
- package/src/openclaw-sdk.ts +311 -0
- package/src/service/central-database.ts +8 -4
- package/src/service/evolution-queue-migration.ts +2 -1
- package/src/service/evolution-worker.ts +163 -1786
- package/src/service/internalization-trigger-adapter.ts +302 -0
- package/src/service/keyword-optimization-service.ts +4 -4
- package/src/service/monitoring-query-service.ts +1 -215
- package/src/service/queue-io.ts +60 -331
- package/src/service/runtime-summary-service.ts +59 -16
- package/src/service/subagent-workflow/index.ts +0 -41
- package/src/service/subagent-workflow/types.ts +9 -120
- package/src/service/subagent-workflow/workflow-store.ts +2 -119
- package/src/service/workflow-watchdog.ts +0 -43
- package/src/types/event-payload.ts +16 -74
- package/src/types/event-types.ts +38 -547
- package/src/types/hygiene-types.ts +7 -30
- package/src/types/principle-tree-schema.ts +20 -222
- package/src/types/queue.ts +15 -70
- package/src/types/runtime-summary.ts +5 -49
- package/src/utils/io.ts +8 -20
- package/src/utils/retry.ts +1 -1
- package/src/utils/shadow-fingerprint.ts +2 -2
- package/src/utils/workspace-resolver.ts +50 -0
- package/templates/langs/en/core/AGENTS.md +7 -7
- package/templates/langs/en/core/BOOT.md +1 -1
- package/templates/langs/en/core/HEARTBEAT.md +2 -2
- package/templates/langs/en/principles/THINKING_OS.md +3 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/en/skills/evolve-task/SKILL.md +3 -3
- package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-mentor/SKILL.md +2 -3
- package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
- package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
- package/templates/langs/zh/core/AGENTS.md +7 -7
- package/templates/langs/zh/core/BOOT.md +1 -1
- package/templates/langs/zh/core/HEARTBEAT.md +2 -2
- package/templates/langs/zh/principles/THINKING_OS.md +3 -2
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
- package/templates/langs/zh/skills/evolve-task/SKILL.md +4 -4
- package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-mentor/SKILL.md +2 -3
- package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
- package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
- package/tests/build-artifacts.test.ts +1 -3
- package/tests/commands/evolution-status.test.ts +0 -118
- package/tests/core/bootstrap-rules.test.ts +1 -1
- package/tests/core/config.test.ts +1 -1
- package/tests/core/event-log.test.ts +35 -0
- package/tests/core/evolution-engine.test.ts +610 -0
- package/tests/core/file-store.test.ts +102 -0
- package/tests/core/focus-history.test.ts +203 -11
- package/tests/core/merge-gate-audit.test.ts +2 -169
- package/tests/core/migration.test.ts +7 -7
- package/tests/core/model-deployment-registry.test.ts +7 -1
- package/tests/core/model-training-registry.test.ts +19 -0
- package/tests/core/observability.test.ts +0 -1
- package/tests/core/pain-diagnostic-gate.test.ts +498 -0
- package/tests/core/pain.test.ts +0 -1
- package/tests/core/path-resolver.test.ts +1 -1
- package/tests/core/paths-refactor.test.ts +0 -22
- package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
- package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
- package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
- package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
- package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
- package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
- package/tests/core/reflection-context.test.ts +0 -14
- package/tests/core/replay-engine.test.ts +127 -215
- package/tests/core/rule-host-helpers.test.ts +2 -2
- package/tests/core/rule-implementation-runtime.test.ts +0 -27
- package/tests/core/workflow-funnel-loader.test.ts +162 -0
- package/tests/core/workspace-context.test.ts +2 -2
- package/tests/core/workspace-dir-validation.test.ts +8 -1
- package/tests/core-anti-growth.test.ts +191 -0
- package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
- package/tests/hooks/confirm-first-removal.test.ts +188 -0
- package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
- package/tests/hooks/gate-auto-correct.test.ts +665 -0
- package/tests/hooks/gate-no-path-write-tool.test.ts +172 -0
- package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
- package/tests/hooks/pain.test.ts +269 -12
- package/tests/hooks/prompt-characterization.test.ts +500 -0
- package/tests/hooks/prompt-size-guard.test.ts +32 -17
- package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
- package/tests/index.test.ts +94 -1
- package/tests/integration/auto-entry-gate.test.ts +248 -0
- package/tests/integration/internalization-trigger-guard.test.ts +69 -0
- package/tests/integration/m8-legacy-paths.test.ts +63 -0
- package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
- package/tests/plugin-config-resolution-cutover.test.ts +359 -0
- package/tests/runtime-v2-discovery-guard.test.ts +154 -0
- package/tests/service/central-database.test.ts +457 -0
- package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
- package/tests/service/evolution-worker.timeout.test.ts +11 -129
- package/tests/service/internalization-trigger-adapter.test.ts +251 -0
- package/tests/service/monitoring-query-service.test.ts +1 -47
- package/tests/service/queue-io.test.ts +1 -62
- package/tests/service/runtime-summary-service.test.ts +3 -1
- package/tests/service/workflow-watchdog.test.ts +0 -91
- package/tests/utils/file-lock.test.ts +5 -3
- package/tests/utils/session-key.test.ts +52 -0
- package/tests/utils/subagent-probe.test.ts +48 -1
- package/vitest.config.ts +4 -11
- package/.planning/codebase/ARCHITECTURE.md +0 -157
- package/.planning/codebase/CONCERNS.md +0 -145
- package/.planning/codebase/CONVENTIONS.md +0 -148
- package/.planning/codebase/INTEGRATIONS.md +0 -81
- package/.planning/codebase/STACK.md +0 -87
- package/.planning/codebase/STRUCTURE.md +0 -193
- package/.planning/codebase/TESTING.md +0 -243
- package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
- package/docs/COMMAND_REFERENCE.md +0 -76
- package/docs/COMMAND_REFERENCE_EN.md +0 -79
- package/scripts/build-web.mjs +0 -46
- package/scripts/diagnose-nocturnal.mjs +0 -537
- package/scripts/seed-nocturnal-scenarios.mjs +0 -384
- package/src/commands/nocturnal-review.ts +0 -322
- package/src/commands/nocturnal-rollout.ts +0 -790
- package/src/commands/nocturnal-train.ts +0 -986
- package/src/commands/pd-reflect.ts +0 -88
- package/src/core/adaptive-thresholds.ts +0 -478
- package/src/core/diagnostician-task-store.ts +0 -192
- package/src/core/nocturnal-arbiter.ts +0 -715
- package/src/core/nocturnal-artifact-lineage.ts +0 -116
- package/src/core/nocturnal-artificer.ts +0 -257
- package/src/core/nocturnal-candidate-scoring.ts +0 -530
- package/src/core/nocturnal-compliance.ts +0 -1146
- package/src/core/nocturnal-dataset.ts +0 -763
- package/src/core/nocturnal-executability.ts +0 -428
- package/src/core/nocturnal-export.ts +0 -499
- package/src/core/nocturnal-paths.ts +0 -240
- package/src/core/nocturnal-reasoning-deriver.ts +0 -343
- package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
- package/src/core/nocturnal-snapshot-contract.ts +0 -99
- package/src/core/nocturnal-trajectory-extractor.ts +0 -512
- package/src/core/nocturnal-trinity-types.ts +0 -218
- package/src/core/nocturnal-trinity.ts +0 -2680
- package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
- package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
- package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
- package/src/http/principles-console-route.ts +0 -709
- package/src/service/central-health-service.ts +0 -49
- package/src/service/central-overview-service.ts +0 -138
- package/src/service/control-ui-query-service.ts +0 -900
- package/src/service/cooldown-strategy.ts +0 -97
- package/src/service/evolution-pain-context.ts +0 -79
- package/src/service/evolution-query-service.ts +0 -407
- package/src/service/health-query-service.ts +0 -1038
- package/src/service/nocturnal-config.ts +0 -214
- package/src/service/nocturnal-runtime.ts +0 -734
- package/src/service/nocturnal-service.ts +0 -1605
- package/src/service/nocturnal-target-selector.ts +0 -545
- package/src/service/sleep-cycle.ts +0 -157
- package/src/service/startup-reconciler.ts +0 -112
- package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
- package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
- package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
- package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
- package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
- package/src/tools/write-pain-flag.ts +0 -215
- package/templates/langs/en/skills/plan-script/SKILL.md +0 -32
- package/templates/langs/zh/skills/plan-script/SKILL.md +0 -32
- package/tests/commands/nocturnal-review.test.ts +0 -448
- package/tests/commands/nocturnal-train.test.ts +0 -97
- package/tests/commands/pd-reflect.test.ts +0 -49
- package/tests/core/adaptive-thresholds.test.ts +0 -261
- package/tests/core/nocturnal-arbiter.test.ts +0 -559
- package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
- package/tests/core/nocturnal-artificer.test.ts +0 -241
- package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
- package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
- package/tests/core/nocturnal-compliance.test.ts +0 -646
- package/tests/core/nocturnal-dataset.test.ts +0 -892
- package/tests/core/nocturnal-e2e.test.ts +0 -234
- package/tests/core/nocturnal-executability.test.ts +0 -357
- package/tests/core/nocturnal-export.test.ts +0 -517
- package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
- package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
- package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
- package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
- package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
- package/tests/core/nocturnal-trinity.test.ts +0 -2053
- package/tests/core/pain-auto-repair.test.ts +0 -96
- package/tests/core/pain-integration.test.ts +0 -510
- package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
- package/tests/http/principles-console-route.test.ts +0 -162
- package/tests/integration/chaos-resilience.test.ts +0 -348
- package/tests/integration/empathy-workflow-integration.test.ts +0 -626
- package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
- package/tests/service/control-ui-query-service.test.ts +0 -121
- package/tests/service/cooldown-strategy.test.ts +0 -164
- package/tests/service/data-endpoints-regression.test.ts +0 -834
- package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
- package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
- package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
- package/tests/service/nocturnal-runtime.test.ts +0 -473
- package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
- package/tests/service/nocturnal-target-selector.test.ts +0 -615
- package/tests/service/startup-reconciler.test.ts +0 -148
- package/tests/tools/write-pain-flag.test.ts +0 -358
- package/ui/src/App.tsx +0 -45
- package/ui/src/api.ts +0 -220
- package/ui/src/charts.tsx +0 -955
- package/ui/src/components/ErrorState.tsx +0 -6
- package/ui/src/components/Loading.tsx +0 -13
- package/ui/src/components/ProtectedRoute.tsx +0 -12
- package/ui/src/components/Shell.tsx +0 -91
- package/ui/src/components/WorkspaceConfig.tsx +0 -178
- package/ui/src/components/index.ts +0 -5
- package/ui/src/context/auth.tsx +0 -80
- package/ui/src/context/theme.tsx +0 -66
- package/ui/src/hooks/useAutoRefresh.ts +0 -39
- package/ui/src/i18n/ui.ts +0 -473
- package/ui/src/main.tsx +0 -16
- package/ui/src/pages/EvolutionPage.tsx +0 -333
- package/ui/src/pages/FeedbackPage.tsx +0 -138
- package/ui/src/pages/GateMonitorPage.tsx +0 -136
- package/ui/src/pages/LoginPage.tsx +0 -89
- package/ui/src/pages/OverviewPage.tsx +0 -599
- package/ui/src/pages/SamplesPage.tsx +0 -174
- package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
- package/ui/src/styles.css +0 -2020
- package/ui/src/types.ts +0 -384
- package/ui/src/utils/format.ts +0 -15
|
@@ -1,615 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as os from 'os';
|
|
4
|
-
import * as path from 'path';
|
|
5
|
-
import {
|
|
6
|
-
NocturnalTargetSelector,
|
|
7
|
-
selectNocturnalTarget,
|
|
8
|
-
type NocturnalSelectionResult,
|
|
9
|
-
type SkipReason,
|
|
10
|
-
} from '../../src/service/nocturnal-target-selector.js';
|
|
11
|
-
import { NocturnalTrajectoryExtractor } from '../../src/core/nocturnal-trajectory-extractor.js';
|
|
12
|
-
import { TrajectoryDatabase } from '../../src/core/trajectory.js';
|
|
13
|
-
import {
|
|
14
|
-
saveStore,
|
|
15
|
-
createDefaultPrincipleState,
|
|
16
|
-
PRINCIPLE_TRAINING_FILE,
|
|
17
|
-
} from '../../src/core/principle-training-state.js';
|
|
18
|
-
import {
|
|
19
|
-
initPersistence,
|
|
20
|
-
trackToolRead,
|
|
21
|
-
clearSession,
|
|
22
|
-
getSession,
|
|
23
|
-
} from '../../src/core/session-tracker.js';
|
|
24
|
-
import { safeRmDir } from '../test-utils.js';
|
|
25
|
-
import {
|
|
26
|
-
NOCTURNAL_RUNTIME_FILE,
|
|
27
|
-
DEFAULT_IDLE_THRESHOLD_MS,
|
|
28
|
-
} from '../../src/service/nocturnal-runtime.js';
|
|
29
|
-
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
// Helpers
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
|
|
34
|
-
function seedSession(
|
|
35
|
-
trajectory: TrajectoryDatabase,
|
|
36
|
-
sessionId: string,
|
|
37
|
-
startedAt?: string
|
|
38
|
-
): void {
|
|
39
|
-
trajectory.recordSession({ sessionId, startedAt: startedAt ?? new Date().toISOString() });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function seedAssistantTurn(
|
|
43
|
-
trajectory: TrajectoryDatabase,
|
|
44
|
-
sessionId: string,
|
|
45
|
-
sanitizedText: string,
|
|
46
|
-
rawText: string
|
|
47
|
-
): void {
|
|
48
|
-
trajectory.recordAssistantTurn({
|
|
49
|
-
sessionId,
|
|
50
|
-
runId: 'run-1',
|
|
51
|
-
provider: 'openai',
|
|
52
|
-
model: 'gpt-4',
|
|
53
|
-
rawText,
|
|
54
|
-
sanitizedText,
|
|
55
|
-
usageJson: {},
|
|
56
|
-
empathySignalJson: {},
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function seedToolCall(
|
|
61
|
-
trajectory: TrajectoryDatabase,
|
|
62
|
-
sessionId: string,
|
|
63
|
-
toolName: string,
|
|
64
|
-
outcome: 'success' | 'failure' | 'blocked',
|
|
65
|
-
errorMessage?: string,
|
|
66
|
-
filePath?: string
|
|
67
|
-
): void {
|
|
68
|
-
trajectory.recordToolCall({
|
|
69
|
-
sessionId,
|
|
70
|
-
toolName,
|
|
71
|
-
outcome,
|
|
72
|
-
errorMessage: errorMessage ?? null,
|
|
73
|
-
paramsJson: filePath ? { filePath } : undefined,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function seedPainEvent(
|
|
78
|
-
trajectory: TrajectoryDatabase,
|
|
79
|
-
sessionId: string,
|
|
80
|
-
score: number,
|
|
81
|
-
source: string
|
|
82
|
-
): void {
|
|
83
|
-
trajectory.recordPainEvent({ sessionId, source, score });
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function seedGateBlock(
|
|
87
|
-
trajectory: TrajectoryDatabase,
|
|
88
|
-
sessionId: string,
|
|
89
|
-
toolName: string,
|
|
90
|
-
reason: string
|
|
91
|
-
): void {
|
|
92
|
-
trajectory.recordGateBlock({ sessionId, toolName, reason });
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function seedTrainingState(
|
|
96
|
-
stateDir: string,
|
|
97
|
-
principleId: string,
|
|
98
|
-
overrides: Partial<ReturnType<typeof createDefaultPrincipleState>> = {}
|
|
99
|
-
): void {
|
|
100
|
-
const store = { [principleId]: { ...createDefaultPrincipleState(principleId), ...overrides } };
|
|
101
|
-
saveStore(stateDir, store);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function setRuntimeCooldown(
|
|
105
|
-
stateDir: string,
|
|
106
|
-
cooldownUntil: string
|
|
107
|
-
): void {
|
|
108
|
-
const runtimePath = path.join(stateDir, NOCTURNAL_RUNTIME_FILE);
|
|
109
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
110
|
-
// Set per-principle cooldown only (not global cooldown)
|
|
111
|
-
fs.writeFileSync(
|
|
112
|
-
runtimePath,
|
|
113
|
-
JSON.stringify({ principleCooldowns: { 'T-01': cooldownUntil }, recentRunTimestamps: [] }),
|
|
114
|
-
'utf-8'
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
// Tests
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
|
|
122
|
-
describe('NocturnalTargetSelector', () => {
|
|
123
|
-
let tempDir: string;
|
|
124
|
-
let workspaceDir: string;
|
|
125
|
-
let stateDir: string;
|
|
126
|
-
let trajectory: TrajectoryDatabase;
|
|
127
|
-
let extractor: NocturnalTrajectoryExtractor;
|
|
128
|
-
|
|
129
|
-
beforeEach(() => {
|
|
130
|
-
vi.useFakeTimers();
|
|
131
|
-
vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
|
|
132
|
-
|
|
133
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-target-sel-'));
|
|
134
|
-
workspaceDir = path.join(tempDir, 'workspace');
|
|
135
|
-
stateDir = path.join(tempDir, 'state');
|
|
136
|
-
fs.mkdirSync(workspaceDir);
|
|
137
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
138
|
-
|
|
139
|
-
// Initialize session tracker persistence
|
|
140
|
-
initPersistence(stateDir);
|
|
141
|
-
|
|
142
|
-
// Create trajectory and extractor
|
|
143
|
-
trajectory = new TrajectoryDatabase({ workspaceDir });
|
|
144
|
-
extractor = new NocturnalTrajectoryExtractor(trajectory);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
afterEach(() => {
|
|
148
|
-
vi.useRealTimers();
|
|
149
|
-
vi.clearAllMocks();
|
|
150
|
-
trajectory.dispose();
|
|
151
|
-
clearSession('session-violating');
|
|
152
|
-
clearSession('session-clean');
|
|
153
|
-
clearSession('session-active-workspace-idle');
|
|
154
|
-
safeRmDir(tempDir);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// -------------------------------------------------------------------------
|
|
158
|
-
// Idle check integration
|
|
159
|
-
// -------------------------------------------------------------------------
|
|
160
|
-
|
|
161
|
-
describe('workspace idle check', () => {
|
|
162
|
-
it('returns skip with workspace_not_idle when session is active', () => {
|
|
163
|
-
// Create a recently active session
|
|
164
|
-
trackToolRead('session-active', 'src/main.ts', workspaceDir);
|
|
165
|
-
|
|
166
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
167
|
-
const result = selector.select();
|
|
168
|
-
|
|
169
|
-
expect(result.decision).toBe('skip');
|
|
170
|
-
expect(result.skipReason).toBe('workspace_not_idle');
|
|
171
|
-
expect(result.diagnostics.idleCheckPassed).toBe(false);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('passes idle check when no sessions exist', () => {
|
|
175
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
176
|
-
const result = selector.select();
|
|
177
|
-
|
|
178
|
-
// Will fail on cooldown or no_evaluable_principles next, not idle
|
|
179
|
-
expect(result.diagnostics.idleCheckPassed).toBe(true);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// -------------------------------------------------------------------------
|
|
184
|
-
// Evaluable principles filtering
|
|
185
|
-
// -------------------------------------------------------------------------
|
|
186
|
-
|
|
187
|
-
describe('evaluable principles filtering', () => {
|
|
188
|
-
it('returns no_evaluable_principles when no training state exists', () => {
|
|
189
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
190
|
-
const result = selector.select();
|
|
191
|
-
|
|
192
|
-
expect(result.decision).toBe('skip');
|
|
193
|
-
expect(result.skipReason).toBe('no_evaluable_principles');
|
|
194
|
-
expect(result.diagnostics.totalEvaluablePrinciples).toBe(0);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('excludes manual_only principles from selection', () => {
|
|
198
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
199
|
-
evaluability: 'manual_only',
|
|
200
|
-
internalizationStatus: 'prompt_only',
|
|
201
|
-
applicableOpportunityCount: 5,
|
|
202
|
-
observedViolationCount: 2,
|
|
203
|
-
complianceRate: 0.6,
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
207
|
-
const result = selector.select();
|
|
208
|
-
|
|
209
|
-
expect(result.decision).toBe('skip');
|
|
210
|
-
expect(result.skipReason).toBe('no_evaluable_principles');
|
|
211
|
-
expect(result.diagnostics.totalEvaluablePrinciples).toBe(0);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('includes deterministic evaluability principles', () => {
|
|
215
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
216
|
-
evaluability: 'deterministic',
|
|
217
|
-
internalizationStatus: 'needs_training',
|
|
218
|
-
applicableOpportunityCount: 5,
|
|
219
|
-
observedViolationCount: 2,
|
|
220
|
-
complianceRate: 0.6,
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
seedSession(trajectory, 'session-violating');
|
|
224
|
-
seedToolCall(trajectory, 'session-violating', 'edit_file', 'success');
|
|
225
|
-
seedToolCall(trajectory, 'session-violating', 'bash', 'failure', 'error');
|
|
226
|
-
|
|
227
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
228
|
-
const result = selector.select();
|
|
229
|
-
|
|
230
|
-
expect(result.decision).toBe('skip'); // Will fail on no_violating_sessions (T-01 not violated)
|
|
231
|
-
expect(result.diagnostics.totalEvaluablePrinciples).toBe(1);
|
|
232
|
-
expect(result.diagnostics.passedPrinciples).toContain('T-01');
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// -------------------------------------------------------------------------
|
|
237
|
-
// Cooldown filtering
|
|
238
|
-
// -------------------------------------------------------------------------
|
|
239
|
-
|
|
240
|
-
describe('cooldown filtering', () => {
|
|
241
|
-
it('returns all_targets_in_cooldown when all candidates are in cooldown', () => {
|
|
242
|
-
// Set cooldown for T-01
|
|
243
|
-
const cooldownUntil = new Date(Date.now() + 3600000).toISOString(); // 1 hour from now
|
|
244
|
-
setRuntimeCooldown(stateDir, cooldownUntil);
|
|
245
|
-
|
|
246
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
247
|
-
evaluability: 'deterministic',
|
|
248
|
-
internalizationStatus: 'needs_training',
|
|
249
|
-
applicableOpportunityCount: 5,
|
|
250
|
-
observedViolationCount: 2,
|
|
251
|
-
complianceRate: 0.6,
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
255
|
-
const result = selector.select();
|
|
256
|
-
|
|
257
|
-
expect(result.decision).toBe('skip');
|
|
258
|
-
expect(result.skipReason).toBe('all_targets_in_cooldown');
|
|
259
|
-
expect(result.diagnostics.filteredByCooldown).toBe(1);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
it('penalizes cooldown principles in scoring', () => {
|
|
263
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
264
|
-
evaluability: 'deterministic',
|
|
265
|
-
internalizationStatus: 'needs_training',
|
|
266
|
-
applicableOpportunityCount: 5,
|
|
267
|
-
observedViolationCount: 2,
|
|
268
|
-
complianceRate: 0.6,
|
|
269
|
-
});
|
|
270
|
-
seedTrainingState(stateDir, 'T-02', {
|
|
271
|
-
evaluability: 'weak_heuristic',
|
|
272
|
-
internalizationStatus: 'needs_training',
|
|
273
|
-
applicableOpportunityCount: 5,
|
|
274
|
-
observedViolationCount: 2,
|
|
275
|
-
complianceRate: 0.6,
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// Set cooldown only for T-01
|
|
279
|
-
const cooldownUntil = new Date(Date.now() + 3600000).toISOString();
|
|
280
|
-
setRuntimeCooldown(stateDir, cooldownUntil);
|
|
281
|
-
|
|
282
|
-
seedSession(trajectory, 'session-violating');
|
|
283
|
-
seedToolCall(trajectory, 'session-violating', 'read_file', 'success');
|
|
284
|
-
|
|
285
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
286
|
-
const result = selector.select();
|
|
287
|
-
|
|
288
|
-
// T-01 is in cooldown, T-02 should be selected
|
|
289
|
-
expect(result.diagnostics.passedPrinciples).not.toContain('T-01');
|
|
290
|
-
expect(result.diagnostics.passedPrinciples).toContain('T-02');
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
// -------------------------------------------------------------------------
|
|
295
|
-
// No violating sessions
|
|
296
|
-
// -------------------------------------------------------------------------
|
|
297
|
-
|
|
298
|
-
describe('no violating sessions', () => {
|
|
299
|
-
it('returns no_violating_sessions when sessions exist but no violations for T-01', () => {
|
|
300
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
301
|
-
evaluability: 'deterministic',
|
|
302
|
-
internalizationStatus: 'needs_training',
|
|
303
|
-
applicableOpportunityCount: 5,
|
|
304
|
-
observedViolationCount: 2,
|
|
305
|
-
complianceRate: 0.6,
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
seedSession(trajectory, 'session-clean');
|
|
309
|
-
seedToolCall(trajectory, 'session-clean', 'read_file', 'success');
|
|
310
|
-
seedToolCall(trajectory, 'session-clean', 'read_file', 'success');
|
|
311
|
-
|
|
312
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
313
|
-
const result = selector.select();
|
|
314
|
-
|
|
315
|
-
expect(result.decision).toBe('skip');
|
|
316
|
-
expect(result.skipReason).toBe('no_violating_sessions');
|
|
317
|
-
expect(result.diagnostics.violatingSessionCount).toBe(0);
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
// -------------------------------------------------------------------------
|
|
322
|
-
// Successful selection
|
|
323
|
-
// -------------------------------------------------------------------------
|
|
324
|
-
|
|
325
|
-
describe('successful selection', () => {
|
|
326
|
-
it('selects principle with violating session when conditions are met', () => {
|
|
327
|
-
// Use T-08 (Pain as Signal) which triggers when pain + failure followed by immediate continuation
|
|
328
|
-
seedTrainingState(stateDir, 'T-08', {
|
|
329
|
-
evaluability: 'deterministic',
|
|
330
|
-
internalizationStatus: 'needs_training',
|
|
331
|
-
applicableOpportunityCount: 5,
|
|
332
|
-
observedViolationCount: 2,
|
|
333
|
-
complianceRate: 0.6,
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
// T-08 violation: pain + failure, then immediate continued operation without read/reflect
|
|
337
|
-
// Sequence: grep (success) → bash (failure) → edit (continues without reflect)
|
|
338
|
-
seedSession(trajectory, 'session-violating');
|
|
339
|
-
seedToolCall(trajectory, 'session-violating', 'grep', 'success');
|
|
340
|
-
seedToolCall(trajectory, 'session-violating', 'bash', 'failure', 'command failed');
|
|
341
|
-
seedToolCall(trajectory, 'session-violating', 'edit_file', 'success'); // continues immediately (not read)
|
|
342
|
-
seedPainEvent(trajectory, 'session-violating', 50, 'tool_failure');
|
|
343
|
-
|
|
344
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
345
|
-
const result = selector.select();
|
|
346
|
-
|
|
347
|
-
expect(result.decision).toBe('selected');
|
|
348
|
-
expect(result.selectedPrincipleId).toBe('T-08');
|
|
349
|
-
expect(result.selectedSessionId).toBe('session-violating');
|
|
350
|
-
expect(result.diagnostics.idleCheckPassed).toBe(true);
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
it('records scoring breakdown in diagnostics', () => {
|
|
354
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
355
|
-
evaluability: 'deterministic',
|
|
356
|
-
internalizationStatus: 'needs_training',
|
|
357
|
-
applicableOpportunityCount: 5,
|
|
358
|
-
observedViolationCount: 2,
|
|
359
|
-
complianceRate: 0.6,
|
|
360
|
-
generatedSampleCount: 0,
|
|
361
|
-
violationTrend: 1,
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
seedSession(trajectory, 'session-violating');
|
|
365
|
-
seedToolCall(trajectory, 'session-violating', 'edit_file', 'failure', 'error');
|
|
366
|
-
|
|
367
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
368
|
-
const result = selector.select();
|
|
369
|
-
|
|
370
|
-
expect(result.diagnostics.scoringBreakdown).toHaveProperty('T-01');
|
|
371
|
-
expect(typeof result.diagnostics.scoringBreakdown['T-01']).toBe('number');
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
// -------------------------------------------------------------------------
|
|
376
|
-
// Quota and global cooldown
|
|
377
|
-
// -------------------------------------------------------------------------
|
|
378
|
-
|
|
379
|
-
describe('quota and global cooldown', () => {
|
|
380
|
-
it('returns quota_exhausted when max runs reached', () => {
|
|
381
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
382
|
-
evaluability: 'deterministic',
|
|
383
|
-
internalizationStatus: 'needs_training',
|
|
384
|
-
applicableOpportunityCount: 5,
|
|
385
|
-
observedViolationCount: 2,
|
|
386
|
-
complianceRate: 0.6,
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
// Set quota to exhausted by having many recent run timestamps
|
|
390
|
-
const runtimePath = path.join(stateDir, NOCTURNAL_RUNTIME_FILE);
|
|
391
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
392
|
-
const now = new Date().toISOString();
|
|
393
|
-
const recentTimestamps = Array(10).fill(now); // 10 runs, exceeding DEFAULT_MAX_RUNS_PER_WINDOW=3
|
|
394
|
-
fs.writeFileSync(
|
|
395
|
-
runtimePath,
|
|
396
|
-
JSON.stringify({ principleCooldowns: {}, recentRunTimestamps: recentTimestamps }),
|
|
397
|
-
'utf-8'
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
seedSession(trajectory, 'session-violating');
|
|
401
|
-
seedToolCall(trajectory, 'session-violating', 'bash', 'failure', 'error');
|
|
402
|
-
|
|
403
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
404
|
-
const result = selector.select();
|
|
405
|
-
|
|
406
|
-
expect(result.decision).toBe('skip');
|
|
407
|
-
expect(result.skipReason).toBe('quota_exhausted');
|
|
408
|
-
expect(result.diagnostics.quotaCheckPassed).toBe(false);
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
// -------------------------------------------------------------------------
|
|
413
|
-
// Convenience function
|
|
414
|
-
// -------------------------------------------------------------------------
|
|
415
|
-
|
|
416
|
-
describe('selectNocturnalTarget convenience function', () => {
|
|
417
|
-
it('works as a standalone function', () => {
|
|
418
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
419
|
-
evaluability: 'deterministic',
|
|
420
|
-
internalizationStatus: 'needs_training',
|
|
421
|
-
applicableOpportunityCount: 5,
|
|
422
|
-
observedViolationCount: 2,
|
|
423
|
-
complianceRate: 0.6,
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
seedSession(trajectory, 'session-violating');
|
|
427
|
-
seedToolCall(trajectory, 'session-violating', 'edit_file', 'success');
|
|
428
|
-
seedToolCall(trajectory, 'session-violating', 'bash', 'failure', 'error');
|
|
429
|
-
|
|
430
|
-
const result = selectNocturnalTarget(workspaceDir, stateDir, extractor);
|
|
431
|
-
|
|
432
|
-
expect(result.decision).toBe('skip'); // Will fail on no_violating_sessions
|
|
433
|
-
expect(result.diagnostics.totalEvaluablePrinciples).toBe(1);
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
// -------------------------------------------------------------------------
|
|
438
|
-
// Skip reason taxonomy
|
|
439
|
-
// -------------------------------------------------------------------------
|
|
440
|
-
|
|
441
|
-
describe('skip reason taxonomy', () => {
|
|
442
|
-
it('no_evaluable_principles — when no training state exists', () => {
|
|
443
|
-
// No seed needed - no evaluable principles
|
|
444
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
445
|
-
const result = selector.select();
|
|
446
|
-
|
|
447
|
-
expect(result.decision).toBe('skip');
|
|
448
|
-
expect(result.skipReason).toBe('no_evaluable_principles');
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
it('workspace_not_idle — when session is active', () => {
|
|
452
|
-
// Create a recently active session
|
|
453
|
-
trackToolRead('session-active-workspace-idle', 'src/main.ts', workspaceDir);
|
|
454
|
-
|
|
455
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
456
|
-
const result = selector.select();
|
|
457
|
-
|
|
458
|
-
expect(result.decision).toBe('skip');
|
|
459
|
-
expect(result.skipReason).toBe('workspace_not_idle');
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
it('quota_exhausted — when max runs per window reached', () => {
|
|
463
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
464
|
-
evaluability: 'deterministic',
|
|
465
|
-
internalizationStatus: 'needs_training',
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
const runtimePath = path.join(stateDir, NOCTURNAL_RUNTIME_FILE);
|
|
469
|
-
const now = new Date().toISOString();
|
|
470
|
-
fs.writeFileSync(
|
|
471
|
-
runtimePath,
|
|
472
|
-
JSON.stringify({ principleCooldowns: {}, recentRunTimestamps: Array(10).fill(now) }),
|
|
473
|
-
'utf-8'
|
|
474
|
-
);
|
|
475
|
-
|
|
476
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
477
|
-
const result = selector.select();
|
|
478
|
-
|
|
479
|
-
expect(result.decision).toBe('skip');
|
|
480
|
-
expect(result.skipReason).toBe('quota_exhausted');
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
it('all_targets_in_cooldown — when all candidates are in per-principle cooldown', () => {
|
|
484
|
-
const cooldownUntil = new Date(Date.now() + 3600000).toISOString();
|
|
485
|
-
setRuntimeCooldown(stateDir, cooldownUntil);
|
|
486
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
487
|
-
evaluability: 'deterministic',
|
|
488
|
-
internalizationStatus: 'needs_training',
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
492
|
-
const result = selector.select();
|
|
493
|
-
|
|
494
|
-
expect(result.decision).toBe('skip');
|
|
495
|
-
expect(result.skipReason).toBe('all_targets_in_cooldown');
|
|
496
|
-
});
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
// -------------------------------------------------------------------------
|
|
500
|
-
// Pain context bias
|
|
501
|
-
// -------------------------------------------------------------------------
|
|
502
|
-
|
|
503
|
-
describe('pain context bias', () => {
|
|
504
|
-
it('includes pain context in diagnostics when provided', () => {
|
|
505
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
506
|
-
evaluability: 'deterministic',
|
|
507
|
-
internalizationStatus: 'needs_training',
|
|
508
|
-
applicableOpportunityCount: 5,
|
|
509
|
-
observedViolationCount: 2,
|
|
510
|
-
complianceRate: 0.6,
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
const recentPainContext = {
|
|
514
|
-
mostRecent: { score: 8, source: 'tool_failure', reason: 'edit failed', timestamp: '2026-03-27T10:00:00.000Z' },
|
|
515
|
-
recentPainCount: 3,
|
|
516
|
-
recentMaxPainScore: 8,
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor, {
|
|
520
|
-
recentPainContext,
|
|
521
|
-
});
|
|
522
|
-
const result = selector.select();
|
|
523
|
-
|
|
524
|
-
expect(result.diagnostics.painContext).toBeDefined();
|
|
525
|
-
expect(result.diagnostics.painContext!.hasRecentPain).toBe(true);
|
|
526
|
-
expect(result.diagnostics.painContext!.recentPainCount).toBe(3);
|
|
527
|
-
expect(result.diagnostics.painContext!.recentMaxPainScore).toBe(8);
|
|
528
|
-
expect(result.diagnostics.painContext!.painSource).toBe('tool_failure');
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
it('does not include pain context in diagnostics when not provided', () => {
|
|
532
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
533
|
-
evaluability: 'deterministic',
|
|
534
|
-
internalizationStatus: 'needs_training',
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
538
|
-
const result = selector.select();
|
|
539
|
-
|
|
540
|
-
expect(result.diagnostics.painContext).toBeUndefined();
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
it('biases toward principles when recent pain count is high', () => {
|
|
544
|
-
// Seed one principle with known scoring characteristics
|
|
545
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
546
|
-
evaluability: 'deterministic',
|
|
547
|
-
internalizationStatus: 'needs_training',
|
|
548
|
-
applicableOpportunityCount: 10,
|
|
549
|
-
observedViolationCount: 5,
|
|
550
|
-
complianceRate: 0.5,
|
|
551
|
-
generatedSampleCount: 5,
|
|
552
|
-
violationTrend: 0,
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
seedSession(trajectory, 'session-violating');
|
|
556
|
-
seedToolCall(trajectory, 'session-violating', 'edit_file', 'failure', 'error');
|
|
557
|
-
|
|
558
|
-
// Without pain context - baseline score
|
|
559
|
-
// Base 50 + compliance 12.5 (0.5*25) + scarcity 9 (15-6) + trend 0 = 71.5 -> 71
|
|
560
|
-
const selectorWithoutPain = new NocturnalTargetSelector(workspaceDir, stateDir, extractor);
|
|
561
|
-
const resultWithoutPain = selectorWithoutPain.select();
|
|
562
|
-
|
|
563
|
-
// With high pain context - score should be boosted
|
|
564
|
-
// Pain contribution: mostRecent (score 9) = 13.5, additional 4 = 4, high pain bonus = 5
|
|
565
|
-
// Total pain = 22.5 -> 23
|
|
566
|
-
// New total: 71 + 23 = 94
|
|
567
|
-
const highPainContext = {
|
|
568
|
-
mostRecent: { score: 9, source: 'tool_failure', reason: 'high pain', timestamp: '2026-03-27T10:00:00.000Z' },
|
|
569
|
-
recentPainCount: 5,
|
|
570
|
-
recentMaxPainScore: 9,
|
|
571
|
-
};
|
|
572
|
-
|
|
573
|
-
const selectorWithPain = new NocturnalTargetSelector(workspaceDir, stateDir, extractor, {
|
|
574
|
-
recentPainContext: highPainContext,
|
|
575
|
-
});
|
|
576
|
-
const resultWithPain = selectorWithPain.select();
|
|
577
|
-
|
|
578
|
-
// Pain context should be present and have correct values
|
|
579
|
-
expect(resultWithPain.diagnostics.painContext).toBeDefined();
|
|
580
|
-
expect(resultWithPain.diagnostics.painContext!.hasRecentPain).toBe(true);
|
|
581
|
-
expect(resultWithPain.diagnostics.painContext!.recentMaxPainScore).toBe(9);
|
|
582
|
-
expect(resultWithPain.diagnostics.painContext!.recentPainCount).toBe(5);
|
|
583
|
-
|
|
584
|
-
// Score with pain should be significantly higher than without pain
|
|
585
|
-
const t01ScoreWithPain = resultWithPain.diagnostics.scoringBreakdown['T-01'];
|
|
586
|
-
const t01ScoreWithoutPain = resultWithoutPain.diagnostics.scoringBreakdown['T-01'];
|
|
587
|
-
expect(t01ScoreWithPain).toBeGreaterThan(t01ScoreWithoutPain);
|
|
588
|
-
|
|
589
|
-
// The difference should be at least 20 (pain contribution)
|
|
590
|
-
expect(t01ScoreWithPain - t01ScoreWithoutPain).toBeGreaterThanOrEqual(20);
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
it('has zero painContext when recentPainCount is 0', () => {
|
|
594
|
-
seedTrainingState(stateDir, 'T-01', {
|
|
595
|
-
evaluability: 'deterministic',
|
|
596
|
-
internalizationStatus: 'needs_training',
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
const emptyPainContext = {
|
|
600
|
-
mostRecent: null,
|
|
601
|
-
recentPainCount: 0,
|
|
602
|
-
recentMaxPainScore: 0,
|
|
603
|
-
};
|
|
604
|
-
|
|
605
|
-
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor, {
|
|
606
|
-
recentPainContext: emptyPainContext,
|
|
607
|
-
});
|
|
608
|
-
const result = selector.select();
|
|
609
|
-
|
|
610
|
-
expect(result.diagnostics.painContext).toBeDefined();
|
|
611
|
-
expect(result.diagnostics.painContext!.hasRecentPain).toBe(false);
|
|
612
|
-
expect(result.diagnostics.painContext!.recentPainCount).toBe(0);
|
|
613
|
-
});
|
|
614
|
-
});
|
|
615
|
-
});
|