principles-disciple 1.71.0 → 1.73.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/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/confirm-first-gate.ts +255 -0
- 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 +38 -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/pain-diagnostic-gate.ts +154 -0
- package/src/core/pain-signal.ts +21 -138
- package/src/core/pain.ts +15 -88
- 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 +46 -44
- package/src/hooks/gate.ts +207 -7
- 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 +469 -339
- 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 +115 -18
- 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 +39 -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 +10 -0
- 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 +2 -2
- package/templates/langs/en/core/BOOT.md +1 -1
- package/templates/langs/en/core/HEARTBEAT.md +2 -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/runtime/.gitignore +2 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/en/skills/evolve-task/SKILL.md +1 -1
- 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 +1 -1
- 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 +2 -2
- package/templates/langs/zh/core/BOOT.md +1 -1
- package/templates/langs/zh/core/HEARTBEAT.md +2 -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/runtime/.gitignore +2 -2
- 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 +2 -2
- 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 +1 -1
- 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/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/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-dir-validation.test.ts +8 -1
- package/tests/core-anti-growth.test.ts +192 -0
- package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
- package/tests/hooks/confirm-first-gate.test.ts +333 -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-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 +329 -0
- 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 +184 -3
- 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/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,215 +0,0 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from '../openclaw-sdk.js';
|
|
2
|
-
import { Type } from '@sinclair/typebox';
|
|
3
|
-
import { buildPainFlag } from '../core/pain.js';
|
|
4
|
-
import { resolveWorkspaceDirFromApi } from '../core/path-resolver.js';
|
|
5
|
-
import { TrajectoryRegistry } from '../core/trajectory.js';
|
|
6
|
-
import * as fs from 'fs';
|
|
7
|
-
import * as path from 'path';
|
|
8
|
-
import { atomicWriteFileSync } from '../utils/io.js';
|
|
9
|
-
|
|
10
|
-
// Pain flag contract required fields
|
|
11
|
-
const PAIN_FLAG_REQUIRED_FIELDS = ['source', 'score', 'time', 'reason'] as const;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Atomic file write: write to temp file then rename.
|
|
15
|
-
* Prevents corruption if process crashes mid-write.
|
|
16
|
-
*/
|
|
17
|
-
function writePainFlagAtomic(filePath: string, content: string): void {
|
|
18
|
-
const dir = path.dirname(filePath);
|
|
19
|
-
if (!fs.existsSync(dir)) {
|
|
20
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
21
|
-
}
|
|
22
|
-
atomicWriteFileSync(filePath, content);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Creates the `write_pain_flag` tool.
|
|
27
|
-
*
|
|
28
|
-
* This tool allows the agent to record a pain signal when it recognizes
|
|
29
|
-
* that it made a mistake, violated a principle, or needs to flag an issue
|
|
30
|
-
* for later reflection.
|
|
31
|
-
*
|
|
32
|
-
* The tool wraps `buildPainFlag` + atomic `writePainFlag` to ensure:
|
|
33
|
-
* - Correct KV format serialization (never [object Object] corruption)
|
|
34
|
-
* - Atomic writes (temp file + rename, crash-safe)
|
|
35
|
-
* - Full contract compliance (source, score, time, reason)
|
|
36
|
-
*
|
|
37
|
-
* The agent should NEVER write to .pain_flag directly.
|
|
38
|
-
*/
|
|
39
|
-
export function createWritePainFlagTool(api: OpenClawPluginApi) {
|
|
40
|
-
return {
|
|
41
|
-
name: 'write_pain_flag',
|
|
42
|
-
description:
|
|
43
|
-
'Record a pain signal to flag mistakes, principle violations, or issues for later reflection. ' +
|
|
44
|
-
'Use this tool INSTEAD of writing .pain_flag directly. ' +
|
|
45
|
-
'Pain signals are processed by the evolution system on the next heartbeat cycle.',
|
|
46
|
-
parameters: Type.Object({
|
|
47
|
-
reason: Type.String({
|
|
48
|
-
description:
|
|
49
|
-
'Describe specifically what went wrong. ' +
|
|
50
|
-
'Include the error, the violated principle, or the issue. ' +
|
|
51
|
-
'Be concrete: "I edited config.ts without reading it first, breaking the export" ' +
|
|
52
|
-
'is better than "I made a mistake".',
|
|
53
|
-
}),
|
|
54
|
-
score: Type.Optional(Type.Number({
|
|
55
|
-
description:
|
|
56
|
-
'Pain severity score (0-100). Default: 80. ' +
|
|
57
|
-
'Guidelines: 30-50 (minor issue), 50-70 (moderate error), ' +
|
|
58
|
-
'70-100 (severe principle violation or data loss risk).',
|
|
59
|
-
minimum: 0,
|
|
60
|
-
maximum: 100,
|
|
61
|
-
})),
|
|
62
|
-
source: Type.Optional(Type.String({
|
|
63
|
-
description:
|
|
64
|
-
'Source of the pain signal. ' +
|
|
65
|
-
'Values: manual (user flagged), tool_failure (tool error), ' +
|
|
66
|
-
'user_empathy (user frustration), principle_violation (principle broken), ' +
|
|
67
|
-
'human_intervention (user manually intervened). ' +
|
|
68
|
-
'Default: manual.',
|
|
69
|
-
})),
|
|
70
|
-
session_id: Type.Optional(Type.String({
|
|
71
|
-
description:
|
|
72
|
-
'Session ID where the pain occurred. ' +
|
|
73
|
-
'If not provided, the system will use the current session.',
|
|
74
|
-
})),
|
|
75
|
-
is_risky: Type.Optional(Type.Boolean({
|
|
76
|
-
description:
|
|
77
|
-
'Whether this involves a high-risk operation (e.g., writing to sensitive files). ' +
|
|
78
|
-
'Default: false.',
|
|
79
|
-
})),
|
|
80
|
-
}),
|
|
81
|
-
|
|
82
|
-
async execute(
|
|
83
|
-
_toolCallId: string,
|
|
84
|
-
rawParams: Record<string, unknown>
|
|
85
|
-
): Promise<{ content: { type: string; text: string }[] }> {
|
|
86
|
-
const reason = typeof rawParams.reason === 'string' ? rawParams.reason.trim() : '';
|
|
87
|
-
const score = typeof rawParams.score === 'number' ? Math.max(0, Math.min(100, Math.round(rawParams.score))) : 80;
|
|
88
|
-
const source = typeof rawParams.source === 'string' && rawParams.source.trim() ? rawParams.source.trim() : 'manual';
|
|
89
|
-
const sessionId = typeof rawParams.session_id === 'string' ? rawParams.session_id.trim() : '';
|
|
90
|
-
const isRisky = rawParams.is_risky === true;
|
|
91
|
-
|
|
92
|
-
// ── Validate required fields ──
|
|
93
|
-
if (!reason) {
|
|
94
|
-
api.logger?.warn?.('[PD:write_pain_flag] Missing required field: reason');
|
|
95
|
-
return {
|
|
96
|
-
content: [{
|
|
97
|
-
type: 'text',
|
|
98
|
-
text: '❌ Error: The `reason` parameter is required.\n' +
|
|
99
|
-
'Describe specifically what went wrong. Example:\n' +
|
|
100
|
-
'"I edited config.ts without reading it first, breaking the export"',
|
|
101
|
-
}],
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ── Resolve workspace ──
|
|
106
|
-
const workspaceDir = resolveWorkspaceDirFromApi(api);
|
|
107
|
-
if (!workspaceDir) {
|
|
108
|
-
api.logger?.error?.('[PD:write_pain_flag] Cannot resolve workspace directory');
|
|
109
|
-
return {
|
|
110
|
-
content: [{
|
|
111
|
-
type: 'text',
|
|
112
|
-
text: '❌ Error: Cannot determine the workspace directory. ' +
|
|
113
|
-
'Please ensure you are in an active workspace.',
|
|
114
|
-
}],
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
// ── Record pain event to trajectory DB first (before flag file) ──
|
|
120
|
-
// This ensures we have a real AUTOINCREMENT ID that flows through
|
|
121
|
-
// the entire pain→principle→compile chain (painEventId propagation).
|
|
122
|
-
let painEventId: number | undefined;
|
|
123
|
-
try {
|
|
124
|
-
const trajectory = TrajectoryRegistry.get(workspaceDir);
|
|
125
|
-
painEventId = trajectory.recordPainEvent({
|
|
126
|
-
sessionId: sessionId || 'unknown',
|
|
127
|
-
source,
|
|
128
|
-
score,
|
|
129
|
-
reason,
|
|
130
|
-
severity: null,
|
|
131
|
-
origin: 'manual',
|
|
132
|
-
confidence: null,
|
|
133
|
-
text: undefined,
|
|
134
|
-
});
|
|
135
|
-
} catch (trajErr) {
|
|
136
|
-
// If trajectory write fails, log but continue — the pain flag
|
|
137
|
-
// itself is still valid and should be processed. The pain event
|
|
138
|
-
// ID simply won't be available for the compiler's exact matching.
|
|
139
|
-
api.logger?.warn?.(`[PD:write_pain_flag] Failed to record pain event to trajectory: ${String(trajErr)}`);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ── Build pain flag data (KV format) ──
|
|
143
|
-
const painData = buildPainFlag({
|
|
144
|
-
source,
|
|
145
|
-
score: String(score),
|
|
146
|
-
reason,
|
|
147
|
-
session_id: sessionId,
|
|
148
|
-
is_risky: isRisky,
|
|
149
|
-
pain_event_id: painEventId !== undefined ? String(painEventId) : undefined,
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// ── Validate contract compliance ──
|
|
153
|
-
const missingFields: string[] = [];
|
|
154
|
-
for (const field of PAIN_FLAG_REQUIRED_FIELDS) {
|
|
155
|
-
if (!painData[field] || painData[field].trim() === '') {
|
|
156
|
-
missingFields.push(field);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (missingFields.length > 0) {
|
|
160
|
-
api.logger?.error?.(`[PD:write_pain_flag] Pain flag missing required fields: ${missingFields.join(', ')}`);
|
|
161
|
-
return {
|
|
162
|
-
content: [{
|
|
163
|
-
type: 'text',
|
|
164
|
-
text: `❌ Error: Pain flag is missing required fields: ${missingFields.join(', ')}. ` +
|
|
165
|
-
'This is an internal error — please report it.',
|
|
166
|
-
}],
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ── Atomic write (temp file + rename) ──
|
|
171
|
-
const painFlagPath = path.join(workspaceDir, '.state', '.pain_flag');
|
|
172
|
-
const { serializeKvLines } = await import('../utils/io.js');
|
|
173
|
-
const content = serializeKvLines(painData);
|
|
174
|
-
writePainFlagAtomic(painFlagPath, content);
|
|
175
|
-
|
|
176
|
-
// ── Log success ──
|
|
177
|
-
api.logger?.info?.(
|
|
178
|
-
`[PD:write_pain_flag] Pain signal recorded: source=${source}, score=${score}, ` +
|
|
179
|
-
`reason="${reason.slice(0, 80)}"${reason.length > 80 ? '...' : ''}"`
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
// ── Agent feedback ──
|
|
183
|
-
return {
|
|
184
|
-
content: [{
|
|
185
|
-
type: 'text',
|
|
186
|
-
text: `✅ Pain signal recorded successfully.\n\n` +
|
|
187
|
-
`- **Reason**: ${reason}\n` +
|
|
188
|
-
`- **Score**: ${score}/100\n` +
|
|
189
|
-
`- **Source**: ${source}\n` +
|
|
190
|
-
`- **Risk**: ${isRisky ? 'Yes' : 'No'}\n` +
|
|
191
|
-
`- **Session**: ${sessionId || '(current)'}\n\n` +
|
|
192
|
-
`The evolution system will process this signal on the next heartbeat cycle ` +
|
|
193
|
-
`(typically within 60 seconds).`,
|
|
194
|
-
}],
|
|
195
|
-
};
|
|
196
|
-
} catch (err) {
|
|
197
|
-
// ── Log failure with stack trace ──
|
|
198
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
199
|
-
const stack = err instanceof Error ? err.stack?.split('\n').slice(0, 3).join(' → ') : '';
|
|
200
|
-
api.logger?.error?.(
|
|
201
|
-
`[PD:write_pain_flag] Failed to write pain flag: ${errorMsg}` +
|
|
202
|
-
(stack ? `\n Stack: ${stack}` : '')
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
content: [{
|
|
207
|
-
type: 'text',
|
|
208
|
-
text: `❌ Failed to record pain signal: ${errorMsg}\n\n` +
|
|
209
|
-
'The error has been logged. Please try again or report this issue.',
|
|
210
|
-
}],
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
};
|
|
215
|
-
}
|
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as os from 'os';
|
|
5
|
-
import { handleNocturnalReviewCommand } from '../../src/commands/nocturnal-review.js';
|
|
6
|
-
import type { PluginCommandContext } from '../../src/openclaw-sdk.js';
|
|
7
|
-
import {
|
|
8
|
-
registerSample,
|
|
9
|
-
updateReviewStatus,
|
|
10
|
-
getDatasetRecord,
|
|
11
|
-
} from '../../src/core/nocturnal-dataset.js';
|
|
12
|
-
import type { NocturnalArtifact } from '../../src/core/nocturnal-arbiter.js';
|
|
13
|
-
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Fixtures
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
function makeArtifact(overrides: Partial<NocturnalArtifact> = {}): NocturnalArtifact {
|
|
19
|
-
return {
|
|
20
|
-
artifactId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
|
21
|
-
sessionId: 'session-abc123',
|
|
22
|
-
principleId: 'T-08',
|
|
23
|
-
sourceSnapshotRef: 'snapshot-2026-03-27-001',
|
|
24
|
-
badDecision: 'After bash command failed, immediately retried without diagnosing',
|
|
25
|
-
betterDecision: 'Check the error message before retrying',
|
|
26
|
-
rationale: 'Diagnosing failures prevents repeated failures and respects cost of each attempt',
|
|
27
|
-
createdAt: '2026-03-27T12:00:00.000Z',
|
|
28
|
-
...overrides,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function makeTmpDir(): string {
|
|
33
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), 'pd-nocturnal-review-test-'));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function rmdir(dir: string): void {
|
|
37
|
-
try {
|
|
38
|
-
if (fs.existsSync(dir)) {
|
|
39
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
40
|
-
}
|
|
41
|
-
} catch {
|
|
42
|
-
// Ignore
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function makeCtx(workspaceDir: string, args = ''): PluginCommandContext {
|
|
47
|
-
return {
|
|
48
|
-
config: { workspaceDir, language: 'en' },
|
|
49
|
-
args,
|
|
50
|
-
} as unknown as PluginCommandContext;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function setupSample(
|
|
54
|
-
workspaceDir: string,
|
|
55
|
-
artifactId: string,
|
|
56
|
-
family: string | null = 'gpt-4'
|
|
57
|
-
): { fingerprint: string; artifactPath: string } {
|
|
58
|
-
const artifact = makeArtifact({ artifactId });
|
|
59
|
-
const artifactPath = path.join(
|
|
60
|
-
workspaceDir,
|
|
61
|
-
'.state',
|
|
62
|
-
'nocturnal',
|
|
63
|
-
'samples',
|
|
64
|
-
`${artifactId}.json`
|
|
65
|
-
);
|
|
66
|
-
fs.mkdirSync(path.dirname(artifactPath), { recursive: true });
|
|
67
|
-
fs.writeFileSync(artifactPath, JSON.stringify({ ...artifact, status: 'approved' }), 'utf-8');
|
|
68
|
-
const result = registerSample(workspaceDir, artifact, artifactPath, family);
|
|
69
|
-
return { fingerprint: result.record.sampleFingerprint, artifactPath };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
// Tests: list
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
|
|
76
|
-
describe('NocturnalReviewCommand list', () => {
|
|
77
|
-
let tmpDir: string;
|
|
78
|
-
|
|
79
|
-
beforeEach(() => {
|
|
80
|
-
tmpDir = makeTmpDir();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
afterEach(() => {
|
|
84
|
-
rmdir(tmpDir);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('shows pending samples', () => {
|
|
88
|
-
const { fingerprint } = setupSample(tmpDir, 'art-pending-list');
|
|
89
|
-
|
|
90
|
-
const result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'list'));
|
|
91
|
-
|
|
92
|
-
expect(result.text).toContain('pending_review');
|
|
93
|
-
expect(result.text).toContain(fingerprint.substring(0, 16));
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('shows empty when no pending samples', () => {
|
|
97
|
-
const { fingerprint } = setupSample(tmpDir, 'art-approved-list');
|
|
98
|
-
updateReviewStatus(tmpDir, fingerprint, 'approved_for_training', 'Approved');
|
|
99
|
-
|
|
100
|
-
const result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'list'));
|
|
101
|
-
|
|
102
|
-
expect(result.text).toContain('No pending');
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// ---------------------------------------------------------------------------
|
|
107
|
-
// Tests: approve
|
|
108
|
-
// ---------------------------------------------------------------------------
|
|
109
|
-
|
|
110
|
-
describe('NocturnalReviewCommand approve', () => {
|
|
111
|
-
let tmpDir: string;
|
|
112
|
-
|
|
113
|
-
beforeEach(() => {
|
|
114
|
-
tmpDir = makeTmpDir();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
afterEach(() => {
|
|
118
|
-
rmdir(tmpDir);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('approves a pending sample', () => {
|
|
122
|
-
const { fingerprint } = setupSample(tmpDir, 'art-approve-test');
|
|
123
|
-
|
|
124
|
-
const result = handleNocturnalReviewCommand(
|
|
125
|
-
makeCtx(tmpDir, `approve ${fingerprint} Looks good for training`)
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
expect(result.text).toContain('approved for training');
|
|
129
|
-
const record = getDatasetRecord(tmpDir, fingerprint);
|
|
130
|
-
expect(record?.reviewStatus).toBe('approved_for_training');
|
|
131
|
-
expect(record?.reviewReason).toBe('Looks good for training');
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('approves with default reason if not provided', () => {
|
|
135
|
-
const { fingerprint } = setupSample(tmpDir, 'art-approve-default');
|
|
136
|
-
|
|
137
|
-
const result = handleNocturnalReviewCommand(
|
|
138
|
-
makeCtx(tmpDir, `approve ${fingerprint}`)
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
expect(result.text).toContain('approved for training');
|
|
142
|
-
const record = getDatasetRecord(tmpDir, fingerprint);
|
|
143
|
-
expect(record?.reviewStatus).toBe('approved_for_training');
|
|
144
|
-
expect(record?.reviewReason).toBeTruthy();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('rejects already approved sample', () => {
|
|
148
|
-
const { fingerprint } = setupSample(tmpDir, 'art-already-approved');
|
|
149
|
-
updateReviewStatus(tmpDir, fingerprint, 'approved_for_training', 'Already approved');
|
|
150
|
-
|
|
151
|
-
const result = handleNocturnalReviewCommand(
|
|
152
|
-
makeCtx(tmpDir, `approve ${fingerprint}`)
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
expect(result.text).toContain('already approved');
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('rejects rejected sample without reset', () => {
|
|
159
|
-
const { fingerprint } = setupSample(tmpDir, 'art-already-rejected');
|
|
160
|
-
updateReviewStatus(tmpDir, fingerprint, 'rejected', 'Rejected');
|
|
161
|
-
|
|
162
|
-
const result = handleNocturnalReviewCommand(
|
|
163
|
-
makeCtx(tmpDir, `approve ${fingerprint}`)
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
expect(result.text).toContain('rejected');
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('rejects superseded sample', () => {
|
|
170
|
-
const { fingerprint } = setupSample(tmpDir, 'art-superseded');
|
|
171
|
-
updateReviewStatus(tmpDir, fingerprint, 'approved_for_training', 'Approved');
|
|
172
|
-
updateReviewStatus(tmpDir, fingerprint, 'superseded', 'Superseded by better');
|
|
173
|
-
|
|
174
|
-
const result = handleNocturnalReviewCommand(
|
|
175
|
-
makeCtx(tmpDir, `approve ${fingerprint}`)
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
expect(result.text).toContain('superseded');
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('returns error for unknown fingerprint', () => {
|
|
182
|
-
const result = handleNocturnalReviewCommand(
|
|
183
|
-
makeCtx(tmpDir, 'approve unknown-fingerprint')
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
expect(result.text).toContain('not found');
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('returns error when missing fingerprint', () => {
|
|
190
|
-
const result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'approve'));
|
|
191
|
-
|
|
192
|
-
expect(result.text).toContain('Usage');
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
// ---------------------------------------------------------------------------
|
|
197
|
-
// Tests: reject
|
|
198
|
-
// ---------------------------------------------------------------------------
|
|
199
|
-
|
|
200
|
-
describe('NocturnalReviewCommand reject', () => {
|
|
201
|
-
let tmpDir: string;
|
|
202
|
-
|
|
203
|
-
beforeEach(() => {
|
|
204
|
-
tmpDir = makeTmpDir();
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
afterEach(() => {
|
|
208
|
-
rmdir(tmpDir);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('rejects a pending sample', () => {
|
|
212
|
-
const { fingerprint } = setupSample(tmpDir, 'art-reject-test');
|
|
213
|
-
|
|
214
|
-
const result = handleNocturnalReviewCommand(
|
|
215
|
-
makeCtx(tmpDir, `reject ${fingerprint} Not suitable for training`)
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
expect(result.text).toContain('rejected');
|
|
219
|
-
const record = getDatasetRecord(tmpDir, fingerprint);
|
|
220
|
-
expect(record?.reviewStatus).toBe('rejected');
|
|
221
|
-
expect(record?.reviewReason).toBe('Not suitable for training');
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it('rejects with default reason if not provided', () => {
|
|
225
|
-
const { fingerprint } = setupSample(tmpDir, 'art-reject-default');
|
|
226
|
-
|
|
227
|
-
const result = handleNocturnalReviewCommand(
|
|
228
|
-
makeCtx(tmpDir, `reject ${fingerprint}`)
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
expect(result.text).toContain('rejected');
|
|
232
|
-
const record = getDatasetRecord(tmpDir, fingerprint);
|
|
233
|
-
expect(record?.reviewStatus).toBe('rejected');
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it('rejects already rejected sample', () => {
|
|
237
|
-
const { fingerprint } = setupSample(tmpDir, 'art-already-rejected');
|
|
238
|
-
updateReviewStatus(tmpDir, fingerprint, 'rejected', 'Already rejected');
|
|
239
|
-
|
|
240
|
-
const result = handleNocturnalReviewCommand(
|
|
241
|
-
makeCtx(tmpDir, `reject ${fingerprint}`)
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
expect(result.text).toContain('already rejected');
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it('rejects superseded sample', () => {
|
|
248
|
-
const { fingerprint } = setupSample(tmpDir, 'art-superseded-reject');
|
|
249
|
-
updateReviewStatus(tmpDir, fingerprint, 'approved_for_training', 'Approved');
|
|
250
|
-
updateReviewStatus(tmpDir, fingerprint, 'superseded', 'Superseded');
|
|
251
|
-
|
|
252
|
-
const result = handleNocturnalReviewCommand(
|
|
253
|
-
makeCtx(tmpDir, `reject ${fingerprint}`)
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
expect(result.text).toContain('superseded');
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it('returns error for unknown fingerprint', () => {
|
|
260
|
-
const result = handleNocturnalReviewCommand(
|
|
261
|
-
makeCtx(tmpDir, 'reject unknown-fingerprint')
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
expect(result.text).toContain('not found');
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// ---------------------------------------------------------------------------
|
|
269
|
-
// Tests: show
|
|
270
|
-
// ---------------------------------------------------------------------------
|
|
271
|
-
|
|
272
|
-
describe('NocturnalReviewCommand show', () => {
|
|
273
|
-
let tmpDir: string;
|
|
274
|
-
|
|
275
|
-
beforeEach(() => {
|
|
276
|
-
tmpDir = makeTmpDir();
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
afterEach(() => {
|
|
280
|
-
rmdir(tmpDir);
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it('shows sample details', () => {
|
|
284
|
-
const { fingerprint } = setupSample(tmpDir, 'art-show-test');
|
|
285
|
-
|
|
286
|
-
const result = handleNocturnalReviewCommand(
|
|
287
|
-
makeCtx(tmpDir, `show ${fingerprint}`)
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
expect(result.text).toContain(fingerprint.substring(0, 16));
|
|
291
|
-
expect(result.text).toContain('T-08');
|
|
292
|
-
expect(result.text).toContain('session-abc123');
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it('returns error for unknown fingerprint', () => {
|
|
296
|
-
const result = handleNocturnalReviewCommand(
|
|
297
|
-
makeCtx(tmpDir, 'show unknown-fingerprint')
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
expect(result.text).toContain('not found');
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it('returns error when missing fingerprint', () => {
|
|
304
|
-
const result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'show'));
|
|
305
|
-
|
|
306
|
-
expect(result.text).toContain('Usage');
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
// ---------------------------------------------------------------------------
|
|
311
|
-
// Tests: set-family
|
|
312
|
-
// ---------------------------------------------------------------------------
|
|
313
|
-
|
|
314
|
-
describe('NocturnalReviewCommand set-family', () => {
|
|
315
|
-
let tmpDir: string;
|
|
316
|
-
|
|
317
|
-
beforeEach(() => {
|
|
318
|
-
tmpDir = makeTmpDir();
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
afterEach(() => {
|
|
322
|
-
rmdir(tmpDir);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
it('sets target model family', () => {
|
|
326
|
-
const { fingerprint } = setupSample(tmpDir, 'art-family-test', null);
|
|
327
|
-
|
|
328
|
-
const result = handleNocturnalReviewCommand(
|
|
329
|
-
makeCtx(tmpDir, `set-family ${fingerprint} claude-3`)
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
expect(result.text).toContain('claude-3');
|
|
333
|
-
const record = getDatasetRecord(tmpDir, fingerprint);
|
|
334
|
-
expect(record?.targetModelFamily).toBe('claude-3');
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it('updates existing family', () => {
|
|
338
|
-
const { fingerprint } = setupSample(tmpDir, 'art-family-update-test', 'gpt-4');
|
|
339
|
-
|
|
340
|
-
const result = handleNocturnalReviewCommand(
|
|
341
|
-
makeCtx(tmpDir, `set-family ${fingerprint} gpt-4o`)
|
|
342
|
-
);
|
|
343
|
-
|
|
344
|
-
expect(result.text).toContain('gpt-4o');
|
|
345
|
-
const record = getDatasetRecord(tmpDir, fingerprint);
|
|
346
|
-
expect(record?.targetModelFamily).toBe('gpt-4o');
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it('returns error for unknown fingerprint', () => {
|
|
350
|
-
const result = handleNocturnalReviewCommand(
|
|
351
|
-
makeCtx(tmpDir, 'set-family unknown-fingerprint gpt-4')
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
expect(result.text).toContain('not found');
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
it('returns error when missing args', () => {
|
|
358
|
-
const { fingerprint } = setupSample(tmpDir, 'art-family-missing-test');
|
|
359
|
-
|
|
360
|
-
const result = handleNocturnalReviewCommand(
|
|
361
|
-
makeCtx(tmpDir, `set-family ${fingerprint}`)
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
expect(result.text).toContain('Usage');
|
|
365
|
-
});
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
// ---------------------------------------------------------------------------
|
|
369
|
-
// Tests: stats
|
|
370
|
-
// ---------------------------------------------------------------------------
|
|
371
|
-
|
|
372
|
-
describe('NocturnalReviewCommand stats', () => {
|
|
373
|
-
let tmpDir: string;
|
|
374
|
-
|
|
375
|
-
beforeEach(() => {
|
|
376
|
-
tmpDir = makeTmpDir();
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
afterEach(() => {
|
|
380
|
-
rmdir(tmpDir);
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it('shows dataset statistics', () => {
|
|
384
|
-
setupSample(tmpDir, 'art-stats-1', 'gpt-4');
|
|
385
|
-
const { fingerprint: fp2 } = setupSample(tmpDir, 'art-stats-2', 'gpt-4');
|
|
386
|
-
updateReviewStatus(tmpDir, fp2, 'approved_for_training', 'Approved');
|
|
387
|
-
|
|
388
|
-
const result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'stats'));
|
|
389
|
-
|
|
390
|
-
expect(result.text).toContain('Total');
|
|
391
|
-
expect(result.text).toContain('2');
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
it('shows export-ready counts by family', () => {
|
|
395
|
-
const { fingerprint: fp1 } = setupSample(tmpDir, 'art-stats-family-1', 'gpt-4');
|
|
396
|
-
updateReviewStatus(tmpDir, fp1, 'approved_for_training', 'Approved');
|
|
397
|
-
|
|
398
|
-
const result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'stats'));
|
|
399
|
-
|
|
400
|
-
expect(result.text).toContain('gpt-4');
|
|
401
|
-
expect(result.text).toContain('1');
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
// ---------------------------------------------------------------------------
|
|
406
|
-
// Integration: full review flow
|
|
407
|
-
// ---------------------------------------------------------------------------
|
|
408
|
-
|
|
409
|
-
describe('NocturnalReviewCommand full review flow', () => {
|
|
410
|
-
let tmpDir: string;
|
|
411
|
-
|
|
412
|
-
beforeEach(() => {
|
|
413
|
-
tmpDir = makeTmpDir();
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
afterEach(() => {
|
|
417
|
-
rmdir(tmpDir);
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
it('full flow: list → set-family → approve → export-ready', () => {
|
|
421
|
-
// 1. Register sample without family
|
|
422
|
-
const { fingerprint } = setupSample(tmpDir, 'art-full-flow', null);
|
|
423
|
-
|
|
424
|
-
// 2. Verify it's pending
|
|
425
|
-
let result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'list'));
|
|
426
|
-
expect(result.text).toContain(fingerprint.substring(0, 16));
|
|
427
|
-
|
|
428
|
-
// 3. Set family
|
|
429
|
-
result = handleNocturnalReviewCommand(
|
|
430
|
-
makeCtx(tmpDir, `set-family ${fingerprint} gpt-4`)
|
|
431
|
-
);
|
|
432
|
-
expect(result.text).toContain('gpt-4');
|
|
433
|
-
|
|
434
|
-
// 4. Approve
|
|
435
|
-
result = handleNocturnalReviewCommand(
|
|
436
|
-
makeCtx(tmpDir, `approve ${fingerprint} Good sample`)
|
|
437
|
-
);
|
|
438
|
-
expect(result.text).toContain('approved for training');
|
|
439
|
-
|
|
440
|
-
// 5. Verify stats show approved
|
|
441
|
-
result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'stats'));
|
|
442
|
-
expect(result.text).toContain('Approved for training');
|
|
443
|
-
|
|
444
|
-
// 6. Verify not in pending list anymore
|
|
445
|
-
result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'list'));
|
|
446
|
-
expect(result.text).not.toContain(fingerprint.substring(0, 16));
|
|
447
|
-
});
|
|
448
|
-
});
|