@principles/pd-cli 1.133.0 → 1.135.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/dist/commands/__tests__/intent-flag-wiring.test.js +74 -3
- package/dist/commands/__tests__/intent-flag-wiring.test.js.map +1 -1
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts +2 -0
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts.map +1 -0
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js +60 -0
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js.map +1 -0
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js +11 -4
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +1 -1
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +9 -0
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +1 -1
- package/dist/commands/admission-gate.d.ts +24 -0
- package/dist/commands/admission-gate.d.ts.map +1 -0
- package/dist/commands/admission-gate.js +29 -0
- package/dist/commands/admission-gate.js.map +1 -0
- package/dist/commands/candidate.d.ts.map +1 -1
- package/dist/commands/candidate.js +72 -6
- package/dist/commands/candidate.js.map +1 -1
- package/dist/commands/console.d.ts.map +1 -1
- package/dist/commands/console.js +42 -0
- package/dist/commands/console.js.map +1 -1
- package/dist/commands/demo-story-a.d.ts.map +1 -1
- package/dist/commands/demo-story-a.js +4 -2
- package/dist/commands/demo-story-a.js.map +1 -1
- package/dist/commands/diagnose.d.ts.map +1 -1
- package/dist/commands/diagnose.js +155 -8
- package/dist/commands/diagnose.js.map +1 -1
- package/dist/commands/errors-list.d.ts +25 -0
- package/dist/commands/errors-list.d.ts.map +1 -0
- package/dist/commands/errors-list.js +368 -0
- package/dist/commands/errors-list.js.map +1 -0
- package/dist/commands/health-codex.d.ts +7 -0
- package/dist/commands/health-codex.d.ts.map +1 -0
- package/dist/commands/health-codex.js +209 -0
- package/dist/commands/health-codex.js.map +1 -0
- package/dist/commands/health.d.ts +24 -0
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +48 -0
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/intent.d.ts +21 -4
- package/dist/commands/intent.d.ts.map +1 -1
- package/dist/commands/intent.js +88 -27
- package/dist/commands/intent.js.map +1 -1
- package/dist/commands/pain-evidence.d.ts +6 -0
- package/dist/commands/pain-evidence.d.ts.map +1 -1
- package/dist/commands/pain-evidence.js +6 -0
- package/dist/commands/pain-evidence.js.map +1 -1
- package/dist/commands/pain-record.d.ts.map +1 -1
- package/dist/commands/pain-record.js +21 -0
- package/dist/commands/pain-record.js.map +1 -1
- package/dist/commands/pain-retry.d.ts +4 -0
- package/dist/commands/pain-retry.d.ts.map +1 -1
- package/dist/commands/pain-retry.js +327 -32
- package/dist/commands/pain-retry.js.map +1 -1
- package/dist/commands/proven-channel-baseline.d.ts.map +1 -1
- package/dist/commands/proven-channel-baseline.js +4 -2
- package/dist/commands/proven-channel-baseline.js.map +1 -1
- package/dist/commands/runtime-activation.d.ts +30 -0
- package/dist/commands/runtime-activation.d.ts.map +1 -1
- package/dist/commands/runtime-activation.js +567 -11
- package/dist/commands/runtime-activation.js.map +1 -1
- package/dist/commands/runtime-canary.d.ts.map +1 -1
- package/dist/commands/runtime-canary.js +32 -7
- package/dist/commands/runtime-canary.js.map +1 -1
- package/dist/commands/runtime-health-snapshot.d.ts.map +1 -1
- package/dist/commands/runtime-health-snapshot.js +4 -2
- package/dist/commands/runtime-health-snapshot.js.map +1 -1
- package/dist/commands/runtime-init.d.ts +43 -0
- package/dist/commands/runtime-init.d.ts.map +1 -0
- package/dist/commands/runtime-init.js +264 -0
- package/dist/commands/runtime-init.js.map +1 -0
- package/dist/commands/runtime-internalization-context-trace.d.ts +25 -0
- package/dist/commands/runtime-internalization-context-trace.d.ts.map +1 -0
- package/dist/commands/runtime-internalization-context-trace.js +374 -0
- package/dist/commands/runtime-internalization-context-trace.js.map +1 -0
- package/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-enqueue-successors.js +3 -1
- package/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -1
- package/dist/commands/runtime-internalization-integrity.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-integrity.js +4 -2
- package/dist/commands/runtime-internalization-integrity.js.map +1 -1
- package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-once.js +20 -5
- package/dist/commands/runtime-internalization-run-once.js.map +1 -1
- package/dist/commands/runtime-internalization-run-rulehost.d.ts +1 -0
- package/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-rulehost.js +149 -6
- package/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
- package/dist/commands/runtime-uat.d.ts.map +1 -1
- package/dist/commands/runtime-uat.guard.test.js +13 -15
- package/dist/commands/runtime-uat.guard.test.js.map +1 -1
- package/dist/commands/runtime-uat.js +4 -3
- package/dist/commands/runtime-uat.js.map +1 -1
- package/dist/config-reader.js +1 -1
- package/dist/config-reader.js.map +1 -1
- package/dist/index.js +54 -78
- package/dist/index.js.map +1 -1
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts +2 -0
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts.map +1 -0
- package/dist/services/__tests__/evaluator-runner-deps.test.js +304 -0
- package/dist/services/__tests__/evaluator-runner-deps.test.js.map +1 -0
- package/dist/services/__tests__/rulehost-readiness.test.js.map +1 -1
- package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +1 -1
- package/dist/services/config-doctor.d.ts.map +1 -1
- package/dist/services/config-doctor.js +9 -2
- package/dist/services/config-doctor.js.map +1 -1
- package/dist/services/console-launcher.d.ts +6 -0
- package/dist/services/console-launcher.d.ts.map +1 -1
- package/dist/services/console-launcher.js.map +1 -1
- package/dist/services/pain-flood-simulation-runner.d.ts.map +1 -1
- package/dist/services/pain-flood-simulation-runner.js +9 -0
- package/dist/services/pain-flood-simulation-runner.js.map +1 -1
- package/dist/services/pd-config-loader.js +2 -2
- package/dist/services/pd-config-loader.js.map +1 -1
- package/dist/services/proven-channel-baseline-runner.d.ts.map +1 -1
- package/dist/services/proven-channel-baseline-runner.js +10 -2
- package/dist/services/proven-channel-baseline-runner.js.map +1 -1
- package/dist/services/quality-scorecard/data-extractor.js.map +1 -1
- package/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
- package/dist/services/rulehost-pipeline-runner.d.ts +77 -1
- package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/dist/services/rulehost-pipeline-runner.js +111 -7
- package/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/dist/services/rulehost-readiness.js.map +1 -1
- package/dist/services/runtime-adapter-resolver.d.ts +1 -0
- package/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
- package/dist/services/runtime-adapter-resolver.js +3 -0
- package/dist/services/runtime-adapter-resolver.js.map +1 -1
- package/dist/services/synthetic-baseline-runner.d.ts.map +1 -1
- package/dist/services/synthetic-baseline-runner.js +11 -0
- package/dist/services/synthetic-baseline-runner.js.map +1 -1
- package/dist/utils/production-workspace-guard.d.ts +4 -4
- package/dist/utils/production-workspace-guard.d.ts.map +1 -1
- package/dist/utils/production-workspace-guard.js +19 -16
- package/dist/utils/production-workspace-guard.js.map +1 -1
- package/dist/utils/production-workspace-guard.test.js +54 -19
- package/dist/utils/production-workspace-guard.test.js.map +1 -1
- package/package.json +8 -5
- package/src/commands/__tests__/intent-flag-wiring.test.ts +88 -3
- package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +73 -0
- package/src/commands/__tests__/rulecode-flag-wiring.test.ts +31 -24
- package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +12 -0
- package/src/commands/admission-gate.ts +30 -0
- package/src/commands/candidate.ts +74 -6
- package/src/commands/console.ts +41 -0
- package/src/commands/demo-story-a.ts +4 -2
- package/src/commands/diagnose.ts +158 -10
- package/src/commands/errors-list.ts +440 -0
- package/src/commands/health-codex.ts +249 -0
- package/src/commands/health.ts +60 -0
- package/src/commands/intent.ts +98 -29
- package/src/commands/pain-evidence.ts +6 -0
- package/src/commands/pain-record.ts +21 -0
- package/src/commands/pain-retry.ts +353 -35
- package/src/commands/proven-channel-baseline.ts +4 -2
- package/src/commands/runtime-activation.ts +657 -12
- package/src/commands/runtime-canary.ts +31 -5
- package/src/commands/runtime-health-snapshot.ts +4 -2
- package/src/commands/runtime-init.ts +305 -0
- package/src/commands/runtime-internalization-context-trace.ts +445 -0
- package/src/commands/runtime-internalization-enqueue-successors.ts +3 -1
- package/src/commands/runtime-internalization-integrity.ts +4 -2
- package/src/commands/runtime-internalization-run-once.ts +20 -5
- package/src/commands/runtime-internalization-run-rulehost.ts +159 -6
- package/src/commands/runtime-uat.guard.test.ts +14 -16
- package/src/commands/runtime-uat.ts +4 -3
- package/src/config-reader.ts +1 -1
- package/src/index.ts +69 -78
- package/src/services/__tests__/evaluator-runner-deps.test.ts +341 -0
- package/src/services/config-doctor.ts +9 -2
- package/src/services/console-launcher.ts +6 -0
- package/src/services/pain-flood-simulation-runner.ts +9 -0
- package/src/services/pd-config-loader.ts +2 -2
- package/src/services/proven-channel-baseline-runner.ts +10 -3
- package/src/services/rulehost-pipeline-runner.ts +157 -6
- package/src/services/runtime-adapter-resolver.ts +4 -0
- package/src/services/synthetic-baseline-runner.ts +11 -0
- package/src/utils/production-workspace-guard.test.ts +62 -20
- package/src/utils/production-workspace-guard.ts +19 -16
- package/tests/bdd/cli-contract.steps.ts +343 -0
- package/tests/bdd/context-trace.steps.ts +229 -0
- package/tests/bdd/support/gherkin-loader.ts +147 -0
- package/tests/bdd/support/repo-root.ts +62 -0
- package/tests/bdd/support/vitest-bdd.ts +179 -0
- package/tests/commands/admission-gate-coverage.test.ts +79 -0
- package/tests/commands/candidate-audit-repair.test.ts +77 -0
- package/tests/commands/candidate-intake.test.ts +155 -10
- package/tests/commands/candidate-internalization-backfill.test.ts +30 -0
- package/tests/commands/cli-help-snapshot.test.ts +15 -1
- package/tests/commands/console-open.test.ts +229 -32
- package/tests/commands/diagnose.test.ts +619 -14
- package/tests/commands/errors-list.test.ts +678 -0
- package/tests/commands/health-codex.test.ts +243 -0
- package/tests/commands/health-host-wiring.test.ts +76 -0
- package/tests/commands/intent.test.ts +4 -4
- package/tests/commands/pain-record.test.ts +80 -0
- package/tests/commands/pain-retry.test.ts +94 -3
- package/tests/commands/run-rulehost-handler.test.ts +133 -0
- package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +227 -0
- package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +167 -0
- package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +262 -0
- package/tests/commands/runtime-activation-list-flag-wiring.test.ts +193 -0
- package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +222 -0
- package/tests/commands/runtime-activation.test.ts +1090 -5
- package/tests/commands/runtime-canary.test.ts +50 -0
- package/tests/commands/runtime-features.test.ts +2 -0
- package/tests/commands/runtime-init-empty-workspace.test.ts +234 -0
- package/tests/commands/runtime-init.test.ts +346 -0
- package/tests/commands/runtime-internalization-context-trace.test.ts +110 -0
- package/tests/commands/runtime-internalization.test.ts +2 -2
- package/tests/config-reader.test.ts +6 -5
- package/tests/e2e/cross-package-acceptance.test.ts +55 -13
- package/tests/services/mainline-snapshot-assembler.test.ts +6 -3
- package/tests/services/resolve-runtime-from-pd-config.test.ts +2 -0
- package/tests/services/rulehost-pipeline-runner.test.ts +1 -1
- package/vitest.config.ts +1 -1
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pd health --host codex command unit tests.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - cli-1: JSON mode outputs exactly one parseable JSON object.
|
|
6
|
+
* - cli-2: exit paths stop execution (process.exitCode set, no later side effects).
|
|
7
|
+
* - cli-5: failure paths do not mutate state (read-only).
|
|
8
|
+
* - cli-6: every degraded result includes reason + nextAction.
|
|
9
|
+
* - flag-off path (host.codex disabled → warning + exitCode).
|
|
10
|
+
* - hooks trust undetectable (no ~/.codex) → reason + nextAction.
|
|
11
|
+
* - dual registration detection (global hooks.json present → reason + nextAction).
|
|
12
|
+
* - happy path (flag on, hooks trusted) → clean JSON, no warnings.
|
|
13
|
+
*/
|
|
14
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
mockResolveNearestPdWorkspace,
|
|
18
|
+
mockLoadPdConfigForPlugin,
|
|
19
|
+
mockComputeFeatureFlagsFromConfig,
|
|
20
|
+
mockIsFeatureEnabled,
|
|
21
|
+
mockExistsSync,
|
|
22
|
+
mockReadFileSync,
|
|
23
|
+
mockHomedir,
|
|
24
|
+
mockRequireResolve,
|
|
25
|
+
mockConsoleLog,
|
|
26
|
+
mockConsoleWarn,
|
|
27
|
+
} = vi.hoisted(() => {
|
|
28
|
+
return {
|
|
29
|
+
mockResolveNearestPdWorkspace: vi.fn(),
|
|
30
|
+
mockLoadPdConfigForPlugin: vi.fn(),
|
|
31
|
+
mockComputeFeatureFlagsFromConfig: vi.fn(),
|
|
32
|
+
mockIsFeatureEnabled: vi.fn(),
|
|
33
|
+
mockExistsSync: vi.fn(),
|
|
34
|
+
mockReadFileSync: vi.fn(),
|
|
35
|
+
mockHomedir: vi.fn(),
|
|
36
|
+
mockRequireResolve: vi.fn(),
|
|
37
|
+
mockConsoleLog: vi.fn(),
|
|
38
|
+
mockConsoleWarn: vi.fn(),
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
vi.mock('@principles/host-runtime', () => ({
|
|
43
|
+
resolveNearestPdWorkspace: mockResolveNearestPdWorkspace,
|
|
44
|
+
loadPdConfigForPlugin: mockLoadPdConfigForPlugin,
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
vi.mock('@principles/core/runtime-v2', () => ({
|
|
48
|
+
computeFeatureFlagsFromConfig: mockComputeFeatureFlagsFromConfig,
|
|
49
|
+
isFeatureEnabled: mockIsFeatureEnabled,
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
vi.mock('../../src/resolve-workspace.js', () => ({
|
|
53
|
+
resolveWorkspaceDir: vi.fn().mockReturnValue('/fake/workspace'),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
vi.mock('fs', () => ({
|
|
57
|
+
existsSync: mockExistsSync,
|
|
58
|
+
readFileSync: mockReadFileSync,
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
vi.mock('os', () => ({
|
|
62
|
+
homedir: mockHomedir,
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
vi.mock('module', () => ({
|
|
66
|
+
createRequire: () => ({
|
|
67
|
+
resolve: mockRequireResolve,
|
|
68
|
+
}),
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
import { handleHealthCodex } from '../../src/commands/health-codex.js';
|
|
72
|
+
|
|
73
|
+
describe('pd health --host codex', () => {
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
vi.clearAllMocks();
|
|
76
|
+
process.exitCode = undefined;
|
|
77
|
+
mockHomedir.mockReturnValue('/fake/home');
|
|
78
|
+
mockRequireResolve.mockImplementation((name: string) => `/fake/node_modules/${name}/package.json`);
|
|
79
|
+
mockReadFileSync.mockImplementation((p: string) => {
|
|
80
|
+
if (typeof p === 'string' && p.includes('package.json')) {
|
|
81
|
+
return JSON.stringify({ version: '0.1.0' });
|
|
82
|
+
}
|
|
83
|
+
return '';
|
|
84
|
+
});
|
|
85
|
+
mockResolveNearestPdWorkspace.mockReturnValue({
|
|
86
|
+
ok: true,
|
|
87
|
+
workspaceDir: '/fake/workspace',
|
|
88
|
+
configPath: '/fake/workspace/.pd/config.yaml',
|
|
89
|
+
source: 'nearest',
|
|
90
|
+
});
|
|
91
|
+
mockLoadPdConfigForPlugin.mockReturnValue({
|
|
92
|
+
ok: true,
|
|
93
|
+
effective: {},
|
|
94
|
+
source: 'user_config',
|
|
95
|
+
configPath: '/fake/workspace/.pd/config.yaml',
|
|
96
|
+
warnings: [],
|
|
97
|
+
errors: [],
|
|
98
|
+
});
|
|
99
|
+
mockComputeFeatureFlagsFromConfig.mockReturnValue({ flags: {} });
|
|
100
|
+
mockIsFeatureEnabled.mockReturnValue(true);
|
|
101
|
+
// No ~/.codex by default → hooks trust undetectable
|
|
102
|
+
mockExistsSync.mockImplementation((p: string) => !p.includes('.codex'));
|
|
103
|
+
mockConsoleLog.mockImplementation(() => {});
|
|
104
|
+
mockConsoleWarn.mockImplementation(() => {});
|
|
105
|
+
vi.spyOn(console, 'log').mockImplementation(mockConsoleLog);
|
|
106
|
+
vi.spyOn(console, 'warn').mockImplementation(mockConsoleWarn);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
afterEach(() => {
|
|
110
|
+
process.exitCode = undefined;
|
|
111
|
+
vi.restoreAllMocks();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('cli-1: --json outputs exactly one parseable JSON object on stdout', async () => {
|
|
115
|
+
// ~/.codex exists with trusted hooks for happy path
|
|
116
|
+
mockExistsSync.mockImplementation((p: string) => true);
|
|
117
|
+
mockReadFileSync.mockImplementation((p: string) => {
|
|
118
|
+
if (typeof p === 'string' && p.includes('package.json')) return JSON.stringify({ version: '0.1.0' });
|
|
119
|
+
if (typeof p === 'string' && p.includes('config.toml')) return '[features]\nhooks = true\n';
|
|
120
|
+
return '';
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await handleHealthCodex({ json: true });
|
|
124
|
+
|
|
125
|
+
expect(mockConsoleLog).toHaveBeenCalledTimes(1);
|
|
126
|
+
const output = mockConsoleLog.mock.calls[0][0] as string;
|
|
127
|
+
const parsed = JSON.parse(output);
|
|
128
|
+
expect(parsed.host).toBe('codex');
|
|
129
|
+
expect(parsed.adapterVersion).toBe('0.1.0');
|
|
130
|
+
expect(parsed.runtimeVersion).toBe('0.1.0');
|
|
131
|
+
expect(parsed.featureFlag.enabled).toBe(true);
|
|
132
|
+
expect(parsed.hooksTrust.detectable).toBe(true);
|
|
133
|
+
expect(parsed.hooksTrust.trusted).toBe(true);
|
|
134
|
+
expect(process.exitCode).toBeUndefined();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('cli-6: when ~/.codex is missing, reports reason + nextAction', async () => {
|
|
138
|
+
mockExistsSync.mockImplementation((p: string) => !p.includes('.codex'));
|
|
139
|
+
|
|
140
|
+
await handleHealthCodex({ json: true });
|
|
141
|
+
|
|
142
|
+
const output = mockConsoleLog.mock.calls[0][0] as string;
|
|
143
|
+
const parsed = JSON.parse(output);
|
|
144
|
+
expect(parsed.hooksTrust.detectable).toBe(false);
|
|
145
|
+
expect(parsed.hooksTrust.reason).toBe('codex_config_dir_not_found');
|
|
146
|
+
expect(parsed.hooksTrust.nextAction).toContain('Install Codex CLI');
|
|
147
|
+
expect(parsed.warnings).toContainEqual(expect.stringContaining('hooks_trust'));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('flag-off + hooks untrusted → exitCode 1', async () => {
|
|
151
|
+
mockIsFeatureEnabled.mockReturnValue(false);
|
|
152
|
+
mockExistsSync.mockImplementation((p: string) => !p.includes('.codex'));
|
|
153
|
+
|
|
154
|
+
await handleHealthCodex({ json: true });
|
|
155
|
+
|
|
156
|
+
expect(process.exitCode).toBe(1);
|
|
157
|
+
const output = mockConsoleLog.mock.calls[0][0] as string;
|
|
158
|
+
const parsed = JSON.parse(output);
|
|
159
|
+
expect(parsed.featureFlag.enabled).toBe(false);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('cli-6: config.toml exists but hooks setting missing → reason + nextAction', async () => {
|
|
163
|
+
mockExistsSync.mockImplementation((p: string) => true);
|
|
164
|
+
mockReadFileSync.mockImplementation((p: string) => {
|
|
165
|
+
if (typeof p === 'string' && p.includes('package.json')) return JSON.stringify({ version: '0.1.0' });
|
|
166
|
+
if (typeof p === 'string' && p.includes('config.toml')) return '[features]\nmodel = "gpt-4"\n';
|
|
167
|
+
return '';
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await handleHealthCodex({ json: true });
|
|
171
|
+
|
|
172
|
+
const output = mockConsoleLog.mock.calls[0][0] as string;
|
|
173
|
+
const parsed = JSON.parse(output);
|
|
174
|
+
expect(parsed.hooksTrust.detectable).toBe(false);
|
|
175
|
+
expect(parsed.hooksTrust.reason).toBe('hooks_setting_not_found_in_config');
|
|
176
|
+
expect(parsed.hooksTrust.nextAction).toContain('/hooks');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('dual registration: global hooks.json present → detected + reason + nextAction', async () => {
|
|
180
|
+
mockExistsSync.mockImplementation((p: string) => true);
|
|
181
|
+
mockReadFileSync.mockImplementation((p: string) => {
|
|
182
|
+
if (typeof p === 'string' && p.includes('package.json')) return JSON.stringify({ version: '0.1.0' });
|
|
183
|
+
if (typeof p === 'string' && p.includes('config.toml')) return '[features]\nhooks = true\n';
|
|
184
|
+
return '';
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await handleHealthCodex({ json: true });
|
|
188
|
+
|
|
189
|
+
const output = mockConsoleLog.mock.calls[0][0] as string;
|
|
190
|
+
const parsed = JSON.parse(output);
|
|
191
|
+
expect(parsed.dualRegistration.detected).toBe(true);
|
|
192
|
+
expect(parsed.dualRegistration.globalHooksPath).toContain('hooks.json');
|
|
193
|
+
expect(parsed.dualRegistration.reason).toBe('global_hooks_json_present');
|
|
194
|
+
expect(parsed.dualRegistration.nextAction).toContain('double-registration');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('cli-5: workspace not resolved does not mutate state, reports warning', async () => {
|
|
198
|
+
mockResolveNearestPdWorkspace.mockReturnValue({
|
|
199
|
+
ok: false,
|
|
200
|
+
cwd: '/fake/workspace',
|
|
201
|
+
reason: 'config_not_found',
|
|
202
|
+
nextAction: 'Create .pd/config.yaml',
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
await handleHealthCodex({ json: true });
|
|
206
|
+
|
|
207
|
+
const output = mockConsoleLog.mock.calls[0][0] as string;
|
|
208
|
+
const parsed = JSON.parse(output);
|
|
209
|
+
expect(parsed.workspace).toBe('/fake/workspace');
|
|
210
|
+
expect(parsed.featureFlag.enabled).toBe(false);
|
|
211
|
+
expect(parsed.featureFlag.source).toBe('defaults');
|
|
212
|
+
expect(parsed.warnings).toContainEqual(expect.stringContaining('workspace_not_resolved'));
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('text output includes nextAction for degraded hooks trust', async () => {
|
|
216
|
+
mockExistsSync.mockImplementation((p: string) => !p.includes('.codex'));
|
|
217
|
+
|
|
218
|
+
await handleHealthCodex({ json: false });
|
|
219
|
+
|
|
220
|
+
const calls = mockConsoleLog.mock.calls.map((c) => c[0] as string);
|
|
221
|
+
expect(calls.some((s) => s.includes('hooksTrust.nextAction:'))).toBe(true);
|
|
222
|
+
expect(calls.some((s) => s.includes('host: codex'))).toBe(true);
|
|
223
|
+
expect(mockConsoleWarn).toHaveBeenCalled();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('adapter/runtime version unknown when package.json unreadable', async () => {
|
|
227
|
+
mockRequireResolve.mockImplementation(() => {
|
|
228
|
+
throw new Error('not found');
|
|
229
|
+
});
|
|
230
|
+
mockExistsSync.mockImplementation((p: string) => true);
|
|
231
|
+
mockReadFileSync.mockImplementation((p: string) => {
|
|
232
|
+
if (typeof p === 'string' && p.includes('config.toml')) return '[features]\nhooks = true\n';
|
|
233
|
+
return '';
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
await handleHealthCodex({ json: true });
|
|
237
|
+
|
|
238
|
+
const output = mockConsoleLog.mock.calls[0][0] as string;
|
|
239
|
+
const parsed = JSON.parse(output);
|
|
240
|
+
expect(parsed.adapterVersion).toBe('unknown');
|
|
241
|
+
expect(parsed.runtimeVersion).toBe('unknown');
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli-7 test-wiring: prove the real `registerHealthCommand` (used by the CLI
|
|
3
|
+
* entrypoint) wires the `--host codex` flag, dispatches to handleHealthCodex,
|
|
4
|
+
* and rejects unknown host values with a structured error + non-zero exit.
|
|
5
|
+
*
|
|
6
|
+
* Handlers are injected so the test exercises the real production registration
|
|
7
|
+
* without executing real workspace/DB I/O.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { registerHealthCommand } from '../../src/commands/health.js';
|
|
12
|
+
|
|
13
|
+
const mockHandleHealth = vi.fn();
|
|
14
|
+
const mockHandleHealthCodex = vi.fn();
|
|
15
|
+
|
|
16
|
+
const originalExitCode = process.exitCode;
|
|
17
|
+
|
|
18
|
+
function buildHealthCommand(): Command {
|
|
19
|
+
const program = new Command();
|
|
20
|
+
registerHealthCommand(program, { health: mockHandleHealth, healthCodex: mockHandleHealthCodex });
|
|
21
|
+
return program;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('cli-7: pd health --host flag wiring (production registration)', () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.clearAllMocks();
|
|
27
|
+
process.exitCode = originalExitCode;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
process.exitCode = originalExitCode;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('dispatches to handleHealthCodex when --host codex is passed', async () => {
|
|
35
|
+
const program = buildHealthCommand();
|
|
36
|
+
await program.parseAsync(['node', 'pd', 'health', '--host', 'codex', '--json']);
|
|
37
|
+
expect(mockHandleHealthCodex).toHaveBeenCalledTimes(1);
|
|
38
|
+
expect(mockHandleHealthCodex).toHaveBeenCalledWith(expect.objectContaining({ host: 'codex', json: true }));
|
|
39
|
+
expect(mockHandleHealth).not.toHaveBeenCalled();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('dispatches to handleHealth when --host is omitted (defaults to openclaw)', async () => {
|
|
43
|
+
const program = buildHealthCommand();
|
|
44
|
+
await program.parseAsync(['node', 'pd', 'health']);
|
|
45
|
+
expect(mockHandleHealth).toHaveBeenCalledTimes(1);
|
|
46
|
+
expect(mockHandleHealthCodex).not.toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('dispatches to handleHealth when --host openclaw is passed', async () => {
|
|
50
|
+
const program = buildHealthCommand();
|
|
51
|
+
await program.parseAsync(['node', 'pd', 'health', '--host', 'openclaw']);
|
|
52
|
+
expect(mockHandleHealth).toHaveBeenCalledTimes(1);
|
|
53
|
+
expect(mockHandleHealthCodex).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('passes --workspace through to handleHealthCodex', async () => {
|
|
57
|
+
const program = buildHealthCommand();
|
|
58
|
+
await program.parseAsync(['node', 'pd', 'health', '--host', 'codex', '-w', '/some/path']);
|
|
59
|
+
expect(mockHandleHealthCodex).toHaveBeenCalledWith(expect.objectContaining({ workspace: '/some/path' }));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('rejects an unknown --host value without dispatching', async () => {
|
|
63
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
64
|
+
const program = buildHealthCommand();
|
|
65
|
+
await program.parseAsync(['node', 'pd', 'health', '--host', 'codxe', '--json']);
|
|
66
|
+
expect(mockHandleHealth).not.toHaveBeenCalled();
|
|
67
|
+
expect(mockHandleHealthCodex).not.toHaveBeenCalled();
|
|
68
|
+
expect(process.exitCode).toBe(1);
|
|
69
|
+
const payload = JSON.parse(String(logSpy.mock.calls[0][0]));
|
|
70
|
+
expect(payload.ok).toBe(false);
|
|
71
|
+
expect(payload.reason).toBe('invalid_host');
|
|
72
|
+
expect(payload.host).toBe('codxe');
|
|
73
|
+
expect(payload.nextAction).toBeTruthy();
|
|
74
|
+
logSpy.mockRestore();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -59,7 +59,7 @@ function writeConfig(intentEnabled: boolean): void {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
function getIntentPath(): string {
|
|
62
|
-
return path.join(workspaceDir, '.principles', 'INTENT.md');
|
|
62
|
+
return path.join(workspaceDir, '.principles', 'INTENT.zh-CN.md');
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const VALID_INTENT = `# INTENT.md
|
|
@@ -107,7 +107,7 @@ describe('handleIntentInit', () => {
|
|
|
107
107
|
expect(logSpy).toHaveBeenCalled();
|
|
108
108
|
const jsonOutput = JSON.parse(logSpy.mock.calls[0][0] as string);
|
|
109
109
|
expect(jsonOutput.status).toBe('dry_run');
|
|
110
|
-
expect(jsonOutput.path).toContain('INTENT.md');
|
|
110
|
+
expect(jsonOutput.path).toContain('INTENT.zh-CN.md');
|
|
111
111
|
expect(jsonOutput.reason).toBe('dry_run');
|
|
112
112
|
expect(jsonOutput.nextAction).toContain('--confirm');
|
|
113
113
|
|
|
@@ -177,7 +177,7 @@ describe('handleIntentInit', () => {
|
|
|
177
177
|
expect(logSpy).toHaveBeenCalled();
|
|
178
178
|
const jsonOutput = JSON.parse(logSpy.mock.calls[0][0] as string);
|
|
179
179
|
expect(jsonOutput.status).toBe('ok');
|
|
180
|
-
expect(jsonOutput.path).toContain('INTENT.md');
|
|
180
|
+
expect(jsonOutput.path).toContain('INTENT.zh-CN.md');
|
|
181
181
|
expect(jsonOutput.overwritten).toBe(false);
|
|
182
182
|
|
|
183
183
|
logSpy.mockRestore();
|
|
@@ -311,7 +311,7 @@ describe('handleIntentShow', () => {
|
|
|
311
311
|
await handleIntentShow({ workspace: workspaceDir, json: false });
|
|
312
312
|
|
|
313
313
|
const textOutput = logSpy.mock.calls[0][0] as string;
|
|
314
|
-
expect(textOutput).toContain('INTENT.md');
|
|
314
|
+
expect(textOutput).toContain('INTENT.zh-CN.md');
|
|
315
315
|
expect(textOutput).toContain('Content hash:');
|
|
316
316
|
expect(textOutput).toContain('Last edited:');
|
|
317
317
|
expect(textOutput).toContain('## 1. Why');
|
|
@@ -398,4 +398,84 @@ describe('pd pain record', () => {
|
|
|
398
398
|
logSpy.mockRestore();
|
|
399
399
|
exitSpy.mockRestore();
|
|
400
400
|
});
|
|
401
|
+
|
|
402
|
+
// ── Bug-P fix: reason length validation ──────────────────────────────────
|
|
403
|
+
|
|
404
|
+
// RL-01: reason length within limit processes normally
|
|
405
|
+
it('RL-01: accepts reason with length ≤ 500 (boundary)', async () => {
|
|
406
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
407
|
+
const exitSpy = mockProcessExit();
|
|
408
|
+
|
|
409
|
+
const reason500 = 'a'.repeat(500);
|
|
410
|
+
await handlePainRecord({ reason: reason500, json: true });
|
|
411
|
+
|
|
412
|
+
expect(exitSpy).not.toHaveBeenCalledWith(1);
|
|
413
|
+
expect(logSpy).toHaveBeenCalled();
|
|
414
|
+
|
|
415
|
+
logSpy.mockRestore();
|
|
416
|
+
exitSpy.mockRestore();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// RL-02: reason length over limit exits 1 with error message
|
|
420
|
+
it('RL-02: exits 1 when reason length > 500', async () => {
|
|
421
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
422
|
+
const exitSpy = mockProcessExit();
|
|
423
|
+
|
|
424
|
+
const reason501 = 'a'.repeat(501);
|
|
425
|
+
await handlePainRecord({ reason: reason501 });
|
|
426
|
+
|
|
427
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('must be at most 500 characters'));
|
|
428
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('got 501'));
|
|
429
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Next action'));
|
|
430
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
431
|
+
|
|
432
|
+
errorSpy.mockRestore();
|
|
433
|
+
exitSpy.mockRestore();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// RL-03: short reason processes normally (smoke test)
|
|
437
|
+
it('RL-03: accepts short reason without error', async () => {
|
|
438
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
439
|
+
const exitSpy = mockProcessExit();
|
|
440
|
+
|
|
441
|
+
await handlePainRecord({ reason: 'short reason', json: true });
|
|
442
|
+
|
|
443
|
+
expect(exitSpy).not.toHaveBeenCalledWith(1);
|
|
444
|
+
expect(logSpy).toHaveBeenCalled();
|
|
445
|
+
|
|
446
|
+
logSpy.mockRestore();
|
|
447
|
+
exitSpy.mockRestore();
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// RL-04: CodeRabbit review fix — --json mode emits structured JSON (cli-1, cli-2, cli-5)
|
|
451
|
+
it('RL-04: --json mode emits structured JSON on reason_too_long and stops execution', async () => {
|
|
452
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
453
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
454
|
+
const exitSpy = mockProcessExit();
|
|
455
|
+
|
|
456
|
+
const reason501 = 'a'.repeat(501);
|
|
457
|
+
await handlePainRecord({ reason: reason501, json: true });
|
|
458
|
+
|
|
459
|
+
// cli-1: exactly one JSON object on stdout
|
|
460
|
+
expect(logSpy).toHaveBeenCalledTimes(1);
|
|
461
|
+
const jsonOutput = JSON.parse(logSpy.mock.calls[0][0]);
|
|
462
|
+
expect(jsonOutput.status).toBe('failed');
|
|
463
|
+
expect(jsonOutput.reason).toBe('reason_too_long');
|
|
464
|
+
expect(jsonOutput.message).toContain('must be at most 500 characters');
|
|
465
|
+
expect(jsonOutput.message).toContain('got 501');
|
|
466
|
+
expect(jsonOutput.nextAction).toContain('Shorten the reason text');
|
|
467
|
+
|
|
468
|
+
// cli-2: execution stopped — recordPain must NOT have been called
|
|
469
|
+
expect(lastRecordPainInput).toBeNull();
|
|
470
|
+
|
|
471
|
+
// cli-5: failure path exits 1
|
|
472
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
473
|
+
|
|
474
|
+
// No text leaked to stderr
|
|
475
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
476
|
+
|
|
477
|
+
logSpy.mockRestore();
|
|
478
|
+
errorSpy.mockRestore();
|
|
479
|
+
exitSpy.mockRestore();
|
|
480
|
+
});
|
|
401
481
|
});
|
|
@@ -90,6 +90,35 @@ vi.mock('../../src/resolve-workspace.js', () => ({
|
|
|
90
90
|
resolveWorkspaceDir: vi.fn().mockReturnValue('/tmp/fake-workspace'),
|
|
91
91
|
}));
|
|
92
92
|
|
|
93
|
+
// BUG-1 (PRI-442): capture runner constructor args to verify effectiveConfig wiring
|
|
94
|
+
const { diagRootCauseRunnerCtor, diagDistillerRunnerCtor, diagRouterRunnerCtor } = vi.hoisted(() => {
|
|
95
|
+
const diagRootCauseRunnerCtor = vi.fn().mockImplementation(function () { return {}; });
|
|
96
|
+
const diagDistillerRunnerCtor = vi.fn().mockImplementation(function () { return {}; });
|
|
97
|
+
const diagRouterRunnerCtor = vi.fn().mockImplementation(function () { return {}; });
|
|
98
|
+
return { diagRootCauseRunnerCtor, diagDistillerRunnerCtor, diagRouterRunnerCtor };
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
vi.mock('../../src/services/pd-config-loader.js', () => ({
|
|
102
|
+
loadPdConfig: vi.fn().mockReturnValue({
|
|
103
|
+
ok: true,
|
|
104
|
+
effective: { config: { featureFlags: { diagnostician_llm_degradation: true } }, source: 'file', warnings: [] },
|
|
105
|
+
defaults: { config: {}, source: 'defaults', warnings: [] },
|
|
106
|
+
}),
|
|
107
|
+
computeFlagsFromLoadResult: vi.fn().mockReturnValue({}),
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
// Dead-letter store mock: getByPainId returns null by default so the
|
|
111
|
+
// implementation produces status='not_found' with reason='task_not_found'.
|
|
112
|
+
// Tests that need to exercise the dead-letter replay path override this.
|
|
113
|
+
const { MockSqliteDeadLetterStore, mockDeadLetterGetByPainId } = vi.hoisted(() => {
|
|
114
|
+
const mockDeadLetterGetByPainId = vi.fn().mockReturnValue(null);
|
|
115
|
+
class MockSqliteDeadLetterStore {
|
|
116
|
+
getByPainId = mockDeadLetterGetByPainId;
|
|
117
|
+
constructor(_connection: unknown) {}
|
|
118
|
+
}
|
|
119
|
+
return { MockSqliteDeadLetterStore, mockDeadLetterGetByPainId };
|
|
120
|
+
});
|
|
121
|
+
|
|
93
122
|
vi.mock('@principles/core/runtime-v2', () => {
|
|
94
123
|
return {
|
|
95
124
|
RuntimeStateManager: vi.fn().mockImplementation(function () {
|
|
@@ -100,12 +129,14 @@ vi.mock('@principles/core/runtime-v2', () => {
|
|
|
100
129
|
SqliteDiagnosticianCommitter: vi.fn().mockImplementation(function () { return {}; }),
|
|
101
130
|
SqliteTrajectoryLocator: vi.fn().mockImplementation(function () { return {}; }),
|
|
102
131
|
SqliteSourceTraceLocator: vi.fn().mockImplementation(function () { return {}; }),
|
|
132
|
+
SqliteDeadLetterStore: MockSqliteDeadLetterStore,
|
|
133
|
+
PainSignalBridge: vi.fn().mockImplementation(function () { return {}; }),
|
|
103
134
|
StoreEventEmitter: vi.fn().mockImplementation(function () { return {}; }),
|
|
104
135
|
storeEmitter: { emitTelemetry: vi.fn() },
|
|
105
136
|
SplitDiagnosticianRunner: vi.fn().mockImplementation(function () { return {}; }),
|
|
106
|
-
DiagRootCauseRunner:
|
|
107
|
-
DiagDistillerRunner:
|
|
108
|
-
DiagRouterRunner:
|
|
137
|
+
DiagRootCauseRunner: diagRootCauseRunnerCtor,
|
|
138
|
+
DiagDistillerRunner: diagDistillerRunnerCtor,
|
|
139
|
+
DiagRouterRunner: diagRouterRunnerCtor,
|
|
109
140
|
DefaultDiagRootCauseValidator: vi.fn().mockImplementation(function () { return {}; }),
|
|
110
141
|
DefaultDiagDistillerValidator: vi.fn().mockImplementation(function () { return {}; }),
|
|
111
142
|
DisabledDiagnosticianRunner: vi.fn().mockImplementation(function () { return {}; }),
|
|
@@ -120,6 +151,15 @@ vi.mock('@principles/core/runtime-v2', () => {
|
|
|
120
151
|
}
|
|
121
152
|
},
|
|
122
153
|
CandidateIntakeService: MockCandidateIntakeService,
|
|
154
|
+
// PRI-503: admission gate mock — admit by default so existing retry/intake
|
|
155
|
+
// tests keep their original flow. Tests that need to assert refusal behavior
|
|
156
|
+
// can override this mock per-test.
|
|
157
|
+
evaluateCandidateAdmissionFromRecord: vi.fn().mockReturnValue({
|
|
158
|
+
decision: 'admitted',
|
|
159
|
+
reason: 'mock_admitted',
|
|
160
|
+
nextAction: 'none',
|
|
161
|
+
evidenceStatus: 'unknown',
|
|
162
|
+
}),
|
|
123
163
|
resolveRuntimeConfig: mockResolveRuntimeConfig,
|
|
124
164
|
isRuntimeConfigError: vi.fn().mockReturnValue(false),
|
|
125
165
|
isFeatureEnabled: vi.fn().mockReturnValue(true),
|
|
@@ -233,6 +273,7 @@ describe('pd pain retry — validation and error paths', () => {
|
|
|
233
273
|
mockUpdateCandidateStatus.mockResolvedValue(undefined);
|
|
234
274
|
mockGetRunsByTask.mockResolvedValue([]);
|
|
235
275
|
mockIntake.mockReset();
|
|
276
|
+
mockDeadLetterGetByPainId.mockReturnValue(null);
|
|
236
277
|
mockRun.mockResolvedValue({
|
|
237
278
|
status: 'succeeded',
|
|
238
279
|
taskId: 'diagnosis_test-pain-1',
|
|
@@ -770,6 +811,7 @@ describe('pd pain retry — human-readable output', () => {
|
|
|
770
811
|
mockUpdateCandidateStatus.mockResolvedValue(undefined);
|
|
771
812
|
mockGetRunsByTask.mockResolvedValue([]);
|
|
772
813
|
mockIntake.mockReset();
|
|
814
|
+
mockDeadLetterGetByPainId.mockReturnValue(null);
|
|
773
815
|
mockRun.mockResolvedValue({
|
|
774
816
|
status: 'succeeded',
|
|
775
817
|
taskId: 'diagnosis_test-pain-1',
|
|
@@ -957,3 +999,52 @@ describe('Commander wiring for pd pain retry', () => {
|
|
|
957
999
|
expect(capturedOpts.openclawGateway).toBe(true);
|
|
958
1000
|
});
|
|
959
1001
|
});
|
|
1002
|
+
|
|
1003
|
+
// BUG-1 (PRI-442): effectiveConfig must be passed to split-pipeline runners
|
|
1004
|
+
// so that ADR-0019 LLM rate-limit degradation (isDegradationEnabled) can fire.
|
|
1005
|
+
// EP-02: production path wiring.
|
|
1006
|
+
describe('BUG-1 (PRI-442): pain retry — effectiveConfig wiring to split-pipeline runners', () => {
|
|
1007
|
+
beforeEach(() => {
|
|
1008
|
+
vi.clearAllMocks();
|
|
1009
|
+
mockGetCandidatesByTaskId.mockResolvedValue([]);
|
|
1010
|
+
mockUpdateCandidateStatus.mockResolvedValue(undefined);
|
|
1011
|
+
mockGetRunsByTask.mockResolvedValue([]);
|
|
1012
|
+
mockIntake.mockReset();
|
|
1013
|
+
mockRun.mockResolvedValue({
|
|
1014
|
+
status: 'succeeded',
|
|
1015
|
+
taskId: 'diagnosis_test-pain-1',
|
|
1016
|
+
runId: 'run-retry-1',
|
|
1017
|
+
contextHash: 'abc123',
|
|
1018
|
+
});
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
it('passes effectiveConfig to all three runners when split pipeline is enabled', async () => {
|
|
1022
|
+
mockGetTask.mockResolvedValue(RETRY_WAIT_TASK);
|
|
1023
|
+
|
|
1024
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
1025
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
1026
|
+
|
|
1027
|
+
await handlePainRetry({
|
|
1028
|
+
painId: 'test-pain-1',
|
|
1029
|
+
workspace: '/tmp/fake-workspace',
|
|
1030
|
+
runtime: 'test-double',
|
|
1031
|
+
json: true,
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
expect(diagRootCauseRunnerCtor).toHaveBeenCalled();
|
|
1035
|
+
const rootOptions = diagRootCauseRunnerCtor.mock.calls[0]?.[1];
|
|
1036
|
+
expect(rootOptions?.effectiveConfig).toBeDefined();
|
|
1037
|
+
expect(rootOptions?.effectiveConfig).toEqual(
|
|
1038
|
+
expect.objectContaining({ config: expect.anything(), source: 'file' }),
|
|
1039
|
+
);
|
|
1040
|
+
|
|
1041
|
+
const distillerOptions = diagDistillerRunnerCtor.mock.calls[0]?.[1];
|
|
1042
|
+
expect(distillerOptions?.effectiveConfig).toBeDefined();
|
|
1043
|
+
|
|
1044
|
+
const routerOptions = diagRouterRunnerCtor.mock.calls[0]?.[1];
|
|
1045
|
+
expect(routerOptions?.effectiveConfig).toBeDefined();
|
|
1046
|
+
|
|
1047
|
+
logSpy.mockRestore();
|
|
1048
|
+
exitSpy.mockRestore();
|
|
1049
|
+
});
|
|
1050
|
+
});
|