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
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { withLock } from '../utils/file-lock.js';
|
|
4
|
+
import { normalizePath, isRisky, planStatus as getPlanStatus } from '../utils/io.js';
|
|
5
|
+
import {
|
|
6
|
+
listSamplesByClassification,
|
|
7
|
+
loadSampleContent,
|
|
8
|
+
} from './nocturnal-dataset.js';
|
|
9
|
+
import {
|
|
10
|
+
getImplementationAssetRoot,
|
|
11
|
+
loadEntrySource,
|
|
12
|
+
} from './code-implementation-storage.js';
|
|
13
|
+
import type {
|
|
14
|
+
NocturnalDatasetRecord,
|
|
15
|
+
SampleClassification,
|
|
16
|
+
} from './nocturnal-dataset.js';
|
|
17
|
+
import { loadLedger } from './principle-tree-ledger.js';
|
|
18
|
+
import type { Implementation } from '../types/principle-tree-schema.js';
|
|
19
|
+
import type { RuleHostHelpers } from './rule-host-helpers.js';
|
|
20
|
+
import { createRuleHostHelpers } from './rule-host-helpers.js';
|
|
21
|
+
import type { RuleHostInput, RuleHostResult } from './rule-host-types.js';
|
|
22
|
+
import { loadRuleImplementationModule } from './rule-implementation-runtime.js';
|
|
23
|
+
import {
|
|
24
|
+
getNocturnalSessionSnapshot,
|
|
25
|
+
type NocturnalGateBlock,
|
|
26
|
+
type NocturnalSessionSnapshot,
|
|
27
|
+
type NocturnalToolCall,
|
|
28
|
+
} from './nocturnal-trajectory-extractor.js';
|
|
29
|
+
import { TrajectoryRegistry } from './trajectory.js';
|
|
30
|
+
|
|
31
|
+
export interface ReplaySample {
|
|
32
|
+
fingerprint: string;
|
|
33
|
+
classification: SampleClassification;
|
|
34
|
+
content: unknown;
|
|
35
|
+
expectedOutcome: {
|
|
36
|
+
shouldBlock?: boolean;
|
|
37
|
+
shouldPass?: boolean;
|
|
38
|
+
expectedPrinciple?: string;
|
|
39
|
+
};
|
|
40
|
+
record: NocturnalDatasetRecord;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ReplayResult {
|
|
44
|
+
sampleFingerprint: string;
|
|
45
|
+
classification: SampleClassification;
|
|
46
|
+
passed: boolean;
|
|
47
|
+
reason?: string;
|
|
48
|
+
decision: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ClassificationSummary {
|
|
52
|
+
total: number;
|
|
53
|
+
passed: number;
|
|
54
|
+
failed: number;
|
|
55
|
+
details: ReplayResult[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ReplayReport {
|
|
59
|
+
overallDecision: 'pass' | 'fail' | 'needs-review';
|
|
60
|
+
replayResults: {
|
|
61
|
+
painNegative: ClassificationSummary;
|
|
62
|
+
successPositive: ClassificationSummary;
|
|
63
|
+
principleAnchor: ClassificationSummary;
|
|
64
|
+
};
|
|
65
|
+
blockers: string[];
|
|
66
|
+
generatedAt: string;
|
|
67
|
+
implementationId: string;
|
|
68
|
+
sampleFingerprints: string[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface CandidateEvaluator {
|
|
72
|
+
/* eslint-disable no-unused-vars -- Reason: interface method params are type signatures */
|
|
73
|
+
evaluate(sample: unknown): { passed: boolean; reason?: string; decision: string };
|
|
74
|
+
/* eslint-enable no-unused-vars */
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class ReplayEngine {
|
|
78
|
+
private readonly workspaceDir: string;
|
|
79
|
+
private readonly stateDir: string;
|
|
80
|
+
|
|
81
|
+
constructor(workspaceDir: string, stateDir: string) {
|
|
82
|
+
this.workspaceDir = workspaceDir;
|
|
83
|
+
this.stateDir = stateDir;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
loadSamples(classifications: SampleClassification[]): ReplaySample[] {
|
|
87
|
+
const samples: ReplaySample[] = [];
|
|
88
|
+
|
|
89
|
+
for (const classification of classifications) {
|
|
90
|
+
const records = listSamplesByClassification(this.workspaceDir, classification);
|
|
91
|
+
|
|
92
|
+
for (const record of records) {
|
|
93
|
+
try {
|
|
94
|
+
const content = loadSampleContent(this.workspaceDir, record);
|
|
95
|
+
const expectedOutcome = this._deriveExpectedOutcome(record);
|
|
96
|
+
|
|
97
|
+
samples.push({
|
|
98
|
+
fingerprint: record.sampleFingerprint,
|
|
99
|
+
classification,
|
|
100
|
+
content,
|
|
101
|
+
expectedOutcome,
|
|
102
|
+
record,
|
|
103
|
+
});
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.warn(
|
|
106
|
+
`[ReplayEngine] Skipping sample ${record.sampleFingerprint}: ${String(err)}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return samples;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Public API method that delegates to evaluator, no instance state needed
|
|
116
|
+
runSingleSample(sample: ReplaySample, evaluator: CandidateEvaluator): ReplayResult {
|
|
117
|
+
const evaluation = evaluator.evaluate(sample);
|
|
118
|
+
return {
|
|
119
|
+
sampleFingerprint: sample.fingerprint,
|
|
120
|
+
classification: sample.classification,
|
|
121
|
+
passed: evaluation.passed,
|
|
122
|
+
reason: evaluation.passed ? undefined : evaluation.reason,
|
|
123
|
+
decision: evaluation.decision,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
runReplay(
|
|
128
|
+
candidateImplId: string,
|
|
129
|
+
evaluator: CandidateEvaluator,
|
|
130
|
+
classifications?: SampleClassification[]
|
|
131
|
+
): ReplayReport {
|
|
132
|
+
const selectedClassifications: SampleClassification[] = classifications ?? [
|
|
133
|
+
'pain-negative',
|
|
134
|
+
'success-positive',
|
|
135
|
+
'principle-anchor',
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const samples = this.loadSamples(selectedClassifications);
|
|
139
|
+
const allResults = samples.map((sample) => this.runSingleSample(sample, evaluator));
|
|
140
|
+
const report = this._buildReport(candidateImplId, allResults);
|
|
141
|
+
this._persistReport(report);
|
|
142
|
+
return report;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
runReplayForImplementation(
|
|
146
|
+
implementationId: string,
|
|
147
|
+
classifications?: SampleClassification[],
|
|
148
|
+
): ReplayReport {
|
|
149
|
+
const implementation = this._getImplementationById(implementationId);
|
|
150
|
+
if (!implementation) {
|
|
151
|
+
throw new Error(`Implementation not found: ${implementationId}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const evaluator = this._createEvaluatorForImplementation(implementation);
|
|
155
|
+
return this.runReplay(implementationId, evaluator, classifications);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
listReports(implementationId: string): ReplayReport[] {
|
|
159
|
+
const reportDir = path.join(
|
|
160
|
+
getImplementationAssetRoot(this.stateDir, implementationId),
|
|
161
|
+
'replays'
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
if (!fs.existsSync(reportDir)) return [];
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const files = fs.readdirSync(reportDir).filter((file) => file.endsWith('.json'));
|
|
168
|
+
return files
|
|
169
|
+
.sort()
|
|
170
|
+
.reverse()
|
|
171
|
+
.map((file) => {
|
|
172
|
+
const content = fs.readFileSync(path.join(reportDir, file), 'utf-8');
|
|
173
|
+
return JSON.parse(content) as ReplayReport;
|
|
174
|
+
});
|
|
175
|
+
} catch {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
getLatestReport(implementationId: string): ReplayReport | null {
|
|
181
|
+
const reports = this.listReports(implementationId);
|
|
182
|
+
return reports.length > 0 ? reports[0] : null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
hasPassingReport(implementationId: string): boolean {
|
|
186
|
+
return this.listReports(implementationId).some((report) => report.overallDecision === 'pass');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private _getImplementationById(implementationId: string): Implementation | null {
|
|
190
|
+
const ledger = loadLedger(this.stateDir);
|
|
191
|
+
return ledger.tree.implementations[implementationId] ?? null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private _createEvaluatorForImplementation(implementation: Implementation): CandidateEvaluator {
|
|
195
|
+
const sourceCode = loadEntrySource(this.stateDir, implementation.id);
|
|
196
|
+
if (!sourceCode) {
|
|
197
|
+
throw new Error(`Implementation asset entry source missing: ${implementation.id}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const moduleExports = loadRuleImplementationModule(sourceCode, implementation.id);
|
|
201
|
+
if (typeof moduleExports.evaluate !== 'function') {
|
|
202
|
+
throw new Error(`Implementation ${implementation.id} does not export evaluate().`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* eslint-disable no-unused-vars -- Reason: type-only parameters in type cast, not used at runtime */
|
|
206
|
+
const evaluate = moduleExports.evaluate as (
|
|
207
|
+
_input: RuleHostInput,
|
|
208
|
+
_helpers: RuleHostHelpers,
|
|
209
|
+
) => RuleHostResult;
|
|
210
|
+
/* eslint-enable no-unused-vars */
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
evaluate: (sample: unknown) => {
|
|
214
|
+
const replaySample = sample as ReplaySample;
|
|
215
|
+
const input = this._buildRuleHostInput(replaySample);
|
|
216
|
+
if (!input) {
|
|
217
|
+
return {
|
|
218
|
+
passed: false,
|
|
219
|
+
reason: `Could not build replay input for sample ${replaySample.fingerprint}.`,
|
|
220
|
+
decision: 'replay-input-missing',
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const result = evaluate(input, createRuleHostHelpers(input));
|
|
225
|
+
return this._scoreEvaluation(replaySample, result);
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private _buildRuleHostInput(sample: ReplaySample): RuleHostInput | null {
|
|
231
|
+
const snapshot = getNocturnalSessionSnapshot(
|
|
232
|
+
TrajectoryRegistry.get(this.workspaceDir),
|
|
233
|
+
sample.record.sessionId,
|
|
234
|
+
);
|
|
235
|
+
if (!snapshot) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const toolCall = this._selectToolCall(snapshot, sample.classification);
|
|
240
|
+
if (!toolCall) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const normalizedPath =
|
|
245
|
+
typeof toolCall.filePath === 'string' && toolCall.filePath.length > 0
|
|
246
|
+
? normalizePath(toolCall.filePath, this.workspaceDir)
|
|
247
|
+
: null;
|
|
248
|
+
const matchedGateBlock = this._matchGateBlock(snapshot.gateBlocks, toolCall);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
action: {
|
|
252
|
+
toolName: toolCall.toolName,
|
|
253
|
+
normalizedPath,
|
|
254
|
+
paramsSummary: {
|
|
255
|
+
artifactId: sample.record.artifactId,
|
|
256
|
+
sourceSnapshotRef: sample.record.sourceSnapshotRef,
|
|
257
|
+
classification: sample.classification,
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
workspace: {
|
|
261
|
+
isRiskPath:
|
|
262
|
+
Boolean(matchedGateBlock) ||
|
|
263
|
+
(normalizedPath !== null && this._isRiskPath(normalizedPath)),
|
|
264
|
+
planStatus:
|
|
265
|
+
matchedGateBlock?.planStatus === 'READY' ||
|
|
266
|
+
matchedGateBlock?.planStatus === 'DRAFT' ||
|
|
267
|
+
matchedGateBlock?.planStatus === 'NONE'
|
|
268
|
+
? matchedGateBlock.planStatus
|
|
269
|
+
: this._safePlanStatus(),
|
|
270
|
+
hasPlanFile: fs.existsSync(path.join(this.workspaceDir, 'PLAN.md')),
|
|
271
|
+
},
|
|
272
|
+
session: {
|
|
273
|
+
sessionId: sample.record.sessionId,
|
|
274
|
+
currentGfi: 0,
|
|
275
|
+
recentThinking: false,
|
|
276
|
+
},
|
|
277
|
+
evolution: {
|
|
278
|
+
epTier: 0,
|
|
279
|
+
},
|
|
280
|
+
derived: {
|
|
281
|
+
estimatedLineChanges: this._estimateLineChanges(toolCall),
|
|
282
|
+
bashRisk: this._inferBashRisk(toolCall),
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
288
|
+
private _selectToolCall(
|
|
289
|
+
snapshot: NocturnalSessionSnapshot,
|
|
290
|
+
classification: SampleClassification,
|
|
291
|
+
): NocturnalToolCall | null {
|
|
292
|
+
const byNewest = [...snapshot.toolCalls].sort(
|
|
293
|
+
(left, right) => new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime(),
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
if (classification === 'pain-negative') {
|
|
297
|
+
return (
|
|
298
|
+
byNewest.find((toolCall) => toolCall.outcome === 'blocked') ??
|
|
299
|
+
byNewest.find((toolCall) => toolCall.outcome === 'failure') ??
|
|
300
|
+
byNewest[0] ??
|
|
301
|
+
null
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (classification === 'success-positive' || classification === 'principle-anchor') {
|
|
306
|
+
return (
|
|
307
|
+
byNewest.find((toolCall) => toolCall.outcome === 'success') ??
|
|
308
|
+
byNewest.find((toolCall) => toolCall.outcome === 'failure') ??
|
|
309
|
+
byNewest[0] ??
|
|
310
|
+
null
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return byNewest[0] ?? null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
318
|
+
private _matchGateBlock(
|
|
319
|
+
gateBlocks: NocturnalGateBlock[],
|
|
320
|
+
toolCall: NocturnalToolCall,
|
|
321
|
+
): NocturnalGateBlock | null {
|
|
322
|
+
return (
|
|
323
|
+
[...gateBlocks]
|
|
324
|
+
.sort((left, right) => new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime())
|
|
325
|
+
.find((gateBlock) => gateBlock.toolName === toolCall.toolName) ?? null
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private _isRiskPath(normalizedPath: string): boolean {
|
|
330
|
+
try {
|
|
331
|
+
const profilePath = path.join(this.workspaceDir, 'PROFILE.json');
|
|
332
|
+
const riskPaths =
|
|
333
|
+
fs.existsSync(profilePath)
|
|
334
|
+
? (((JSON.parse(fs.readFileSync(profilePath, 'utf-8')) as { risk_paths?: unknown }).risk_paths as string[] | undefined) ?? [])
|
|
335
|
+
: [];
|
|
336
|
+
return isRisky(normalizedPath, riskPaths);
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private _safePlanStatus(): 'NONE' | 'DRAFT' | 'READY' | 'UNKNOWN' {
|
|
343
|
+
try {
|
|
344
|
+
const status = getPlanStatus(this.workspaceDir);
|
|
345
|
+
if (status === 'READY') return 'READY';
|
|
346
|
+
if (status === 'DRAFT') return 'DRAFT';
|
|
347
|
+
if (status === '') return 'NONE';
|
|
348
|
+
return 'UNKNOWN';
|
|
349
|
+
} catch {
|
|
350
|
+
return 'UNKNOWN';
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
355
|
+
private _estimateLineChanges(toolCall: NocturnalToolCall): number {
|
|
356
|
+
if (toolCall.toolName === 'edit' || toolCall.toolName === 'write') {
|
|
357
|
+
return 20;
|
|
358
|
+
}
|
|
359
|
+
return 0;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
363
|
+
private _inferBashRisk(toolCall: NocturnalToolCall): 'safe' | 'normal' | 'dangerous' | 'unknown' {
|
|
364
|
+
if (toolCall.toolName !== 'bash' && toolCall.toolName !== 'run_shell_command') {
|
|
365
|
+
return 'unknown';
|
|
366
|
+
}
|
|
367
|
+
const errorText = `${toolCall.errorType ?? ''} ${toolCall.errorMessage ?? ''}`;
|
|
368
|
+
if (/\brm\s+-rf\b|\bchmod\b|\bchown\b|>\s*\/dev\//.test(errorText)) {
|
|
369
|
+
return 'dangerous';
|
|
370
|
+
}
|
|
371
|
+
return toolCall.outcome === 'success' ? 'safe' : 'normal';
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
375
|
+
private _scoreEvaluation(
|
|
376
|
+
sample: ReplaySample,
|
|
377
|
+
result: RuleHostResult,
|
|
378
|
+
): { passed: boolean; reason?: string; decision: string } {
|
|
379
|
+
switch (sample.classification) {
|
|
380
|
+
case 'pain-negative':
|
|
381
|
+
return {
|
|
382
|
+
passed: result.decision === 'block' || result.decision === 'requireApproval',
|
|
383
|
+
reason:
|
|
384
|
+
result.decision === 'block' || result.decision === 'requireApproval'
|
|
385
|
+
? undefined
|
|
386
|
+
: `Expected block/requireApproval but received ${result.decision}.`,
|
|
387
|
+
decision: result.decision,
|
|
388
|
+
};
|
|
389
|
+
case 'success-positive':
|
|
390
|
+
return {
|
|
391
|
+
passed: result.decision === 'allow' || !result.matched,
|
|
392
|
+
reason:
|
|
393
|
+
result.decision === 'allow' || !result.matched
|
|
394
|
+
? undefined
|
|
395
|
+
: `Expected allow/no-match but received ${result.decision}.`,
|
|
396
|
+
decision: result.decision,
|
|
397
|
+
};
|
|
398
|
+
case 'principle-anchor':
|
|
399
|
+
return {
|
|
400
|
+
passed: result.decision !== 'block',
|
|
401
|
+
reason:
|
|
402
|
+
result.decision !== 'block'
|
|
403
|
+
? undefined
|
|
404
|
+
: 'Principle-anchor sample should not regress to a hard block.',
|
|
405
|
+
decision: result.decision,
|
|
406
|
+
};
|
|
407
|
+
default:
|
|
408
|
+
return {
|
|
409
|
+
passed: false,
|
|
410
|
+
reason: 'Unknown replay classification.',
|
|
411
|
+
decision: result.decision,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private _buildReport(
|
|
417
|
+
implementationId: string,
|
|
418
|
+
results: ReplayResult[]
|
|
419
|
+
): ReplayReport {
|
|
420
|
+
const painNegative = results.filter((result) => result.classification === 'pain-negative');
|
|
421
|
+
const successPositive = results.filter((result) => result.classification === 'success-positive');
|
|
422
|
+
const principleAnchor = results.filter((result) => result.classification === 'principle-anchor');
|
|
423
|
+
|
|
424
|
+
const toSummary = (details: ReplayResult[]): ClassificationSummary => ({
|
|
425
|
+
total: details.length,
|
|
426
|
+
passed: details.filter((result) => result.passed).length,
|
|
427
|
+
failed: details.filter((result) => !result.passed).length,
|
|
428
|
+
details,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const painSummary = toSummary(painNegative);
|
|
432
|
+
const successSummary = toSummary(successPositive);
|
|
433
|
+
const anchorSummary = toSummary(principleAnchor);
|
|
434
|
+
const blockers: string[] = [];
|
|
435
|
+
|
|
436
|
+
for (const leak of painSummary.details.filter((result) => !result.passed)) {
|
|
437
|
+
blockers.push(
|
|
438
|
+
`PAIN-NEGATIVE LEAK: Sample ${leak.sampleFingerprint} was not blocked. ${leak.reason ?? ''}`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
for (const violation of anchorSummary.details.filter((result) => !result.passed)) {
|
|
443
|
+
blockers.push(
|
|
444
|
+
`PRINCIPLE-ANCHOR VIOLATION: Sample ${violation.sampleFingerprint} did not adhere. ${violation.reason ?? ''}`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
for (const falsePositive of successSummary.details.filter((result) => !result.passed)) {
|
|
449
|
+
blockers.push(
|
|
450
|
+
`FALSE POSITIVE: Sample ${falsePositive.sampleFingerprint} was incorrectly blocked. ${falsePositive.reason ?? ''}`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
overallDecision: this._determineDecision(painSummary, successSummary, anchorSummary),
|
|
456
|
+
replayResults: {
|
|
457
|
+
painNegative: painSummary,
|
|
458
|
+
successPositive: successSummary,
|
|
459
|
+
principleAnchor: anchorSummary,
|
|
460
|
+
},
|
|
461
|
+
blockers,
|
|
462
|
+
generatedAt: new Date().toISOString(),
|
|
463
|
+
implementationId,
|
|
464
|
+
sampleFingerprints: results.map((result) => result.sampleFingerprint),
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
469
|
+
private _determineDecision(
|
|
470
|
+
pain: ClassificationSummary,
|
|
471
|
+
success: ClassificationSummary,
|
|
472
|
+
anchor: ClassificationSummary
|
|
473
|
+
): 'pass' | 'fail' | 'needs-review' {
|
|
474
|
+
if (pain.failed > 0) return 'fail';
|
|
475
|
+
if (anchor.failed > 0) return 'fail';
|
|
476
|
+
if (success.failed > 0) return 'needs-review';
|
|
477
|
+
return 'pass';
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private _persistReport(report: ReplayReport): void {
|
|
481
|
+
const reportDir = path.join(
|
|
482
|
+
getImplementationAssetRoot(this.stateDir, report.implementationId),
|
|
483
|
+
'replays'
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
if (!fs.existsSync(reportDir)) {
|
|
487
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
491
|
+
const reportPath = path.join(reportDir, `${timestamp}.json`);
|
|
492
|
+
|
|
493
|
+
withLock(reportPath, () => {
|
|
494
|
+
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf-8');
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
499
|
+
private _deriveExpectedOutcome(
|
|
500
|
+
record: NocturnalDatasetRecord,
|
|
501
|
+
): ReplaySample['expectedOutcome'] {
|
|
502
|
+
switch (record.classification) {
|
|
503
|
+
case 'pain-negative':
|
|
504
|
+
return { shouldBlock: true };
|
|
505
|
+
case 'success-positive':
|
|
506
|
+
return { shouldPass: true };
|
|
507
|
+
case 'principle-anchor':
|
|
508
|
+
return { expectedPrinciple: record.principleId };
|
|
509
|
+
default:
|
|
510
|
+
return {};
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export function formatReplayReport(report: ReplayReport): string {
|
|
516
|
+
const decisionEmoji =
|
|
517
|
+
report.overallDecision === 'pass'
|
|
518
|
+
? 'PASS'
|
|
519
|
+
: report.overallDecision === 'fail'
|
|
520
|
+
? 'FAIL'
|
|
521
|
+
: 'NEEDS-REVIEW';
|
|
522
|
+
|
|
523
|
+
let output = '';
|
|
524
|
+
output += '\nReplay Evaluation Report\n';
|
|
525
|
+
output += `${'='.repeat(50)}\n`;
|
|
526
|
+
output += `Implementation: ${report.implementationId}\n`;
|
|
527
|
+
output += `Generated At: ${report.generatedAt}\n`;
|
|
528
|
+
output += `Overall Decision: [${decisionEmoji}]\n\n`;
|
|
529
|
+
|
|
530
|
+
const formatSection = (
|
|
531
|
+
label: string,
|
|
532
|
+
summary: ClassificationSummary
|
|
533
|
+
) => {
|
|
534
|
+
const rate = summary.total > 0
|
|
535
|
+
? ((summary.passed / summary.total) * 100).toFixed(1)
|
|
536
|
+
: 'N/A';
|
|
537
|
+
let section = ` ${label}:\n`;
|
|
538
|
+
section += ` Total: ${summary.total} | Passed: ${summary.passed} | Failed: ${summary.failed}\n`;
|
|
539
|
+
section += ` Pass Rate: ${rate}%\n`;
|
|
540
|
+
if (summary.failed > 0) {
|
|
541
|
+
section += ' Failures:\n';
|
|
542
|
+
for (const detail of summary.details.filter((item) => !item.passed)) {
|
|
543
|
+
section += ` - ${detail.sampleFingerprint}: ${detail.reason ?? detail.decision}\n`;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return section;
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
output += formatSection('Pain-Negative Samples', report.replayResults.painNegative);
|
|
550
|
+
output += formatSection('Success-Positive Samples', report.replayResults.successPositive);
|
|
551
|
+
output += formatSection('Principle-Anchor Samples', report.replayResults.principleAnchor);
|
|
552
|
+
|
|
553
|
+
if (report.blockers.length > 0) {
|
|
554
|
+
output += '\nBlockers:\n';
|
|
555
|
+
for (const blocker of report.blockers) {
|
|
556
|
+
output += ` - ${blocker}\n`;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
output += `${'='.repeat(50)}\n`;
|
|
561
|
+
return output;
|
|
562
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* global NodeJS */
|
|
1
2
|
import * as fs from 'fs';
|
|
2
3
|
import { isRisky } from '../utils/io.js';
|
|
3
4
|
|
|
@@ -5,24 +6,24 @@ export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
|
5
6
|
|
|
6
7
|
export interface FileModification {
|
|
7
8
|
toolName: string;
|
|
8
|
-
params:
|
|
9
|
+
params: Record<string, unknown>;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function estimateLineChanges(modification: FileModification): number {
|
|
12
13
|
const { toolName, params } = modification;
|
|
13
14
|
|
|
14
15
|
if (toolName === 'write_file' || toolName === 'write') {
|
|
15
|
-
const content = params.content || '';
|
|
16
|
+
const content = (params.content as string) || '';
|
|
16
17
|
return content.split('\n').length;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
if (toolName === 'replace' || toolName === 'edit') {
|
|
20
|
-
const newContent = params.new_string || params.newText || '';
|
|
21
|
+
const newContent = (params.new_string as string) || (params.newText as string) || '';
|
|
21
22
|
return newContent.split('\n').length;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
if (toolName === 'apply_patch' || toolName === 'patch') {
|
|
25
|
-
const patch = params.patch || '';
|
|
26
|
+
const patch = (params.patch as string) || '';
|
|
26
27
|
// Rough estimate for patch files
|
|
27
28
|
return patch.split('\n').filter((l: string) => l.startsWith('+') || l.startsWith('-')).length;
|
|
28
29
|
}
|
|
@@ -92,6 +93,7 @@ export function getTargetFileLineCount(absoluteFilePath: string): number | null
|
|
|
92
93
|
* @param maxLines - Optional upper bound to prevent misconfiguration
|
|
93
94
|
* @returns Maximum allowed lines (at least minLines, at most maxLines if provided)
|
|
94
95
|
*/
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- Reason: percentage threshold calculation requires all 4 params - refactoring would break API
|
|
95
97
|
export function calculatePercentageThreshold(
|
|
96
98
|
targetLineCount: number,
|
|
97
99
|
percentage: number,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule Host Helpers — Minimal whitelisted helper surface for hosted implementations
|
|
3
|
+
*
|
|
4
|
+
* PURPOSE: Provide a constrained set of pure functions that hosted implementations
|
|
5
|
+
* can call. All values are pre-computed from the frozen RuleHostInput snapshot.
|
|
6
|
+
*
|
|
7
|
+
* SECURITY:
|
|
8
|
+
* - Helpers are a frozen object — cannot be modified by implementations
|
|
9
|
+
* - No filesystem, process, require, dynamic import, eval, Function, or network access
|
|
10
|
+
* - All functions are pure — no side effects, no external state access
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { RuleHostInput } from './rule-host-types.js';
|
|
14
|
+
|
|
15
|
+
export interface RuleHostHelpers {
|
|
16
|
+
isRiskPath(): boolean;
|
|
17
|
+
getToolName(): string;
|
|
18
|
+
getEstimatedLineChanges(): number;
|
|
19
|
+
getBashRisk(): 'safe' | 'normal' | 'dangerous' | 'unknown';
|
|
20
|
+
hasPlanFile(): boolean;
|
|
21
|
+
getPlanStatus(): 'NONE' | 'DRAFT' | 'READY' | 'UNKNOWN';
|
|
22
|
+
getCurrentEpiTier(): number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a frozen helper object from the pre-computed input snapshot.
|
|
27
|
+
* Implementations receive this via the vm context — they cannot modify it.
|
|
28
|
+
*/
|
|
29
|
+
export function createRuleHostHelpers(input: RuleHostInput): RuleHostHelpers {
|
|
30
|
+
return Object.freeze({
|
|
31
|
+
isRiskPath: () => input.workspace.isRiskPath,
|
|
32
|
+
getToolName: () => input.action.toolName,
|
|
33
|
+
getEstimatedLineChanges: () => input.derived.estimatedLineChanges,
|
|
34
|
+
getBashRisk: () => input.derived.bashRisk,
|
|
35
|
+
hasPlanFile: () => input.workspace.hasPlanFile,
|
|
36
|
+
getPlanStatus: () => input.workspace.planStatus,
|
|
37
|
+
getCurrentEpiTier: () => input.evolution.epTier,
|
|
38
|
+
});
|
|
39
|
+
}
|