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
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
getDatasetRecord,
|
|
28
28
|
updateReviewStatus,
|
|
29
29
|
updateTargetModelFamily,
|
|
30
|
-
readDatasetArtifact,
|
|
31
30
|
getDatasetStats,
|
|
32
31
|
type NocturnalDatasetRecord,
|
|
33
32
|
type NocturnalReviewStatus,
|
|
@@ -53,7 +52,7 @@ function formatRecord(record: NocturnalDatasetRecord, includeDetails = false): s
|
|
|
53
52
|
lines.push(` createdAt: ${new Date(record.createdAt).toLocaleString('en-US')}`);
|
|
54
53
|
|
|
55
54
|
if (includeDetails) {
|
|
56
|
-
const artifactPath = record
|
|
55
|
+
const {artifactPath} = record;
|
|
57
56
|
if (fs.existsSync(artifactPath)) {
|
|
58
57
|
try {
|
|
59
58
|
const content = fs.readFileSync(artifactPath, 'utf-8');
|
|
@@ -122,7 +121,7 @@ export function handleNocturnalReviewCommand(ctx: PluginCommandContext): PluginC
|
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
if (action === 'show') {
|
|
125
|
-
const fingerprint = parts
|
|
124
|
+
const [, fingerprint] = parts;
|
|
126
125
|
if (!fingerprint) {
|
|
127
126
|
return {
|
|
128
127
|
text: zh
|
|
@@ -149,7 +148,7 @@ export function handleNocturnalReviewCommand(ctx: PluginCommandContext): PluginC
|
|
|
149
148
|
}
|
|
150
149
|
|
|
151
150
|
if (action === 'approve') {
|
|
152
|
-
const fingerprint = parts
|
|
151
|
+
const [, fingerprint] = parts;
|
|
153
152
|
const reason = parts.slice(2).join(' ') || (zh ? 'Approved by human reviewer' : 'Approved by human reviewer');
|
|
154
153
|
|
|
155
154
|
if (!fingerprint) {
|
|
@@ -203,7 +202,7 @@ export function handleNocturnalReviewCommand(ctx: PluginCommandContext): PluginC
|
|
|
203
202
|
}
|
|
204
203
|
|
|
205
204
|
if (action === 'reject') {
|
|
206
|
-
const fingerprint = parts
|
|
205
|
+
const [, fingerprint] = parts;
|
|
207
206
|
const reason = parts.slice(2).join(' ') || (zh ? 'Rejected by human reviewer' : 'Rejected by human reviewer');
|
|
208
207
|
|
|
209
208
|
if (!fingerprint) {
|
|
@@ -249,8 +248,7 @@ export function handleNocturnalReviewCommand(ctx: PluginCommandContext): PluginC
|
|
|
249
248
|
}
|
|
250
249
|
|
|
251
250
|
if (action === 'set-family') {
|
|
252
|
-
const fingerprint = parts
|
|
253
|
-
const family = parts[2];
|
|
251
|
+
const [, fingerprint, family] = parts;
|
|
254
252
|
|
|
255
253
|
if (!fingerprint || !family) {
|
|
256
254
|
return {
|
|
@@ -24,15 +24,12 @@
|
|
|
24
24
|
* /nocturnal-rollout show-promotion <checkpointId>
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
28
27
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
29
|
-
import type { TrainableWorkerProfile } from '../core/external-training-contract.js';
|
|
30
28
|
import {
|
|
31
29
|
evaluatePromotionGate,
|
|
32
30
|
advancePromotion,
|
|
33
31
|
getPromotionRecord,
|
|
34
32
|
listPromotionsByState,
|
|
35
|
-
rejectCheckpoint,
|
|
36
33
|
DEFAULT_BASELINE_METRICS,
|
|
37
34
|
DEFAULT_MIN_DELTA,
|
|
38
35
|
DEFAULT_ALLOWED_MARGIN,
|
|
@@ -44,9 +41,6 @@ import {
|
|
|
44
41
|
disableRoutingForProfile,
|
|
45
42
|
rollbackDeployment,
|
|
46
43
|
getDeployment,
|
|
47
|
-
getDeploymentLineage,
|
|
48
|
-
listDeployments,
|
|
49
|
-
getFullDeploymentRegistry,
|
|
50
44
|
isRoutingEnabledForProfile,
|
|
51
45
|
type WorkerProfile,
|
|
52
46
|
} from '../core/model-deployment-registry.js';
|
|
@@ -61,7 +55,6 @@ import {
|
|
|
61
55
|
} from '../core/shadow-observation-registry.js';
|
|
62
56
|
import {
|
|
63
57
|
getCheckpoint,
|
|
64
|
-
listCheckpoints,
|
|
65
58
|
} from '../core/model-training-registry.js';
|
|
66
59
|
|
|
67
60
|
function isZh(ctx: PluginCommandContext): boolean {
|
|
@@ -473,10 +466,6 @@ Routing: ${deployment.routingEnabled ? 'Enabled' : 'Disabled'}`,
|
|
|
473
466
|
? getCheckpoint(workspaceDir, deployment.activeCheckpointId)
|
|
474
467
|
: null;
|
|
475
468
|
|
|
476
|
-
const lineage = deployment.activeCheckpointId
|
|
477
|
-
? getDeploymentLineage(workspaceDir, profile)
|
|
478
|
-
: null;
|
|
479
|
-
|
|
480
469
|
return {
|
|
481
470
|
text: zh
|
|
482
471
|
? `=== ${profile} 部署状态 ===
|
|
@@ -583,7 +572,7 @@ ${promotion.previousPromotionId ? `Previous Promotion: ${promotion.previousPromo
|
|
|
583
572
|
|
|
584
573
|
// ── List by State ─────────────────────────────────────────────────────
|
|
585
574
|
if (subcommand === 'list-by-state') {
|
|
586
|
-
const stateArg = parts
|
|
575
|
+
const [, stateArg] = parts;
|
|
587
576
|
if (!stateArg) {
|
|
588
577
|
return { text: zh ? '错误: 需要状态参数' : 'Error: state argument required' };
|
|
589
578
|
}
|
|
@@ -26,18 +26,13 @@ import * as path from 'path';
|
|
|
26
26
|
import * as fs from 'fs';
|
|
27
27
|
import { execFileSync, spawn } from 'child_process';
|
|
28
28
|
import { fileURLToPath } from 'url';
|
|
29
|
-
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
30
29
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
31
30
|
import {
|
|
32
31
|
type TrainerBackendKind,
|
|
33
32
|
type HardwareTier,
|
|
34
|
-
generateExperimentId,
|
|
35
|
-
computeDatasetFingerprint,
|
|
36
33
|
} from '../core/external-training-contract.js';
|
|
37
34
|
import {
|
|
38
35
|
TrainingProgram,
|
|
39
|
-
DEFAULT_ORPO_HYPERPARAMETERS,
|
|
40
|
-
type CreateExperimentParams,
|
|
41
36
|
} from '../core/training-program.js';
|
|
42
37
|
import {
|
|
43
38
|
listTrainingRuns,
|
|
@@ -48,20 +43,11 @@ import {
|
|
|
48
43
|
getTrainingRegistryStats,
|
|
49
44
|
} from '../core/model-training-registry.js';
|
|
50
45
|
import { getDeployment } from '../core/model-deployment-registry.js';
|
|
51
|
-
import {
|
|
52
|
-
DEFAULT_MIN_DELTA,
|
|
53
|
-
DEFAULT_ALLOWED_MARGIN,
|
|
54
|
-
DEFAULT_BASELINE_METRICS,
|
|
55
|
-
} from '../core/promotion-gate.js';
|
|
56
46
|
|
|
57
47
|
function isZh(ctx: PluginCommandContext): boolean {
|
|
58
48
|
return String(ctx.config?.language || 'en').startsWith('zh');
|
|
59
49
|
}
|
|
60
50
|
|
|
61
|
-
function zh(cond: string): string {
|
|
62
|
-
return cond;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
51
|
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
66
52
|
const REPO_ROOT = path.resolve(MODULE_DIR, '..', '..', '..', '..');
|
|
67
53
|
const TRAINER_SCRIPTS_DIR = path.join(REPO_ROOT, 'scripts', 'nocturnal', 'trainer');
|
|
@@ -111,24 +97,6 @@ function formatTrainingRun(run: ReturnType<typeof getTrainingRun>, zh: boolean):
|
|
|
111
97
|
return lines.join('\n ');
|
|
112
98
|
}
|
|
113
99
|
|
|
114
|
-
/**
|
|
115
|
-
* Format checkpoint for display.
|
|
116
|
-
*/
|
|
117
|
-
function formatCheckpoint(cp: ReturnType<typeof getCheckpoint>, zh: boolean): string {
|
|
118
|
-
if (!cp) return zh ? '未找到' : 'Not found';
|
|
119
|
-
const lines = [
|
|
120
|
-
`ID: ${cp.checkpointId.substring(0, 8)}...`,
|
|
121
|
-
`Family: ${cp.targetModelFamily}`,
|
|
122
|
-
`Artifact: ${cp.artifactPath}`,
|
|
123
|
-
`Deployable: ${cp.deployable ? (zh ? '是' : 'Yes') : (zh ? '否' : 'No')}`,
|
|
124
|
-
`Created: ${new Date(cp.createdAt).toLocaleString()}`,
|
|
125
|
-
];
|
|
126
|
-
if (cp.lastEvalSummaryRef) {
|
|
127
|
-
lines.push(`Eval: ${cp.lastEvalSummaryRef.substring(0, 12)}...`);
|
|
128
|
-
}
|
|
129
|
-
return lines.join('\n ');
|
|
130
|
-
}
|
|
131
|
-
|
|
132
100
|
export async function handleNocturnalTrainCommand(ctx: PluginCommandContext): Promise<PluginCommandResult> {
|
|
133
101
|
const workspaceDir = (ctx.config?.workspaceDir as string) || process.cwd();
|
|
134
102
|
const zh = isZh(ctx);
|
|
@@ -261,7 +229,7 @@ Hardware tiers:
|
|
|
261
229
|
: `Error: manifest missing datasetFingerprint: ${manifestPath}`,
|
|
262
230
|
};
|
|
263
231
|
}
|
|
264
|
-
datasetFingerprint = manifest
|
|
232
|
+
({ datasetFingerprint } = manifest);
|
|
265
233
|
}
|
|
266
234
|
|
|
267
235
|
const benchmarkExportId = benchmarkExportIdArg || datasetExportId || 'benchmark-default';
|
|
@@ -284,7 +252,7 @@ Hardware tiers:
|
|
|
284
252
|
// The trainer reads spec from scripts/nocturnal/trainer/experiment-<id>.json.
|
|
285
253
|
// import-result reads spec from .state/nocturnal/checkpoints/experiment-<id>.json.
|
|
286
254
|
// Both must be written so the manual create-experiment -> trainer -> import-result chain works.
|
|
287
|
-
const spec = createResult
|
|
255
|
+
const {spec} = createResult;
|
|
288
256
|
const trainerSpecPath = path.join(TRAINER_SCRIPTS_DIR, `experiment-${spec.experimentId}.json`);
|
|
289
257
|
const workspaceSpecPath = path.join(workspaceDir, '.state', 'nocturnal', 'checkpoints', `experiment-${spec.experimentId}.json`);
|
|
290
258
|
const trainerSpecDir = path.dirname(trainerSpecPath);
|
|
@@ -302,11 +270,13 @@ Hardware tiers:
|
|
|
302
270
|
// This closes the gap in the create-experiment -> trainer -> import-result chain.
|
|
303
271
|
// NOTE: This blocks until training completes (could be minutes).
|
|
304
272
|
if (runNow) {
|
|
305
|
-
|
|
273
|
+
|
|
274
|
+
const {spec} = createResult;
|
|
306
275
|
const baseDir = TRAINER_SCRIPTS_DIR;
|
|
307
276
|
const scriptPath = path.join(baseDir, 'main.py');
|
|
308
277
|
const specPath = path.join(baseDir, `experiment-${spec.experimentId}.json`);
|
|
309
|
-
|
|
278
|
+
|
|
279
|
+
const {outputDir} = spec;
|
|
310
280
|
const resultFilePath = path.join(outputDir, `result-${spec.experimentId}.json`);
|
|
311
281
|
|
|
312
282
|
// Write spec file
|
|
@@ -316,6 +286,7 @@ Hardware tiers:
|
|
|
316
286
|
}
|
|
317
287
|
fs.writeFileSync(specPath, JSON.stringify(spec, null, 2), 'utf-8');
|
|
318
288
|
|
|
289
|
+
|
|
319
290
|
let trainerResult!: import('../core/external-training-contract.js').TrainingExperimentResult;
|
|
320
291
|
|
|
321
292
|
try {
|
|
@@ -423,6 +394,7 @@ Hardware tiers:
|
|
|
423
394
|
|
|
424
395
|
// Process trainer result (register checkpoint)
|
|
425
396
|
// dry_run returns null (no checkpoint); other statuses throw on error
|
|
397
|
+
|
|
426
398
|
let processed: { checkpointId: string; checkpointRef: string } | null;
|
|
427
399
|
try {
|
|
428
400
|
processed = program.processResult({
|
|
@@ -523,7 +495,7 @@ Next steps:
|
|
|
523
495
|
|
|
524
496
|
// ── Show Experiment ───────────────────────────────────────────────────
|
|
525
497
|
if (subcommand === 'show-experiment') {
|
|
526
|
-
const experimentId = parts
|
|
498
|
+
const [, experimentId] = parts;
|
|
527
499
|
if (!experimentId) {
|
|
528
500
|
return { text: zh ? '错误: 需要实验 ID' : 'Error: experiment ID required' };
|
|
529
501
|
}
|
|
@@ -540,7 +512,7 @@ Next steps:
|
|
|
540
512
|
|
|
541
513
|
// ── Import Result ─────────────────────────────────────────────────────
|
|
542
514
|
if (subcommand === 'import-result') {
|
|
543
|
-
const experimentId = parts
|
|
515
|
+
const [, experimentId] = parts;
|
|
544
516
|
if (!experimentId) {
|
|
545
517
|
return { text: zh ? '错误: 需要实验 ID' : 'Error: experiment ID required' };
|
|
546
518
|
}
|
|
@@ -564,6 +536,7 @@ Next steps:
|
|
|
564
536
|
}
|
|
565
537
|
}
|
|
566
538
|
|
|
539
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: JSON.parse returns dynamic JSON - type unknown at parse time, narrowed via type narrowing below
|
|
567
540
|
let result: any;
|
|
568
541
|
try {
|
|
569
542
|
result = JSON.parse(resultJson);
|
|
@@ -594,6 +567,7 @@ Next steps:
|
|
|
594
567
|
|
|
595
568
|
// Process the result
|
|
596
569
|
const program = new TrainingProgram(workspaceDir);
|
|
570
|
+
|
|
597
571
|
let processed: { checkpointId: string; checkpointRef: string } | null;
|
|
598
572
|
try {
|
|
599
573
|
processed = program.processResult({
|
|
@@ -673,11 +647,11 @@ Next steps:
|
|
|
673
647
|
let benchmarkId = benchmarkIdArg || `bench-${Date.now()}`;
|
|
674
648
|
let delta = deltaArg ? parseFloat(deltaArg) : NaN;
|
|
675
649
|
let verdict: 'fail' | 'pass' | 'compare_only' = verdictArg === 'pass' || verdictArg === 'fail' || verdictArg === 'compare_only'
|
|
676
|
-
? (verdictArg
|
|
650
|
+
? (verdictArg)
|
|
677
651
|
: 'compare_only';
|
|
678
652
|
let baselineScore = baselineScoreArg ? parseFloat(baselineScoreArg) : 0.5;
|
|
679
653
|
let candidateScore = candidateScoreArg ? parseFloat(candidateScoreArg) : 0.5;
|
|
680
|
-
const mode = (modeArg === 'prompt_assisted' ? 'prompt_assisted' : 'reduced_prompt')
|
|
654
|
+
const mode: 'prompt_assisted' | 'reduced_prompt' = (modeArg === 'prompt_assisted' ? 'prompt_assisted' : 'reduced_prompt');
|
|
681
655
|
|
|
682
656
|
// --- Run benchmark mode: execute real benchmark to get scores ---
|
|
683
657
|
// This closes the gap in the attach-eval command chain.
|
|
@@ -717,7 +691,7 @@ Next steps:
|
|
|
717
691
|
if (checkpoint.trainRunId) {
|
|
718
692
|
const run = getTrainingRun(workspaceDir, checkpoint.trainRunId);
|
|
719
693
|
if (run?.exportId) {
|
|
720
|
-
exportId = run
|
|
694
|
+
({ exportId } = run);
|
|
721
695
|
}
|
|
722
696
|
}
|
|
723
697
|
const scorerType = 'local-model'; // Use real model scorer
|
|
@@ -780,11 +754,11 @@ Next steps:
|
|
|
780
754
|
};
|
|
781
755
|
}
|
|
782
756
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
757
|
+
({
|
|
758
|
+
delta: { delta, baselineScore, candidateScore },
|
|
759
|
+
benchmarkId,
|
|
760
|
+
verdict,
|
|
761
|
+
} = benchmarkResult);
|
|
788
762
|
} else {
|
|
789
763
|
// Manual mode: require explicit delta and verdict
|
|
790
764
|
if (!deltaArg || !verdictArg) {
|
|
@@ -855,7 +829,7 @@ Next steps:
|
|
|
855
829
|
|
|
856
830
|
// ── Show Lineage ─────────────────────────────────────────────────────
|
|
857
831
|
if (subcommand === 'show-lineage') {
|
|
858
|
-
const checkpointId = parts
|
|
832
|
+
const [, checkpointId] = parts;
|
|
859
833
|
if (!checkpointId) {
|
|
860
834
|
return { text: zh ? '错误: 需要 checkpointId' : 'Error: checkpointId required' };
|
|
861
835
|
}
|
package/src/commands/pain.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resetFriction, getSession } from '../core/session-tracker.js';
|
|
2
2
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
3
3
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
4
4
|
import type { EmpathyEventStats } from '../types/event-types.js';
|
|
@@ -6,7 +6,7 @@ import type { EmpathyEventStats } from '../types/event-types.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* Creates a visual progress bar (e.g., [██████░░░░])
|
|
8
8
|
*/
|
|
9
|
-
function createProgressBar(value: number, max: number, length
|
|
9
|
+
function createProgressBar(value: number, max: number, length = 10): string {
|
|
10
10
|
const filledLength = Math.round((value / max) * length);
|
|
11
11
|
const emptyLength = length - filledLength;
|
|
12
12
|
return `[${'█'.repeat(filledLength)}${'░'.repeat(emptyLength)}]`;
|
|
@@ -15,7 +15,7 @@ function createProgressBar(value: number, max: number, length: number = 10): str
|
|
|
15
15
|
/**
|
|
16
16
|
* Creates a mini bar for daily trends
|
|
17
17
|
*/
|
|
18
|
-
function createMiniBar(count: number, max: number, length
|
|
18
|
+
function createMiniBar(count: number, max: number, length = 6): string {
|
|
19
19
|
const filledLength = Math.round((count / max) * length);
|
|
20
20
|
return '█'.repeat(filledLength) + '░'.repeat(length - filledLength);
|
|
21
21
|
}
|
|
@@ -89,12 +89,14 @@ export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResul
|
|
|
89
89
|
const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
|
|
90
90
|
const lang = (ctx.config?.language as string) || 'en';
|
|
91
91
|
const isZh = lang === 'zh';
|
|
92
|
-
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: sessionId injected by OpenClaw plugin framework - type not available in PluginCommandContext
|
|
93
|
+
const {sessionId} = (ctx as any);
|
|
93
94
|
|
|
94
95
|
const args = (ctx.args || '').trim();
|
|
95
96
|
|
|
96
97
|
// Handle empathy subcommand
|
|
97
98
|
if (args.startsWith('empathy')) {
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between main handler and empathy subcommand handler - reordering would break logical grouping
|
|
98
100
|
return handleEmpathySubcommand(wctx, args, sessionId, isZh);
|
|
99
101
|
}
|
|
100
102
|
|
|
@@ -118,12 +120,13 @@ export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResul
|
|
|
118
120
|
// Default: Show status
|
|
119
121
|
const session = sessionId ? getSession(sessionId) : undefined;
|
|
120
122
|
const gfi = session ? session.currentGfi : 0;
|
|
121
|
-
const dictionary = wctx
|
|
123
|
+
const {dictionary} = wctx;
|
|
122
124
|
const stats = dictionary.getStats();
|
|
123
125
|
|
|
124
126
|
const gfiBar = createProgressBar(gfi, 100, 15);
|
|
125
127
|
|
|
126
128
|
// Determine Mental Mode (aligned with prompt.ts logic)
|
|
129
|
+
// eslint-disable-next-line no-useless-assignment -- Reason: initial value unused due to immediate reassignment - keeping for type inference compatibility
|
|
127
130
|
let mentalMode = '';
|
|
128
131
|
if (isZh) {
|
|
129
132
|
if (gfi >= 70) mentalMode = '🚑 救赎模式 (HUMBLE_RECOVERY)';
|
|
@@ -136,6 +139,7 @@ export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResul
|
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
// Determine health status based on GFI
|
|
142
|
+
// eslint-disable-next-line no-useless-assignment -- Reason: initial values unused due to immediate reassignment in all branches
|
|
139
143
|
let healthLabel = 'Healthy';
|
|
140
144
|
let suggestionText = '';
|
|
141
145
|
|
|
@@ -214,6 +218,7 @@ export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResul
|
|
|
214
218
|
/**
|
|
215
219
|
* Handle /pd-status empathy subcommand
|
|
216
220
|
*/
|
|
221
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- Reason: empathy subcommand requires all 4 context params - refactoring would break API
|
|
217
222
|
function handleEmpathySubcommand(
|
|
218
223
|
wctx: WorkspaceContext,
|
|
219
224
|
args: string,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
2
2
|
import type { PluginCommandContext } from '../openclaw-sdk.js';
|
|
3
3
|
|
|
4
4
|
export function handlePrincipleRollbackCommand(ctx: PluginCommandContext): { text: string } {
|
|
@@ -12,7 +12,9 @@ export function handlePrincipleRollbackCommand(ctx: PluginCommandContext): { tex
|
|
|
12
12
|
return { text: isZh ? '用法: /pd-principle-rollback <principleId> [reason]' : 'Usage: /pd-principle-rollback <principleId> [reason]' };
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// #207/#210: Use WorkspaceContext to get evolutionReducer with stateDir
|
|
16
|
+
const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
|
|
17
|
+
const reducer = wctx.evolutionReducer;
|
|
16
18
|
const principle = reducer.getPrincipleById(principleId);
|
|
17
19
|
if (!principle) {
|
|
18
20
|
return { text: isZh ? `未找到原则: ${principleId}` : `Principle not found: ${principleId}` };
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promote Implementation Command — CLI `/pd-promote-impl`
|
|
3
|
+
* ========================================================
|
|
4
|
+
*
|
|
5
|
+
* PURPOSE: Manually promote a candidate implementation to active status
|
|
6
|
+
* after replay evaluation review.
|
|
7
|
+
*
|
|
8
|
+
* FLOW:
|
|
9
|
+
* 1. List candidates with replay reports
|
|
10
|
+
* 2. Show replay report summary for selected candidate
|
|
11
|
+
* 3. Ask for confirmation
|
|
12
|
+
* 4. On confirm: transition candidate->active, demote previous active->disabled
|
|
13
|
+
*
|
|
14
|
+
* VALIDATION:
|
|
15
|
+
* - Candidate must have at least one replay report with 'pass' decision
|
|
16
|
+
* - Only candidate -> active is valid for promotion
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import * as fs from 'fs';
|
|
20
|
+
import * as path from 'path';
|
|
21
|
+
import { ReplayEngine, formatReplayReport } from '../core/replay-engine.js';
|
|
22
|
+
import { refreshPrincipleLifecycle } from '../core/principle-internalization/lifecycle-refresh.js';
|
|
23
|
+
import {
|
|
24
|
+
findActiveImplementation,
|
|
25
|
+
loadLedger,
|
|
26
|
+
transitionImplementationState,
|
|
27
|
+
updateImplementation,
|
|
28
|
+
} from '../core/principle-tree-ledger.js';
|
|
29
|
+
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
30
|
+
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
31
|
+
import type { Implementation } from '../types/principle-tree-schema.js';
|
|
32
|
+
import { withLock } from '../utils/file-lock.js';
|
|
33
|
+
|
|
34
|
+
function getAllImplementations(stateDir: string): Implementation[] {
|
|
35
|
+
const ledger = loadLedger(stateDir);
|
|
36
|
+
return Object.values(ledger.tree.implementations);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function _handleListCandidates(
|
|
40
|
+
stateDir: string,
|
|
41
|
+
isZh: boolean,
|
|
42
|
+
): PluginCommandResult {
|
|
43
|
+
const engine = new ReplayEngine('', stateDir);
|
|
44
|
+
const allImpls = getAllImplementations(stateDir);
|
|
45
|
+
const candidates = allImpls.filter(
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: lifecycleState is a dynamic property added by the system - type not in official interface
|
|
47
|
+
(impl) => (impl as any).lifecycleState === 'candidate',
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (candidates.length === 0) {
|
|
51
|
+
return {
|
|
52
|
+
text: isZh
|
|
53
|
+
? '\nℹ️ 未找到候选实现。\n\n用法: /pd-promote-impl list'
|
|
54
|
+
: '\nℹ️ No candidate implementations found.\n\nUsage: /pd-promote-impl list',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let output = isZh ? '\n📋 候选实现列表\n' : '\n📋 Candidate Implementations\n';
|
|
59
|
+
output += `${'='.repeat(50)}\n`;
|
|
60
|
+
|
|
61
|
+
for (const impl of candidates) {
|
|
62
|
+
const hasPass = engine.hasPassingReport(impl.id);
|
|
63
|
+
const passBadge = hasPass
|
|
64
|
+
? isZh ? '✅ 有通过报告' : '✅ Has pass report'
|
|
65
|
+
: isZh ? '❌ 无通过报告' : '❌ No pass report';
|
|
66
|
+
output += ` ${impl.id}\n`;
|
|
67
|
+
output += ` Rule: ${impl.ruleId} | Version: ${impl.version}\n`;
|
|
68
|
+
output += ` Replay: ${passBadge}\n\n`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { text: output };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _handleShowReport(
|
|
75
|
+
stateDir: string,
|
|
76
|
+
implId: string,
|
|
77
|
+
isZh: boolean,
|
|
78
|
+
): PluginCommandResult {
|
|
79
|
+
const engine = new ReplayEngine('', stateDir);
|
|
80
|
+
const report = engine.getLatestReport(implId);
|
|
81
|
+
|
|
82
|
+
if (!report) {
|
|
83
|
+
return {
|
|
84
|
+
text: isZh
|
|
85
|
+
? `❌ 实现 ${implId} 没有回放报告。`
|
|
86
|
+
: `❌ No replay report found for implementation ${implId}.`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { text: formatReplayReport(report) };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface RunReplayOptions {
|
|
94
|
+
workspaceDir: string;
|
|
95
|
+
stateDir: string;
|
|
96
|
+
implId: string;
|
|
97
|
+
isZh: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function _handleRunReplay(options: RunReplayOptions): PluginCommandResult {
|
|
101
|
+
const { workspaceDir, stateDir, implId, isZh } = options;
|
|
102
|
+
const engine = new ReplayEngine(workspaceDir, stateDir);
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const report = engine.runReplayForImplementation(implId);
|
|
106
|
+
let text = formatReplayReport(report);
|
|
107
|
+
if (report.sampleFingerprints.length === 0) {
|
|
108
|
+
text += isZh
|
|
109
|
+
? '\n⚠️ 未找到已分类的 replay 样本。报告已生成,但当前结果只反映空样本集。\n'
|
|
110
|
+
: '\n⚠️ No classified replay samples were found. The report was generated, but it only reflects an empty sample set.\n';
|
|
111
|
+
}
|
|
112
|
+
return { text };
|
|
113
|
+
} catch (error: unknown) {
|
|
114
|
+
return {
|
|
115
|
+
text: isZh
|
|
116
|
+
? `❌ 回放评估失败: ${String(error)}`
|
|
117
|
+
: `❌ Replay evaluation failed: ${String(error)}`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface PromoteImplOptions {
|
|
123
|
+
workspaceDir: string;
|
|
124
|
+
stateDir: string;
|
|
125
|
+
implId: string;
|
|
126
|
+
isZh: boolean;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function _handlePromoteImpl(options: PromoteImplOptions): PluginCommandResult {
|
|
130
|
+
const { workspaceDir, stateDir, implId, isZh } = options;
|
|
131
|
+
const engine = new ReplayEngine('', stateDir);
|
|
132
|
+
const allImpls = getAllImplementations(stateDir);
|
|
133
|
+
const candidate = allImpls.find((i) => i.id === implId);
|
|
134
|
+
|
|
135
|
+
if (!candidate) {
|
|
136
|
+
return {
|
|
137
|
+
text: isZh
|
|
138
|
+
? `❌ 未找到实现: ${implId}`
|
|
139
|
+
: `❌ Implementation not found: ${implId}`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: lifecycleState is a dynamic property added by the system - type not in official interface
|
|
144
|
+
const currentState = (candidate as any).lifecycleState || 'candidate';
|
|
145
|
+
|
|
146
|
+
if (currentState !== 'candidate' && currentState !== 'disabled') {
|
|
147
|
+
return {
|
|
148
|
+
text: isZh
|
|
149
|
+
? `❌ 只能晋升 candidate 或 disabled 状态的实现。当前: ${currentState}`
|
|
150
|
+
: `❌ Can only promote 'candidate' or 'disabled' implementations. Current: ${currentState}`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!engine.hasPassingReport(implId)) {
|
|
155
|
+
return {
|
|
156
|
+
text: isZh
|
|
157
|
+
? `❌ 实现 ${implId} 没有通过的回放报告,无法晋升。\n\n请先运行回放评估。`
|
|
158
|
+
: `❌ Implementation ${implId} has no passing replay report. Promotion rejected.\n\nPlease run a replay evaluation first.`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const report = engine.getLatestReport(implId);
|
|
163
|
+
let output = '';
|
|
164
|
+
if (report) {
|
|
165
|
+
output = `${formatReplayReport(report)}\n`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (currentState === 'disabled') {
|
|
169
|
+
transitionImplementationState(stateDir, implId, 'active');
|
|
170
|
+
updateImplementation(stateDir, implId, {
|
|
171
|
+
disabledAt: undefined,
|
|
172
|
+
disabledBy: undefined,
|
|
173
|
+
disabledReason: undefined,
|
|
174
|
+
});
|
|
175
|
+
refreshPrincipleLifecycle(workspaceDir, stateDir);
|
|
176
|
+
|
|
177
|
+
output += isZh
|
|
178
|
+
? `\n✅ 实现已重新启用: ${implId}\n 状态: disabled -> active`
|
|
179
|
+
: `\n✅ Implementation re-enabled: ${implId}\n State: disabled -> active`;
|
|
180
|
+
|
|
181
|
+
return { text: output };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const activeForRule = findActiveImplementation(stateDir, candidate.ruleId);
|
|
185
|
+
|
|
186
|
+
if (activeForRule) {
|
|
187
|
+
updateImplementation(stateDir, implId, {
|
|
188
|
+
previousActive: activeForRule.id,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
transitionImplementationState(stateDir, implId, 'active');
|
|
193
|
+
|
|
194
|
+
if (activeForRule) {
|
|
195
|
+
transitionImplementationState(stateDir, activeForRule.id, 'disabled');
|
|
196
|
+
|
|
197
|
+
output += isZh
|
|
198
|
+
? `\n上一个活跃实现已禁用: ${activeForRule.id}\n 状态: active -> disabled`
|
|
199
|
+
: `\nPrevious active implementation disabled: ${activeForRule.id}\n State: active -> disabled`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const eventsDir = path.join(
|
|
203
|
+
stateDir,
|
|
204
|
+
'principles',
|
|
205
|
+
'implementations',
|
|
206
|
+
implId,
|
|
207
|
+
'events',
|
|
208
|
+
);
|
|
209
|
+
if (!fs.existsSync(eventsDir)) {
|
|
210
|
+
fs.mkdirSync(eventsDir, { recursive: true });
|
|
211
|
+
}
|
|
212
|
+
const eventPath = path.join(eventsDir, 'promotion.json');
|
|
213
|
+
const promotionEvent = {
|
|
214
|
+
eventType: 'promotion',
|
|
215
|
+
implementationId: implId,
|
|
216
|
+
ruleId: candidate.ruleId,
|
|
217
|
+
fromState: 'candidate',
|
|
218
|
+
toState: 'active',
|
|
219
|
+
previousActive: activeForRule?.id ?? null,
|
|
220
|
+
replayReportId: report
|
|
221
|
+
? report.generatedAt.replace(/[:.]/g, '-')
|
|
222
|
+
: null,
|
|
223
|
+
promotedAt: new Date().toISOString(),
|
|
224
|
+
};
|
|
225
|
+
withLock(eventPath, () => {
|
|
226
|
+
fs.writeFileSync(eventPath, JSON.stringify(promotionEvent, null, 2), 'utf-8');
|
|
227
|
+
});
|
|
228
|
+
refreshPrincipleLifecycle(workspaceDir, stateDir);
|
|
229
|
+
|
|
230
|
+
output += isZh
|
|
231
|
+
? `\n\n✅ 实现已晋升: ${implId}\n 状态: candidate -> active`
|
|
232
|
+
: `\n\n✅ Implementation promoted: ${implId}\n State: candidate -> active`;
|
|
233
|
+
|
|
234
|
+
return { text: output };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function handlePromoteImplCommand(ctx: PluginCommandContext): PluginCommandResult {
|
|
238
|
+
const workspaceDir = (ctx.config?.workspaceDir as string) || process.cwd();
|
|
239
|
+
const {stateDir} = WorkspaceContext.fromHookContext({ ...ctx, workspaceDir });
|
|
240
|
+
const lang = (ctx.config?.language as string) || 'en';
|
|
241
|
+
const isZh = lang === 'zh';
|
|
242
|
+
|
|
243
|
+
const args = (ctx.args || '').trim().split(/\s+/);
|
|
244
|
+
const subcommand = args[0] || '';
|
|
245
|
+
const implId = args[1] || '';
|
|
246
|
+
|
|
247
|
+
if (subcommand === 'list' || subcommand === '') {
|
|
248
|
+
return _handleListCandidates(stateDir, isZh);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (subcommand === 'show') {
|
|
252
|
+
if (!implId) {
|
|
253
|
+
return {
|
|
254
|
+
text: isZh
|
|
255
|
+
? '请指定要查看的实现ID: /pd-promote-impl show <implId>'
|
|
256
|
+
: 'Please specify an implementation ID: /pd-promote-impl show <implId>',
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
return _handleShowReport(stateDir, implId, isZh);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (subcommand === 'eval') {
|
|
263
|
+
if (!implId) {
|
|
264
|
+
return {
|
|
265
|
+
text: isZh
|
|
266
|
+
? '请指定要评估的实现ID: /pd-promote-impl eval <implId>'
|
|
267
|
+
: 'Please specify an implementation ID: /pd-promote-impl eval <implId>',
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
return _handleRunReplay({ workspaceDir, stateDir, implId, isZh });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return _handlePromoteImpl({ workspaceDir, stateDir, implId: subcommand, isZh });
|
|
274
|
+
}
|