principles-disciple 1.72.0 → 1.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +1 -3
- package/openclaw.plugin.json +10 -5
- package/package.json +17 -19
- package/scripts/acceptance-test.mjs +16 -73
- package/scripts/sync-plugin.mjs +382 -77
- package/src/commands/archive-impl.ts +2 -1
- package/src/commands/capabilities.ts +2 -2
- package/src/commands/context.ts +2 -2
- package/src/commands/disable-impl.ts +2 -1
- package/src/commands/evolution-status.ts +16 -16
- package/src/commands/export.ts +12 -67
- package/src/commands/pain.ts +91 -1
- package/src/commands/principle-rollback.ts +2 -1
- package/src/commands/promote-impl.ts +7 -43
- package/src/commands/rollback-impl.ts +2 -1
- package/src/commands/rollback.ts +2 -1
- package/src/commands/samples.ts +2 -1
- package/src/commands/thinking-os.ts +2 -1
- package/src/config/errors.ts +18 -2
- package/src/constants/diagnostician.ts +2 -2
- package/src/constants/tools.ts +2 -1
- package/src/core/__tests__/focus-history.test.ts +210 -0
- package/src/core/config.ts +1 -1
- package/src/core/correction-cue-learner.ts +2 -136
- package/src/core/correction-types.ts +16 -88
- package/src/core/dictionary.ts +19 -20
- package/src/core/empathy-keyword-matcher.ts +17 -289
- package/src/core/empathy-types.ts +18 -229
- package/src/core/event-log.ts +29 -132
- package/src/core/evolution-reducer.ts +21 -2
- package/src/core/evolution-types.ts +76 -464
- package/src/core/file-store.ts +80 -0
- package/src/core/focus-history.ts +228 -955
- package/src/core/local-worker-routing.ts +34 -314
- package/src/core/merge-gate-audit.ts +0 -195
- package/src/core/migration.ts +0 -1
- package/src/core/pain-diagnostic-gate.ts +154 -0
- package/src/core/pain-signal.ts +21 -138
- package/src/core/pain.ts +15 -88
- package/src/core/path-resolver.ts +0 -1
- package/src/core/paths.ts +0 -1
- package/src/core/pd-task-reconciler.ts +26 -115
- package/src/core/pd-task-service.ts +9 -9
- package/src/core/pd-task-types.ts +23 -127
- package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
- package/src/core/principle-compiler/code-validator.ts +15 -42
- package/src/core/principle-compiler/compiler.ts +100 -15
- package/src/core/principle-compiler/index.ts +5 -2
- package/src/core/principle-compiler/template-generator.ts +4 -104
- package/src/core/principle-injection.ts +10 -202
- package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
- package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
- package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
- package/src/core/principle-tree-ledger-adapter.ts +145 -0
- package/src/core/principle-tree-ledger.ts +8 -6
- package/src/core/reflection/reflection-context.ts +14 -109
- package/src/core/replay-engine.ts +8 -500
- package/src/core/rule-host-helpers.ts +5 -35
- package/src/core/rule-host-types.ts +10 -82
- package/src/core/rule-host.ts +6 -63
- package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
- package/src/core/session-tracker.ts +87 -101
- package/src/core/shadow-observation-registry.ts +19 -48
- package/src/core/trajectory.ts +3 -1
- package/src/core/workflow-funnel-loader.ts +62 -68
- package/src/core/workspace-context.ts +46 -0
- package/src/core/workspace-dir-service.ts +1 -1
- package/src/core/workspace-dir-validation.ts +18 -9
- package/src/hooks/AGENTS.md +1 -1
- package/src/hooks/gate-block-helper.ts +71 -64
- package/src/hooks/gate.ts +183 -31
- package/src/hooks/lifecycle.ts +30 -32
- package/src/hooks/llm.ts +60 -32
- package/src/hooks/pain.ts +297 -103
- package/src/hooks/prompt.ts +400 -440
- package/src/hooks/subagent.ts +2 -29
- package/src/i18n/commands.ts +2 -10
- package/src/index.ts +95 -85
- package/src/openclaw-sdk.ts +311 -0
- package/src/service/central-database.ts +8 -4
- package/src/service/evolution-queue-migration.ts +2 -1
- package/src/service/evolution-worker.ts +163 -1786
- package/src/service/internalization-trigger-adapter.ts +302 -0
- package/src/service/keyword-optimization-service.ts +4 -4
- package/src/service/monitoring-query-service.ts +1 -215
- package/src/service/queue-io.ts +60 -331
- package/src/service/runtime-summary-service.ts +59 -16
- package/src/service/subagent-workflow/index.ts +0 -41
- package/src/service/subagent-workflow/types.ts +9 -120
- package/src/service/subagent-workflow/workflow-store.ts +2 -119
- package/src/service/workflow-watchdog.ts +0 -43
- package/src/types/event-payload.ts +16 -74
- package/src/types/event-types.ts +38 -547
- package/src/types/hygiene-types.ts +7 -30
- package/src/types/principle-tree-schema.ts +20 -222
- package/src/types/queue.ts +15 -70
- package/src/types/runtime-summary.ts +5 -49
- package/src/utils/io.ts +8 -20
- package/src/utils/retry.ts +1 -1
- package/src/utils/shadow-fingerprint.ts +2 -2
- package/src/utils/workspace-resolver.ts +50 -0
- package/templates/langs/en/core/AGENTS.md +7 -7
- package/templates/langs/en/core/BOOT.md +1 -1
- package/templates/langs/en/core/HEARTBEAT.md +2 -2
- package/templates/langs/en/principles/THINKING_OS.md +3 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/en/skills/evolve-task/SKILL.md +3 -3
- package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-mentor/SKILL.md +2 -3
- package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
- package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
- package/templates/langs/zh/core/AGENTS.md +7 -7
- package/templates/langs/zh/core/BOOT.md +1 -1
- package/templates/langs/zh/core/HEARTBEAT.md +2 -2
- package/templates/langs/zh/principles/THINKING_OS.md +3 -2
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
- package/templates/langs/zh/skills/evolve-task/SKILL.md +4 -4
- package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-mentor/SKILL.md +2 -3
- package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
- package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
- package/tests/build-artifacts.test.ts +1 -3
- package/tests/commands/evolution-status.test.ts +0 -118
- package/tests/core/bootstrap-rules.test.ts +1 -1
- package/tests/core/config.test.ts +1 -1
- package/tests/core/event-log.test.ts +35 -0
- package/tests/core/evolution-engine.test.ts +610 -0
- package/tests/core/file-store.test.ts +102 -0
- package/tests/core/focus-history.test.ts +203 -11
- package/tests/core/merge-gate-audit.test.ts +2 -169
- package/tests/core/migration.test.ts +7 -7
- package/tests/core/model-deployment-registry.test.ts +7 -1
- package/tests/core/model-training-registry.test.ts +19 -0
- package/tests/core/observability.test.ts +0 -1
- package/tests/core/pain-diagnostic-gate.test.ts +498 -0
- package/tests/core/pain.test.ts +0 -1
- package/tests/core/path-resolver.test.ts +1 -1
- package/tests/core/paths-refactor.test.ts +0 -22
- package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
- package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
- package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
- package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
- package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
- package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
- package/tests/core/reflection-context.test.ts +0 -14
- package/tests/core/replay-engine.test.ts +127 -215
- package/tests/core/rule-host-helpers.test.ts +2 -2
- package/tests/core/rule-implementation-runtime.test.ts +0 -27
- package/tests/core/workflow-funnel-loader.test.ts +162 -0
- package/tests/core/workspace-context.test.ts +2 -2
- package/tests/core/workspace-dir-validation.test.ts +8 -1
- package/tests/core-anti-growth.test.ts +191 -0
- package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
- package/tests/hooks/confirm-first-removal.test.ts +188 -0
- package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
- package/tests/hooks/gate-auto-correct.test.ts +665 -0
- package/tests/hooks/gate-no-path-write-tool.test.ts +172 -0
- package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
- package/tests/hooks/pain.test.ts +269 -12
- package/tests/hooks/prompt-characterization.test.ts +500 -0
- package/tests/hooks/prompt-size-guard.test.ts +32 -17
- package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
- package/tests/index.test.ts +94 -1
- package/tests/integration/auto-entry-gate.test.ts +248 -0
- package/tests/integration/internalization-trigger-guard.test.ts +69 -0
- package/tests/integration/m8-legacy-paths.test.ts +63 -0
- package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
- package/tests/plugin-config-resolution-cutover.test.ts +359 -0
- package/tests/runtime-v2-discovery-guard.test.ts +154 -0
- package/tests/service/central-database.test.ts +457 -0
- package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
- package/tests/service/evolution-worker.timeout.test.ts +11 -129
- package/tests/service/internalization-trigger-adapter.test.ts +251 -0
- package/tests/service/monitoring-query-service.test.ts +1 -47
- package/tests/service/queue-io.test.ts +1 -62
- package/tests/service/runtime-summary-service.test.ts +3 -1
- package/tests/service/workflow-watchdog.test.ts +0 -91
- package/tests/utils/file-lock.test.ts +5 -3
- package/tests/utils/session-key.test.ts +52 -0
- package/tests/utils/subagent-probe.test.ts +48 -1
- package/vitest.config.ts +4 -11
- package/.planning/codebase/ARCHITECTURE.md +0 -157
- package/.planning/codebase/CONCERNS.md +0 -145
- package/.planning/codebase/CONVENTIONS.md +0 -148
- package/.planning/codebase/INTEGRATIONS.md +0 -81
- package/.planning/codebase/STACK.md +0 -87
- package/.planning/codebase/STRUCTURE.md +0 -193
- package/.planning/codebase/TESTING.md +0 -243
- package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
- package/docs/COMMAND_REFERENCE.md +0 -76
- package/docs/COMMAND_REFERENCE_EN.md +0 -79
- package/scripts/build-web.mjs +0 -46
- package/scripts/diagnose-nocturnal.mjs +0 -537
- package/scripts/seed-nocturnal-scenarios.mjs +0 -384
- package/src/commands/nocturnal-review.ts +0 -322
- package/src/commands/nocturnal-rollout.ts +0 -790
- package/src/commands/nocturnal-train.ts +0 -986
- package/src/commands/pd-reflect.ts +0 -88
- package/src/core/adaptive-thresholds.ts +0 -478
- package/src/core/diagnostician-task-store.ts +0 -192
- package/src/core/nocturnal-arbiter.ts +0 -715
- package/src/core/nocturnal-artifact-lineage.ts +0 -116
- package/src/core/nocturnal-artificer.ts +0 -257
- package/src/core/nocturnal-candidate-scoring.ts +0 -530
- package/src/core/nocturnal-compliance.ts +0 -1146
- package/src/core/nocturnal-dataset.ts +0 -763
- package/src/core/nocturnal-executability.ts +0 -428
- package/src/core/nocturnal-export.ts +0 -499
- package/src/core/nocturnal-paths.ts +0 -240
- package/src/core/nocturnal-reasoning-deriver.ts +0 -343
- package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
- package/src/core/nocturnal-snapshot-contract.ts +0 -99
- package/src/core/nocturnal-trajectory-extractor.ts +0 -512
- package/src/core/nocturnal-trinity-types.ts +0 -218
- package/src/core/nocturnal-trinity.ts +0 -2680
- package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
- package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
- package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
- package/src/http/principles-console-route.ts +0 -709
- package/src/service/central-health-service.ts +0 -49
- package/src/service/central-overview-service.ts +0 -138
- package/src/service/control-ui-query-service.ts +0 -900
- package/src/service/cooldown-strategy.ts +0 -97
- package/src/service/evolution-pain-context.ts +0 -79
- package/src/service/evolution-query-service.ts +0 -407
- package/src/service/health-query-service.ts +0 -1038
- package/src/service/nocturnal-config.ts +0 -214
- package/src/service/nocturnal-runtime.ts +0 -734
- package/src/service/nocturnal-service.ts +0 -1605
- package/src/service/nocturnal-target-selector.ts +0 -545
- package/src/service/sleep-cycle.ts +0 -157
- package/src/service/startup-reconciler.ts +0 -112
- package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
- package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
- package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
- package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
- package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
- package/src/tools/write-pain-flag.ts +0 -215
- package/templates/langs/en/skills/plan-script/SKILL.md +0 -32
- package/templates/langs/zh/skills/plan-script/SKILL.md +0 -32
- package/tests/commands/nocturnal-review.test.ts +0 -448
- package/tests/commands/nocturnal-train.test.ts +0 -97
- package/tests/commands/pd-reflect.test.ts +0 -49
- package/tests/core/adaptive-thresholds.test.ts +0 -261
- package/tests/core/nocturnal-arbiter.test.ts +0 -559
- package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
- package/tests/core/nocturnal-artificer.test.ts +0 -241
- package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
- package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
- package/tests/core/nocturnal-compliance.test.ts +0 -646
- package/tests/core/nocturnal-dataset.test.ts +0 -892
- package/tests/core/nocturnal-e2e.test.ts +0 -234
- package/tests/core/nocturnal-executability.test.ts +0 -357
- package/tests/core/nocturnal-export.test.ts +0 -517
- package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
- package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
- package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
- package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
- package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
- package/tests/core/nocturnal-trinity.test.ts +0 -2053
- package/tests/core/pain-auto-repair.test.ts +0 -96
- package/tests/core/pain-integration.test.ts +0 -510
- package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
- package/tests/http/principles-console-route.test.ts +0 -162
- package/tests/integration/chaos-resilience.test.ts +0 -348
- package/tests/integration/empathy-workflow-integration.test.ts +0 -626
- package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
- package/tests/service/control-ui-query-service.test.ts +0 -121
- package/tests/service/cooldown-strategy.test.ts +0 -164
- package/tests/service/data-endpoints-regression.test.ts +0 -834
- package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
- package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
- package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
- package/tests/service/nocturnal-runtime.test.ts +0 -473
- package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
- package/tests/service/nocturnal-target-selector.test.ts +0 -615
- package/tests/service/startup-reconciler.test.ts +0 -148
- package/tests/tools/write-pain-flag.test.ts +0 -358
- package/ui/src/App.tsx +0 -45
- package/ui/src/api.ts +0 -220
- package/ui/src/charts.tsx +0 -955
- package/ui/src/components/ErrorState.tsx +0 -6
- package/ui/src/components/Loading.tsx +0 -13
- package/ui/src/components/ProtectedRoute.tsx +0 -12
- package/ui/src/components/Shell.tsx +0 -91
- package/ui/src/components/WorkspaceConfig.tsx +0 -178
- package/ui/src/components/index.ts +0 -5
- package/ui/src/context/auth.tsx +0 -80
- package/ui/src/context/theme.tsx +0 -66
- package/ui/src/hooks/useAutoRefresh.ts +0 -39
- package/ui/src/i18n/ui.ts +0 -473
- package/ui/src/main.tsx +0 -16
- package/ui/src/pages/EvolutionPage.tsx +0 -333
- package/ui/src/pages/FeedbackPage.tsx +0 -138
- package/ui/src/pages/GateMonitorPage.tsx +0 -136
- package/ui/src/pages/LoginPage.tsx +0 -89
- package/ui/src/pages/OverviewPage.tsx +0 -599
- package/ui/src/pages/SamplesPage.tsx +0 -174
- package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
- package/ui/src/styles.css +0 -2020
- package/ui/src/types.ts +0 -384
- package/ui/src/utils/format.ts +0 -15
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { SqliteConnection, SqliteActivationStateStore } from '@principles/core/runtime-v2';
|
|
6
|
+
import type { ActivationStatusRecord } from '@principles/core/runtime-v2';
|
|
7
|
+
import { PromptActivationReader, RUNTIME_V2_PRINCIPLE_BUDGET } from '../../src/core/runtime-v2-prompt-activation-reader.js';
|
|
8
|
+
|
|
9
|
+
const TEST_PRINCIPLE_TEXT = 'UNIQUE_RUNTIME_V2_TEST_PRINCIPLE_7x9k2';
|
|
10
|
+
|
|
11
|
+
let tempWorkspaceDir: string;
|
|
12
|
+
let tempStateDir: string;
|
|
13
|
+
let sqliteConn: SqliteConnection;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
vi.clearAllMocks();
|
|
17
|
+
process.env.PD_LEGACY_PROMPT_DIAGNOSTICIAN_ENABLED = 'true';
|
|
18
|
+
|
|
19
|
+
const baseTmp = os.tmpdir();
|
|
20
|
+
tempWorkspaceDir = fs.mkdtempSync(path.join(baseTmp, 'pd-prompt-v2-'));
|
|
21
|
+
tempStateDir = path.join(tempWorkspaceDir, '.principles');
|
|
22
|
+
fs.mkdirSync(tempStateDir, { recursive: true });
|
|
23
|
+
|
|
24
|
+
const pdDir = path.join(tempWorkspaceDir, '.pd');
|
|
25
|
+
fs.mkdirSync(pdDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
sqliteConn = new SqliteConnection(tempWorkspaceDir);
|
|
28
|
+
sqliteConn.getDb();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
try {
|
|
33
|
+
sqliteConn?.close();
|
|
34
|
+
} catch {
|
|
35
|
+
// best-effort
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
fs.rmSync(tempWorkspaceDir, { recursive: true, force: true });
|
|
39
|
+
} catch {
|
|
40
|
+
// best-effort on Windows
|
|
41
|
+
}
|
|
42
|
+
process.env.PD_LEGACY_PROMPT_DIAGNOSTICIAN_ENABLED = '';
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
vi.mock('../../src/core/diagnostician-task-store.js', async () => ({
|
|
46
|
+
getPendingDiagnosticianTasks: vi.fn().mockReturnValue([]),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
vi.mock('../../src/core/event-log.js', () => ({
|
|
50
|
+
EventLogService: {
|
|
51
|
+
get: vi.fn().mockReturnValue({
|
|
52
|
+
recordHeartbeatDiagnosis: vi.fn(),
|
|
53
|
+
recordRuntimeV2ActivationsInjected: vi.fn(),
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
vi.mock('../../src/core/workspace-context.js', async () => {
|
|
59
|
+
const { EventLogService } = await import('../../src/core/event-log.js');
|
|
60
|
+
const mockEventLog = EventLogService.get('/mock');
|
|
61
|
+
return {
|
|
62
|
+
WorkspaceContext: {
|
|
63
|
+
fromHookContext: vi.fn().mockImplementation(() => ({
|
|
64
|
+
workspaceDir: tempWorkspaceDir,
|
|
65
|
+
stateDir: tempStateDir,
|
|
66
|
+
resolve: (key: string) => path.join(tempWorkspaceDir, '.principles', key),
|
|
67
|
+
trajectory: { recordSession: vi.fn(), recordUserTurn: vi.fn() },
|
|
68
|
+
config: { get: vi.fn() },
|
|
69
|
+
eventLog: mockEventLog,
|
|
70
|
+
evolutionReducer: {
|
|
71
|
+
getActivePrinciples: vi.fn().mockReturnValue([]),
|
|
72
|
+
getProbationPrinciples: vi.fn().mockReturnValue([]),
|
|
73
|
+
},
|
|
74
|
+
})),
|
|
75
|
+
fromHookContextExplicit: vi.fn().mockImplementation(() => ({
|
|
76
|
+
workspaceDir: tempWorkspaceDir,
|
|
77
|
+
stateDir: tempStateDir,
|
|
78
|
+
resolve: (key: string) => path.join(tempWorkspaceDir, '.principles', key),
|
|
79
|
+
trajectory: { recordSession: vi.fn(), recordUserTurn: vi.fn() },
|
|
80
|
+
config: { get: vi.fn() },
|
|
81
|
+
eventLog: mockEventLog,
|
|
82
|
+
evolutionReducer: {
|
|
83
|
+
getActivePrinciples: vi.fn().mockReturnValue([]),
|
|
84
|
+
getProbationPrinciples: vi.fn().mockReturnValue([]),
|
|
85
|
+
},
|
|
86
|
+
})),
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
vi.mock('../../src/core/session-tracker.js', () => ({
|
|
92
|
+
getSession: vi.fn().mockReturnValue({ currentGfi: 20 }),
|
|
93
|
+
resetFriction: vi.fn(),
|
|
94
|
+
trackFriction: vi.fn(),
|
|
95
|
+
setInjectedProbationIds: vi.fn(),
|
|
96
|
+
clearInjectedProbationIds: vi.fn(),
|
|
97
|
+
decayGfi: vi.fn(),
|
|
98
|
+
getGfiDecayElapsed: vi.fn().mockReturnValue(0),
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
vi.mock('../../src/core/path-resolver.js', () => ({
|
|
102
|
+
PathResolver: { getExtensionRoot: vi.fn().mockReturnValue('/fake/extension') },
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
vi.mock('../../src/core/principle-injection.js', () => ({
|
|
106
|
+
selectPrinciplesForInjection: vi.fn().mockReturnValue({
|
|
107
|
+
selected: [],
|
|
108
|
+
wasTruncated: false,
|
|
109
|
+
breakdown: { p0: 0, p1: 0, p2: 0 },
|
|
110
|
+
totalChars: 0,
|
|
111
|
+
}),
|
|
112
|
+
DEFAULT_PRINCIPLE_BUDGET: 3000,
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
vi.mock('../../src/core/empathy-keyword-matcher.js', () => ({
|
|
116
|
+
matchEmpathyKeywords: vi.fn().mockReturnValue({ score: 0, matched: null, severity: 'none', matchedTerms: [] }),
|
|
117
|
+
loadKeywordStore: vi.fn().mockReturnValue({ terms: {}, stats: { totalHits: 0 } }),
|
|
118
|
+
saveKeywordStore: vi.fn(),
|
|
119
|
+
shouldTriggerOptimization: vi.fn().mockReturnValue(false),
|
|
120
|
+
getKeywordStoreSummary: vi.fn().mockReturnValue({ totalTerms: 0, highFalsePositiveTerms: [] }),
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
vi.mock('../../src/core/empathy-types.js', () => ({
|
|
124
|
+
severityToPenalty: vi.fn().mockReturnValue(5),
|
|
125
|
+
DEFAULT_EMPATHY_KEYWORD_CONFIG: {},
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
vi.mock('../../src/core/correction-cue-learner.js', () => ({
|
|
129
|
+
CorrectionCueLearner: {
|
|
130
|
+
get: vi.fn().mockReturnValue({
|
|
131
|
+
match: vi.fn().mockReturnValue({ matched: null, matchedTerms: [], confidence: 0 }),
|
|
132
|
+
recordHits: vi.fn(),
|
|
133
|
+
recordTruePositive: vi.fn(),
|
|
134
|
+
flush: vi.fn(),
|
|
135
|
+
}),
|
|
136
|
+
},
|
|
137
|
+
}));
|
|
138
|
+
|
|
139
|
+
vi.mock('../../src/core/focus-history.js', () => ({
|
|
140
|
+
extractSummary: vi.fn().mockReturnValue(''),
|
|
141
|
+
getHistoryVersions: vi.fn().mockResolvedValue([]),
|
|
142
|
+
parseWorkingMemorySection: vi.fn().mockReturnValue(null),
|
|
143
|
+
workingMemoryToInjection: vi.fn().mockReturnValue(''),
|
|
144
|
+
autoCompressFocus: vi.fn().mockReturnValue({ compressed: false, reason: 'not_needed' }),
|
|
145
|
+
safeReadCurrentFocus: vi.fn().mockReturnValue({ content: '', recovered: false, validationErrors: [] }),
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
vi.mock('../../src/service/subagent-workflow/index.js', () => ({
|
|
149
|
+
EmpathyObserverWorkflowManager: vi.fn(),
|
|
150
|
+
empathyObserverWorkflowSpec: {},
|
|
151
|
+
isExpectedSubagentError: vi.fn().mockReturnValue(false),
|
|
152
|
+
}));
|
|
153
|
+
|
|
154
|
+
vi.mock('../../src/utils/subagent-probe.js', () => ({
|
|
155
|
+
isSubagentRuntimeAvailable: vi.fn().mockReturnValue(false),
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
vi.mock('../../src/core/local-worker-routing.js', () => ({
|
|
159
|
+
classifyTask: vi.fn().mockReturnValue({
|
|
160
|
+
decision: 'stay_main',
|
|
161
|
+
classification: 'unknown',
|
|
162
|
+
reason: 'mocked',
|
|
163
|
+
blockers: [],
|
|
164
|
+
}),
|
|
165
|
+
}));
|
|
166
|
+
|
|
167
|
+
function makeMinimalEvent(overrides: {
|
|
168
|
+
trigger?: string;
|
|
169
|
+
sessionId?: string;
|
|
170
|
+
} = {}) {
|
|
171
|
+
const { trigger = 'user', sessionId = 'test-session-v2' } = overrides;
|
|
172
|
+
return {
|
|
173
|
+
prompt: 'hello world',
|
|
174
|
+
messages: [],
|
|
175
|
+
trigger,
|
|
176
|
+
sessionId,
|
|
177
|
+
} as unknown as Parameters<typeof import('../../src/hooks/prompt.js').handleBeforePromptBuild>[0];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function makeCtx(overrides: {
|
|
181
|
+
workspaceDir?: string;
|
|
182
|
+
trigger?: string;
|
|
183
|
+
sessionId?: string;
|
|
184
|
+
} = {}) {
|
|
185
|
+
const {
|
|
186
|
+
workspaceDir = tempWorkspaceDir,
|
|
187
|
+
trigger = 'user',
|
|
188
|
+
sessionId = 'test-session-v2',
|
|
189
|
+
} = overrides;
|
|
190
|
+
return {
|
|
191
|
+
workspaceDir,
|
|
192
|
+
trigger,
|
|
193
|
+
sessionId,
|
|
194
|
+
api: {
|
|
195
|
+
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
196
|
+
runtime: {},
|
|
197
|
+
config: {},
|
|
198
|
+
},
|
|
199
|
+
} as unknown as Parameters<typeof import('../../src/hooks/prompt.js').handleBeforePromptBuild>[1];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function insertPromptActivation(overrides: {
|
|
203
|
+
artifactId: string;
|
|
204
|
+
principleId: string;
|
|
205
|
+
channel?: string;
|
|
206
|
+
action?: string;
|
|
207
|
+
targetRef?: string;
|
|
208
|
+
}) {
|
|
209
|
+
const {
|
|
210
|
+
artifactId,
|
|
211
|
+
principleId,
|
|
212
|
+
channel = 'prompt',
|
|
213
|
+
action = 'prompt_activate',
|
|
214
|
+
targetRef = `ledger://${principleId}`,
|
|
215
|
+
} = overrides;
|
|
216
|
+
|
|
217
|
+
const activationStore = new SqliteActivationStateStore(sqliteConn);
|
|
218
|
+
const now = new Date().toISOString();
|
|
219
|
+
const idempotencyKey = `${artifactId}::${channel}`;
|
|
220
|
+
|
|
221
|
+
await activationStore.recordActivation({
|
|
222
|
+
activationId: `act_prompt_${principleId}`,
|
|
223
|
+
idempotencyKey,
|
|
224
|
+
artifactId,
|
|
225
|
+
channel: channel as ActivationStatusRecord['channel'],
|
|
226
|
+
action,
|
|
227
|
+
targetRef,
|
|
228
|
+
activatedAt: now,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function insertValidatedPrincipleArtifact(overrides: {
|
|
233
|
+
artifactId: string;
|
|
234
|
+
principleId: string;
|
|
235
|
+
text?: string;
|
|
236
|
+
validationStatus?: string;
|
|
237
|
+
contentJson?: string;
|
|
238
|
+
}) {
|
|
239
|
+
const {
|
|
240
|
+
artifactId,
|
|
241
|
+
principleId,
|
|
242
|
+
text = TEST_PRINCIPLE_TEXT,
|
|
243
|
+
validationStatus = 'validated',
|
|
244
|
+
contentJson,
|
|
245
|
+
} = overrides;
|
|
246
|
+
|
|
247
|
+
const db = sqliteConn.getDb();
|
|
248
|
+
const now = new Date().toISOString();
|
|
249
|
+
|
|
250
|
+
db.prepare(`
|
|
251
|
+
INSERT INTO pi_artifacts (artifact_id, artifact_kind, source_task_id, source_principle_id, source_rule_id, lineage_artifact_ids, validation_status, content_json, created_at, updated_at)
|
|
252
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
253
|
+
`).run(
|
|
254
|
+
artifactId,
|
|
255
|
+
'principle',
|
|
256
|
+
`task_${principleId}`,
|
|
257
|
+
principleId,
|
|
258
|
+
null,
|
|
259
|
+
'[]',
|
|
260
|
+
validationStatus,
|
|
261
|
+
contentJson ?? JSON.stringify({ principleId, text }),
|
|
262
|
+
now,
|
|
263
|
+
now,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
describe('Runtime V2 prompt activation injection', () => {
|
|
268
|
+
it('owner-approved activated principle changes future prompt', async () => {
|
|
269
|
+
const artifactId = 'art-v2-prompt-001';
|
|
270
|
+
const principleId = 'princ-v2-001';
|
|
271
|
+
|
|
272
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
273
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
274
|
+
|
|
275
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
276
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
277
|
+
|
|
278
|
+
// Runtime V2 principles are now in prependSystemContext (highest attention)
|
|
279
|
+
expect(result?.prependSystemContext).toContain(TEST_PRINCIPLE_TEXT);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('unactivated principle is not injected', async () => {
|
|
283
|
+
const artifactId = 'art-v2-no-act-002';
|
|
284
|
+
const principleId = 'princ-v2-no-act-002';
|
|
285
|
+
|
|
286
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
287
|
+
|
|
288
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
289
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
290
|
+
|
|
291
|
+
expect(result?.appendSystemContext).not.toContain(TEST_PRINCIPLE_TEXT);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('non-prompt activation is not injected', async () => {
|
|
295
|
+
const artifactId = 'art-v2-defer-003';
|
|
296
|
+
const principleId = 'princ-v2-defer-003';
|
|
297
|
+
|
|
298
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
299
|
+
await insertPromptActivation({
|
|
300
|
+
artifactId,
|
|
301
|
+
principleId,
|
|
302
|
+
channel: 'defer_archive',
|
|
303
|
+
action: 'defer_archive',
|
|
304
|
+
targetRef: `ledger://${principleId}#archived`,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
308
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
309
|
+
|
|
310
|
+
expect(result?.appendSystemContext).not.toContain(TEST_PRINCIPLE_TEXT);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('prompt feature flag check is present — core flag cannot be disabled by config', async () => {
|
|
314
|
+
const artifactId = 'art-v2-flag-004';
|
|
315
|
+
const principleId = 'princ-v2-flag-004';
|
|
316
|
+
|
|
317
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
318
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
319
|
+
|
|
320
|
+
const pdDir = path.join(tempWorkspaceDir, '.pd');
|
|
321
|
+
if (!fs.existsSync(pdDir)) {
|
|
322
|
+
fs.mkdirSync(pdDir, { recursive: true });
|
|
323
|
+
}
|
|
324
|
+
fs.writeFileSync(
|
|
325
|
+
path.join(pdDir, 'feature-flags.yaml'),
|
|
326
|
+
'prompt:\n enabled: false\n',
|
|
327
|
+
'utf8',
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const infoSpy = vi.fn();
|
|
331
|
+
const ctx = {
|
|
332
|
+
workspaceDir: tempWorkspaceDir,
|
|
333
|
+
trigger: 'user',
|
|
334
|
+
sessionId: 'test-session-v2',
|
|
335
|
+
api: {
|
|
336
|
+
logger: { info: infoSpy, warn: vi.fn(), error: vi.fn() },
|
|
337
|
+
runtime: {},
|
|
338
|
+
config: {},
|
|
339
|
+
},
|
|
340
|
+
} as unknown as Parameters<typeof import('../../src/hooks/prompt.js').handleBeforePromptBuild>[1];
|
|
341
|
+
|
|
342
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
343
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), ctx);
|
|
344
|
+
|
|
345
|
+
expect(result?.prependSystemContext).toContain(TEST_PRINCIPLE_TEXT);
|
|
346
|
+
|
|
347
|
+
const infoCalls = infoSpy.mock.calls.map((c: unknown[]) => String(c[0]));
|
|
348
|
+
const hasCoreFlagWarning = infoCalls.some(
|
|
349
|
+
(c: string) => c.includes('core flag cannot be disabled') || c.includes('warnings'),
|
|
350
|
+
);
|
|
351
|
+
expect(hasCoreFlagWarning || result?.prependSystemContext).toBeTruthy();
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('missing activated artifact fails loud without crashing', async () => {
|
|
355
|
+
const artifactId = 'art-v2-missing-005';
|
|
356
|
+
const principleId = 'princ-v2-missing-005';
|
|
357
|
+
|
|
358
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
359
|
+
|
|
360
|
+
const warnSpy = vi.fn();
|
|
361
|
+
const ctx = {
|
|
362
|
+
workspaceDir: tempWorkspaceDir,
|
|
363
|
+
trigger: 'user',
|
|
364
|
+
sessionId: 'test-session-v2',
|
|
365
|
+
api: {
|
|
366
|
+
logger: { info: vi.fn(), warn: warnSpy, error: vi.fn() },
|
|
367
|
+
runtime: {},
|
|
368
|
+
config: {},
|
|
369
|
+
},
|
|
370
|
+
} as unknown as Parameters<typeof import('../../src/hooks/prompt.js').handleBeforePromptBuild>[1];
|
|
371
|
+
|
|
372
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
373
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), ctx);
|
|
374
|
+
|
|
375
|
+
expect(result).toBeDefined();
|
|
376
|
+
expect(result?.appendSystemContext).not.toContain(TEST_PRINCIPLE_TEXT);
|
|
377
|
+
const warnCalls = warnSpy.mock.calls.map((c: unknown[]) => String(c[0]));
|
|
378
|
+
const hasActivationWarning = warnCalls.some((c: string) => c.includes('artifact_not_found') || c.includes('artifact_query_unexpected') || c.includes('activation'));
|
|
379
|
+
expect(hasActivationWarning).toBe(true);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('malformed content_json in artifact fails loud without crashing', async () => {
|
|
383
|
+
const artifactId = 'art-v2-malformed-006';
|
|
384
|
+
const principleId = 'princ-v2-malformed-006';
|
|
385
|
+
|
|
386
|
+
insertValidatedPrincipleArtifact({
|
|
387
|
+
artifactId,
|
|
388
|
+
principleId,
|
|
389
|
+
contentJson: '{not valid json<<<',
|
|
390
|
+
});
|
|
391
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
392
|
+
|
|
393
|
+
const warnSpy = vi.fn();
|
|
394
|
+
const ctx = {
|
|
395
|
+
workspaceDir: tempWorkspaceDir,
|
|
396
|
+
trigger: 'user',
|
|
397
|
+
sessionId: 'test-session-v2',
|
|
398
|
+
api: {
|
|
399
|
+
logger: { info: vi.fn(), warn: warnSpy, error: vi.fn() },
|
|
400
|
+
runtime: {},
|
|
401
|
+
config: {},
|
|
402
|
+
},
|
|
403
|
+
} as unknown as Parameters<typeof import('../../src/hooks/prompt.js').handleBeforePromptBuild>[1];
|
|
404
|
+
|
|
405
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
406
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), ctx);
|
|
407
|
+
|
|
408
|
+
expect(result).toBeDefined();
|
|
409
|
+
expect(result?.appendSystemContext).not.toContain(TEST_PRINCIPLE_TEXT);
|
|
410
|
+
const warnCalls = warnSpy.mock.calls.map((c: unknown[]) => String(c[0]));
|
|
411
|
+
const hasParseWarning = warnCalls.some((c: string) => c.includes('json_parse_error') || c.includes('activation'));
|
|
412
|
+
expect(hasParseWarning).toBe(true);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('no legacy promotion is required for Runtime V2 injection', async () => {
|
|
416
|
+
const artifactId = 'art-v2-no-legacy-007';
|
|
417
|
+
const principleId = 'princ-v2-no-legacy-007';
|
|
418
|
+
|
|
419
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
420
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
421
|
+
|
|
422
|
+
const { WorkspaceContext } = await import('../../src/core/workspace-context.js');
|
|
423
|
+
const mockWctx = (WorkspaceContext.fromHookContext as ReturnType<typeof vi.fn>).mock.results[0]?.value;
|
|
424
|
+
const promoteSpy = vi.fn();
|
|
425
|
+
|
|
426
|
+
if (mockWctx?.evolutionReducer) {
|
|
427
|
+
mockWctx.evolutionReducer.promote = promoteSpy;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
431
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
432
|
+
|
|
433
|
+
expect(result?.prependSystemContext).toContain(TEST_PRINCIPLE_TEXT);
|
|
434
|
+
expect(promoteSpy).not.toHaveBeenCalled();
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
describe('Runtime V2 prompt activation — additional guard tests', () => {
|
|
439
|
+
it('rejected/pending artifact is not injected', async () => {
|
|
440
|
+
const artifactId = 'art-v2-rejected-101';
|
|
441
|
+
const principleId = 'princ-v2-rejected-101';
|
|
442
|
+
|
|
443
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId, validationStatus: 'rejected' });
|
|
444
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
445
|
+
|
|
446
|
+
const reader = new PromptActivationReader(tempWorkspaceDir);
|
|
447
|
+
const result = await reader.readActivatedPrinciples();
|
|
448
|
+
|
|
449
|
+
expect(result.principles).toHaveLength(0);
|
|
450
|
+
expect(result.warnings.some((w) => w.includes('artifact_not_validated'))).toBe(true);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('prompt channel with wrong action is not injected', async () => {
|
|
454
|
+
const artifactId = 'art-v2-wrong-action-102';
|
|
455
|
+
const principleId = 'princ-v2-wrong-action-102';
|
|
456
|
+
|
|
457
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
458
|
+
await insertPromptActivation({
|
|
459
|
+
artifactId,
|
|
460
|
+
principleId,
|
|
461
|
+
channel: 'prompt',
|
|
462
|
+
action: 'prompt_deactivate',
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const reader = new PromptActivationReader(tempWorkspaceDir);
|
|
466
|
+
const result = await reader.readActivatedPrinciples();
|
|
467
|
+
|
|
468
|
+
expect(result.principles).toHaveLength(0);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it('multiple or oversized Runtime V2 principles are trimmed to budget', async () => {
|
|
472
|
+
const longText = 'A'.repeat(800);
|
|
473
|
+
for (let i = 0; i < 5; i++) {
|
|
474
|
+
const artifactId = `art-v2-budget-${i}`;
|
|
475
|
+
const principleId = `princ-v2-budget-${i}`;
|
|
476
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId, text: longText });
|
|
477
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const reader = new PromptActivationReader(tempWorkspaceDir);
|
|
481
|
+
const result = await reader.readActivatedPrinciples();
|
|
482
|
+
|
|
483
|
+
expect(result.principles.length).toBe(5);
|
|
484
|
+
|
|
485
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
486
|
+
const hookResult = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
487
|
+
|
|
488
|
+
// Runtime V2 principles are now in prependSystemContext
|
|
489
|
+
const injected = hookResult?.prependSystemContext ?? '';
|
|
490
|
+
const markerCount = (injected.match(/princ-v2-budget-/g) || []).length;
|
|
491
|
+
expect(markerCount).toBeLessThan(5);
|
|
492
|
+
expect(markerCount).toBeGreaterThan(0);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('malformed DB/config input fails loud with warning', async () => {
|
|
496
|
+
const pdDir = path.join(tempWorkspaceDir, '.pd');
|
|
497
|
+
fs.writeFileSync(
|
|
498
|
+
path.join(pdDir, 'feature-flags.yaml'),
|
|
499
|
+
'__proto__:\n enabled: true\nprompt:\n enabled: true\nconstructor:\n enabled: false\n',
|
|
500
|
+
'utf8',
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
const warnSpy = vi.fn();
|
|
504
|
+
const reader = new PromptActivationReader(tempWorkspaceDir, {
|
|
505
|
+
logger: { warn: warnSpy, info: vi.fn(), error: vi.fn() },
|
|
506
|
+
});
|
|
507
|
+
const result = await reader.readActivatedPrinciples();
|
|
508
|
+
|
|
509
|
+
const warnCalls = warnSpy.mock.calls.map((c: unknown[]) => String(c[0]));
|
|
510
|
+
const hasDangerousKeyWarning = warnCalls.some(
|
|
511
|
+
(c: string) => c.includes('dangerous key') || c.includes('__proto__') || c.includes('constructor'),
|
|
512
|
+
);
|
|
513
|
+
expect(hasDangerousKeyWarning).toBe(true);
|
|
514
|
+
expect(result.principles).toEqual([]);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('reader uses normalized workspaceDir correctly', async () => {
|
|
518
|
+
const artifactId = 'art-v2-norm-105';
|
|
519
|
+
const principleId = 'princ-v2-norm-105';
|
|
520
|
+
|
|
521
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
522
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
523
|
+
|
|
524
|
+
const reader = new PromptActivationReader(tempWorkspaceDir);
|
|
525
|
+
const result = await reader.readActivatedPrinciples();
|
|
526
|
+
|
|
527
|
+
expect(result.principles).toHaveLength(1);
|
|
528
|
+
expect(result.principles[0].principleId).toBe(principleId);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('pending validation status artifact is not injected', async () => {
|
|
532
|
+
const artifactId = 'art-v2-pending-106';
|
|
533
|
+
const principleId = 'princ-v2-pending-106';
|
|
534
|
+
|
|
535
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId, validationStatus: 'pending' });
|
|
536
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
537
|
+
|
|
538
|
+
const reader = new PromptActivationReader(tempWorkspaceDir);
|
|
539
|
+
const result = await reader.readActivatedPrinciples();
|
|
540
|
+
|
|
541
|
+
expect(result.principles).toHaveLength(0);
|
|
542
|
+
expect(result.warnings.some((w) => w.includes('artifact_not_validated'))).toBe(true);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('malformed activation row with empty artifact_id is rejected', async () => {
|
|
546
|
+
const db = sqliteConn.getDb();
|
|
547
|
+
const now = new Date().toISOString();
|
|
548
|
+
db.prepare(`
|
|
549
|
+
INSERT INTO activations (activation_id, idempotency_key, artifact_id, channel, action, target_ref, activated_at)
|
|
550
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
551
|
+
`).run('', 'idem-empty-artifact', '', 'prompt', 'prompt_activate', '', now);
|
|
552
|
+
|
|
553
|
+
const store = new SqliteActivationStateStore(sqliteConn);
|
|
554
|
+
const activations = await store.listPromptActivations();
|
|
555
|
+
const emptyArtifact = activations.find((a) => a.idempotencyKey === 'idem-empty-artifact');
|
|
556
|
+
expect(emptyArtifact).toBeUndefined();
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('malformed activation row with empty activation_id is rejected', async () => {
|
|
560
|
+
const db = sqliteConn.getDb();
|
|
561
|
+
const now = new Date().toISOString();
|
|
562
|
+
db.prepare(`
|
|
563
|
+
INSERT INTO activations (activation_id, idempotency_key, artifact_id, channel, action, target_ref, activated_at)
|
|
564
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
565
|
+
`).run('', 'idem-empty-actid', 'some-artifact', 'prompt', 'prompt_activate', '', now);
|
|
566
|
+
|
|
567
|
+
const store = new SqliteActivationStateStore(sqliteConn);
|
|
568
|
+
const activations = await store.listPromptActivations();
|
|
569
|
+
const emptyActId = activations.find((a) => a.idempotencyKey === 'idem-empty-actid');
|
|
570
|
+
expect(emptyActId).toBeUndefined();
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it('malformed activation row with empty action is rejected', async () => {
|
|
574
|
+
const db = sqliteConn.getDb();
|
|
575
|
+
const now = new Date().toISOString();
|
|
576
|
+
db.prepare(`
|
|
577
|
+
INSERT INTO activations (activation_id, idempotency_key, artifact_id, channel, action, target_ref, activated_at)
|
|
578
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
579
|
+
`).run('act-malformed-action', 'idem-empty-action', 'some-artifact', 'prompt', '', '', now);
|
|
580
|
+
|
|
581
|
+
const store = new SqliteActivationStateStore(sqliteConn);
|
|
582
|
+
const activations = await store.listPromptActivations();
|
|
583
|
+
const emptyAction = activations.find((a) => a.idempotencyKey === 'idem-empty-action');
|
|
584
|
+
expect(emptyAction).toBeUndefined();
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it('malformed activation row with empty activated_at is rejected', async () => {
|
|
588
|
+
const db = sqliteConn.getDb();
|
|
589
|
+
db.prepare(`
|
|
590
|
+
INSERT INTO activations (activation_id, idempotency_key, artifact_id, channel, action, target_ref, activated_at)
|
|
591
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
592
|
+
`).run('act-malformed-at', 'idem-empty-at', 'some-artifact', 'prompt', 'prompt_activate', '', '');
|
|
593
|
+
|
|
594
|
+
const store = new SqliteActivationStateStore(sqliteConn);
|
|
595
|
+
const activations = await store.listPromptActivations();
|
|
596
|
+
const emptyAt = activations.find((a) => a.idempotencyKey === 'idem-empty-at');
|
|
597
|
+
expect(emptyAt).toBeUndefined();
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it('malformed activation row with invalid channel is rejected', async () => {
|
|
601
|
+
const db = sqliteConn.getDb();
|
|
602
|
+
const now = new Date().toISOString();
|
|
603
|
+
db.prepare(`
|
|
604
|
+
INSERT INTO activations (activation_id, idempotency_key, artifact_id, channel, action, target_ref, activated_at)
|
|
605
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
606
|
+
`).run('act-bad-channel', 'idem-bad-channel', 'some-artifact', 'invalid_channel', 'prompt_activate', '', now);
|
|
607
|
+
|
|
608
|
+
const store = new SqliteActivationStateStore(sqliteConn);
|
|
609
|
+
const activations = await store.listPromptActivations();
|
|
610
|
+
const badChannel = activations.find((a) => a.idempotencyKey === 'idem-bad-channel');
|
|
611
|
+
expect(badChannel).toBeUndefined();
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
describe('Runtime V2 prompt activation observability events', () => {
|
|
616
|
+
it('emits injected event with principleIds when valid activations exist', async () => {
|
|
617
|
+
const artifactId = 'art-v2-obs-001';
|
|
618
|
+
const principleId = 'princ-v2-obs-001';
|
|
619
|
+
|
|
620
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
621
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
622
|
+
|
|
623
|
+
const { EventLogService } = await import('../../src/core/event-log.js');
|
|
624
|
+
const mockEventLog = (EventLogService.get as ReturnType<typeof vi.fn>)();
|
|
625
|
+
const spy = mockEventLog.recordRuntimeV2ActivationsInjected as ReturnType<typeof vi.fn>;
|
|
626
|
+
|
|
627
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
628
|
+
await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
629
|
+
|
|
630
|
+
expect(spy).toHaveBeenCalled();
|
|
631
|
+
const payload = spy.mock.calls[0][0];
|
|
632
|
+
expect(payload.principleIds).toContain(principleId);
|
|
633
|
+
expect(payload.artifactIds).toContain(artifactId);
|
|
634
|
+
expect(payload.activationIds).toContain(`act_prompt_${principleId}`);
|
|
635
|
+
expect(payload.injectedCount).toBe(1);
|
|
636
|
+
expect(payload.injectedCharCount).toBeGreaterThan(0);
|
|
637
|
+
expect(payload.budget).toBe(RUNTIME_V2_PRINCIPLE_BUDGET);
|
|
638
|
+
expect(payload.sessionId).toBe('test-session-v2');
|
|
639
|
+
expect(payload.workspaceDir).toBe(tempWorkspaceDir);
|
|
640
|
+
expect(payload.skippedWarnings).toEqual([]);
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
it('emits skipReason when no validated activations exist', async () => {
|
|
644
|
+
const { EventLogService } = await import('../../src/core/event-log.js');
|
|
645
|
+
const mockEventLog = (EventLogService.get as ReturnType<typeof vi.fn>)();
|
|
646
|
+
const spy = mockEventLog.recordRuntimeV2ActivationsInjected as ReturnType<typeof vi.fn>;
|
|
647
|
+
spy.mockClear();
|
|
648
|
+
|
|
649
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
650
|
+
await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
651
|
+
|
|
652
|
+
expect(spy).toHaveBeenCalled();
|
|
653
|
+
const payload = spy.mock.calls[0][0];
|
|
654
|
+
expect(payload.injectedCount).toBe(0);
|
|
655
|
+
expect(payload.principleIds).toEqual([]);
|
|
656
|
+
expect(payload.skipReason).toBe('no_validated_activations');
|
|
657
|
+
expect(payload.nextAction).toContain('activations table');
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it('confirm-first marker appears in principleIds evidence', async () => {
|
|
661
|
+
const artifactId = 'art-mvp-acceptance-001';
|
|
662
|
+
const principleId = 'princ-mvp-acceptance-confirm-first';
|
|
663
|
+
const text = 'Before starting any coding task, the agent must first confirm requirements and present a plan for owner approval.';
|
|
664
|
+
|
|
665
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId, text });
|
|
666
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
667
|
+
|
|
668
|
+
const { EventLogService } = await import('../../src/core/event-log.js');
|
|
669
|
+
const mockEventLog = (EventLogService.get as ReturnType<typeof vi.fn>)();
|
|
670
|
+
const spy = mockEventLog.recordRuntimeV2ActivationsInjected as ReturnType<typeof vi.fn>;
|
|
671
|
+
spy.mockClear();
|
|
672
|
+
|
|
673
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
674
|
+
await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
675
|
+
|
|
676
|
+
expect(spy).toHaveBeenCalled();
|
|
677
|
+
const payload = spy.mock.calls[0][0];
|
|
678
|
+
expect(payload.principleIds).toContain('princ-mvp-acceptance-confirm-first');
|
|
679
|
+
expect(payload.injectedCount).toBeGreaterThanOrEqual(1);
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it('warnings are preserved in skippedWarnings', async () => {
|
|
683
|
+
const artifactId = 'art-v2-obs-warn-003';
|
|
684
|
+
const principleId = 'princ-v2-obs-warn-003';
|
|
685
|
+
|
|
686
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId, validationStatus: 'rejected' });
|
|
687
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
688
|
+
|
|
689
|
+
const { EventLogService } = await import('../../src/core/event-log.js');
|
|
690
|
+
const mockEventLog = (EventLogService.get as ReturnType<typeof vi.fn>)();
|
|
691
|
+
const spy = mockEventLog.recordRuntimeV2ActivationsInjected as ReturnType<typeof vi.fn>;
|
|
692
|
+
spy.mockClear();
|
|
693
|
+
|
|
694
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
695
|
+
await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
696
|
+
|
|
697
|
+
expect(spy).toHaveBeenCalled();
|
|
698
|
+
const payload = spy.mock.calls[0][0];
|
|
699
|
+
expect(payload.skippedWarnings.length).toBeGreaterThan(0);
|
|
700
|
+
expect(payload.skippedWarnings.some((w: string) => w.includes('artifact_not_validated'))).toBe(true);
|
|
701
|
+
expect(payload.injectedCount).toBe(0);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it('no raw secrets or full giant prompt in telemetry payload', async () => {
|
|
705
|
+
const artifactId = 'art-v2-obs-safe-004';
|
|
706
|
+
const principleId = 'princ-v2-obs-safe-004';
|
|
707
|
+
const secretText = 'sk-proj-SECRET_KEY_12345_should_not_appear';
|
|
708
|
+
|
|
709
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId, text: secretText });
|
|
710
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
711
|
+
|
|
712
|
+
const { EventLogService } = await import('../../src/core/event-log.js');
|
|
713
|
+
const mockEventLog = (EventLogService.get as ReturnType<typeof vi.fn>)();
|
|
714
|
+
const spy = mockEventLog.recordRuntimeV2ActivationsInjected as ReturnType<typeof vi.fn>;
|
|
715
|
+
spy.mockClear();
|
|
716
|
+
|
|
717
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
718
|
+
await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
719
|
+
|
|
720
|
+
expect(spy).toHaveBeenCalled();
|
|
721
|
+
const payload = spy.mock.calls[0][0];
|
|
722
|
+
const serialized = JSON.stringify(payload);
|
|
723
|
+
// Should not contain the full principle text — only IDs and char count
|
|
724
|
+
expect(serialized).not.toContain(secretText);
|
|
725
|
+
// Should not contain the full prompt
|
|
726
|
+
expect(serialized).not.toContain('hello world');
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
describe('Runtime V2 owner-approved behavior directives section', () => {
|
|
731
|
+
beforeEach(() => {
|
|
732
|
+
vi.resetModules();
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it('renders owner-approved directives in prependSystemContext when activations exist', async () => {
|
|
736
|
+
const artifactId = 'art-v2-directive-201';
|
|
737
|
+
const principleId = 'princ-v2-directive-201';
|
|
738
|
+
|
|
739
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
740
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
741
|
+
|
|
742
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
743
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
744
|
+
|
|
745
|
+
expect(result?.prependSystemContext).toContain('OWNER-APPROVED BEHAVIOR DIRECTIVES');
|
|
746
|
+
expect(result?.prependSystemContext).toContain('<directive');
|
|
747
|
+
expect(result?.prependSystemContext).toContain('</directive>');
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
it('prependSystemContext contains MANDATORY framing', async () => {
|
|
751
|
+
const artifactId = 'art-v2-directive-202';
|
|
752
|
+
const principleId = 'princ-v2-directive-202';
|
|
753
|
+
|
|
754
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
755
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
756
|
+
|
|
757
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
758
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
759
|
+
|
|
760
|
+
const ctx = result?.prependSystemContext ?? '';
|
|
761
|
+
expect(ctx).toContain('MANDATORY');
|
|
762
|
+
expect(ctx).toContain('Owner-approved');
|
|
763
|
+
expect(ctx).toContain('active behavior constraint');
|
|
764
|
+
expect(ctx).toContain('Do not treat this as background context');
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
it('prependSystemContext includes safety boundary disclaimer', async () => {
|
|
768
|
+
const artifactId = 'art-v2-directive-203';
|
|
769
|
+
const principleId = 'princ-v2-directive-203';
|
|
770
|
+
|
|
771
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
772
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
773
|
+
|
|
774
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
775
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
776
|
+
|
|
777
|
+
const ctx = result?.prependSystemContext ?? '';
|
|
778
|
+
expect(ctx).toContain('do not override safety');
|
|
779
|
+
expect(ctx).toContain('do not override safety, security, or core system policy');
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
it('directives appear in prependSystemContext (before gateway system prompt)', async () => {
|
|
783
|
+
const artifactId = 'art-v2-directive-204';
|
|
784
|
+
const principleId = 'princ-v2-directive-204';
|
|
785
|
+
|
|
786
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
787
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
788
|
+
|
|
789
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
790
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
791
|
+
|
|
792
|
+
// Directives should be in prependSystemContext, NOT in appendSystemContext
|
|
793
|
+
const prependCtx = result?.prependSystemContext ?? '';
|
|
794
|
+
const appendCtx = result?.appendSystemContext ?? '';
|
|
795
|
+
const directiveMarker = 'OWNER-APPROVED BEHAVIOR DIRECTIVES';
|
|
796
|
+
expect(prependCtx).toContain(directiveMarker);
|
|
797
|
+
// Should NOT be duplicated in appendSystemContext
|
|
798
|
+
expect(appendCtx).not.toContain(directiveMarker);
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it('directives appear after AGENT IDENTITY in prependSystemContext', async () => {
|
|
802
|
+
const artifactId = 'art-v2-directive-205';
|
|
803
|
+
const principleId = 'princ-v2-directive-205';
|
|
804
|
+
|
|
805
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
806
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
807
|
+
|
|
808
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
809
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
810
|
+
|
|
811
|
+
const ctx = result?.prependSystemContext ?? '';
|
|
812
|
+
const identityIdx = ctx.indexOf('AGENT IDENTITY');
|
|
813
|
+
const directiveIdx = ctx.indexOf('OWNER-APPROVED BEHAVIOR DIRECTIVES');
|
|
814
|
+
expect(identityIdx).toBeGreaterThanOrEqual(0);
|
|
815
|
+
expect(directiveIdx).toBeGreaterThan(identityIdx);
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
it('confirm-first principle rendered as directive with id attribute', async () => {
|
|
819
|
+
const artifactId = 'art-mvp-acceptance-001';
|
|
820
|
+
const principleId = 'princ-mvp-acceptance-confirm-first';
|
|
821
|
+
const text = 'Before starting any coding task, the agent must first confirm requirements and present a plan for owner approval.';
|
|
822
|
+
|
|
823
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId, text });
|
|
824
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
825
|
+
|
|
826
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
827
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
828
|
+
|
|
829
|
+
const ctx = result?.prependSystemContext ?? '';
|
|
830
|
+
expect(ctx).toContain(`<directive id="princ-mvp-acceptance-confirm-first" source="runtime_v2_activation">`);
|
|
831
|
+
expect(ctx).toContain('MANDATORY: Before starting any coding task');
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
it('no directive section when no activations exist', async () => {
|
|
835
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
836
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
837
|
+
|
|
838
|
+
const prependCtx = result?.prependSystemContext ?? '';
|
|
839
|
+
expect(prependCtx).not.toContain('OWNER-APPROVED BEHAVIOR DIRECTIVES');
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
it('existing evolution_principles behavior for legacy principles remains intact', async () => {
|
|
843
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
844
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
845
|
+
|
|
846
|
+
expect(result).toBeDefined();
|
|
847
|
+
expect(result?.appendSystemContext).toBeDefined();
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
it('feature flag disabled path still skips Runtime V2 directives with structured reason', async () => {
|
|
851
|
+
const artifactId = 'art-v2-flag-206';
|
|
852
|
+
const principleId = 'princ-v2-flag-206';
|
|
853
|
+
|
|
854
|
+
insertValidatedPrincipleArtifact({ artifactId, principleId });
|
|
855
|
+
await insertPromptActivation({ artifactId, principleId });
|
|
856
|
+
|
|
857
|
+
const pdDir = path.join(tempWorkspaceDir, '.pd');
|
|
858
|
+
fs.writeFileSync(
|
|
859
|
+
path.join(pdDir, 'feature-flags.yaml'),
|
|
860
|
+
'prompt:\n enabled: false\n',
|
|
861
|
+
'utf8',
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
const { handleBeforePromptBuild } = await import('../../src/hooks/prompt.js');
|
|
865
|
+
const result = await handleBeforePromptBuild(makeMinimalEvent(), makeCtx());
|
|
866
|
+
|
|
867
|
+
expect(result).toBeDefined();
|
|
868
|
+
});
|
|
869
|
+
});
|