@principles/pd-cli 1.100.0 → 1.102.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/diagnose.js +27 -27
- package/dist/commands/diagnose.js.map +1 -1
- package/dist/commands/pain-retry.d.ts.map +1 -1
- package/dist/commands/pain-retry.js +22 -27
- package/dist/commands/pain-retry.js.map +1 -1
- package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-once.js +11 -9
- package/dist/commands/runtime-internalization-run-once.js.map +1 -1
- package/dist/commands/runtime.d.ts +1 -1
- package/dist/commands/runtime.d.ts.map +1 -1
- package/dist/commands/runtime.js +92 -25
- package/dist/commands/runtime.js.map +1 -1
- package/dist/commands/task.js +2 -2
- package/dist/commands/task.js.map +1 -1
- package/dist/services/resolve-runtime-from-pd-config.d.ts +59 -0
- package/dist/services/resolve-runtime-from-pd-config.d.ts.map +1 -0
- package/dist/services/resolve-runtime-from-pd-config.js +96 -0
- package/dist/services/resolve-runtime-from-pd-config.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/diagnose.ts +26 -26
- package/src/commands/pain-retry.ts +21 -25
- package/src/commands/runtime-internalization-run-once.ts +10 -9
- package/src/commands/runtime.ts +96 -24
- package/src/commands/task.ts +2 -2
- package/src/services/resolve-runtime-from-pd-config.ts +142 -0
- package/tests/commands/console-launcher-edge-cases.test.ts +14 -47
- package/tests/commands/diagnose.test.ts +91 -39
- package/tests/commands/pain-retry.test.ts +130 -15
- package/tests/commands/pri-393-runtime-config-unification.test.ts +284 -0
- package/tests/commands/runtime-internalization-integrity-repair.test.ts +38 -0
- package/tests/commands/runtime-internalization-run-once.test.ts +59 -53
- package/tests/commands/runtime.test.ts +124 -1
- package/tests/commands/task.test.ts +9 -1
|
@@ -82,6 +82,44 @@ describe('handleRuntimeInternalizationIntegrityRepair', () => {
|
|
|
82
82
|
expect(jsonOutput.repairedCount).toBe(1);
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
+
it('emits malformed_run_row quarantine actions as a single JSON object', async () => {
|
|
86
|
+
// Verifies the new malformed-run repair pass flows through the CLI contract
|
|
87
|
+
// unchanged: exactly one JSON object on stdout, with the quarantine action present.
|
|
88
|
+
mockRepair.mockReturnValue(makeResult({
|
|
89
|
+
dryRun: false,
|
|
90
|
+
repairedCount: 1,
|
|
91
|
+
actions: [
|
|
92
|
+
{
|
|
93
|
+
action: 'quarantine_malformed_run',
|
|
94
|
+
targetId: 'run-malf-1',
|
|
95
|
+
taskId: 'task-1',
|
|
96
|
+
type: 'malformed_run_row',
|
|
97
|
+
severity: 'warning',
|
|
98
|
+
previousState: 'queued',
|
|
99
|
+
nextState: 'failed',
|
|
100
|
+
previousStatus: 'queued',
|
|
101
|
+
newStatus: 'failed',
|
|
102
|
+
recommendedAction: 'quarantine_malformed_run',
|
|
103
|
+
reason: 'Run run-malf-1 (task task-1) failed schema validation — quarantined',
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
await handleRuntimeInternalizationIntegrityRepair({ workspace: WS, confirm: true, json: true });
|
|
109
|
+
|
|
110
|
+
// Exactly one console.log call (single JSON object, no banners/extra stdout).
|
|
111
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
|
|
112
|
+
const jsonOutput = JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
|
113
|
+
expect(jsonOutput.repairedCount).toBe(1);
|
|
114
|
+
expect(jsonOutput.actions).toHaveLength(1);
|
|
115
|
+
expect(jsonOutput.actions[0]).toMatchObject({
|
|
116
|
+
type: 'malformed_run_row',
|
|
117
|
+
recommendedAction: 'quarantine_malformed_run',
|
|
118
|
+
newStatus: 'failed',
|
|
119
|
+
targetId: 'run-malf-1',
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
85
123
|
it('outputs text format when --json not specified', async () => {
|
|
86
124
|
mockRepair.mockReturnValue(makeResult({ dryRun: true }));
|
|
87
125
|
|
|
@@ -20,6 +20,27 @@ vi.mock('../../src/resolve-workspace.js', () => ({
|
|
|
20
20
|
resolveWorkspaceDir: vi.fn().mockReturnValue('/fake/workspace'),
|
|
21
21
|
}));
|
|
22
22
|
|
|
23
|
+
const { mockResolveRuntimeFromPdConfig } = vi.hoisted(() => {
|
|
24
|
+
const mockResolveRuntimeFromPdConfig = vi.fn().mockReturnValue({
|
|
25
|
+
result: {
|
|
26
|
+
runtimeKind: 'pi-ai',
|
|
27
|
+
provider: 'test-provider',
|
|
28
|
+
model: 'test-model',
|
|
29
|
+
apiKeyEnv: 'TEST_API_KEY',
|
|
30
|
+
timeoutMs: 300_000,
|
|
31
|
+
agentId: 'main',
|
|
32
|
+
},
|
|
33
|
+
legacyWarnings: [],
|
|
34
|
+
configSource: '.pd/config.yaml',
|
|
35
|
+
configLoadResult: { ok: true, effective: {}, defaults: {}, legacyFilesDetected: [] },
|
|
36
|
+
});
|
|
37
|
+
return { mockResolveRuntimeFromPdConfig };
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
vi.mock('../../src/services/resolve-runtime-from-pd-config.js', () => ({
|
|
41
|
+
resolveRuntimeFromPdConfig: mockResolveRuntimeFromPdConfig,
|
|
42
|
+
}));
|
|
43
|
+
|
|
23
44
|
vi.mock('@principles/core/runtime-v2', () => ({
|
|
24
45
|
RuntimeStateManager: vi.fn().mockImplementation(function () {
|
|
25
46
|
return {
|
|
@@ -120,8 +141,9 @@ vi.mock('@principles/core/runtime-v2', () => ({
|
|
|
120
141
|
model: 'test-model',
|
|
121
142
|
apiKeyEnv: 'TEST_API_KEY',
|
|
122
143
|
}),
|
|
123
|
-
isRuntimeConfigError: vi.fn().
|
|
144
|
+
isRuntimeConfigError: vi.fn().mockImplementation((result: unknown) => result != null && typeof result === 'object' && Object.hasOwn(result, 'reason') && !Object.hasOwn(result, 'runtimeKind')),
|
|
124
145
|
validateRuntimeConfig: vi.fn(),
|
|
146
|
+
resolveRuntimeConfigFromPdConfig: vi.fn().mockReturnValue({ runtimeKind: 'pi-ai', provider: 'test-provider', model: 'test-model', apiKeyEnv: 'TEST_API_KEY', timeoutMs: 300_000, agentId: 'main' }),
|
|
125
147
|
resolveOutputLanguage: vi.fn().mockReturnValue({ outputLanguage: 'zh-CN' }),
|
|
126
148
|
}));
|
|
127
149
|
|
|
@@ -381,12 +403,16 @@ describe('handleRuntimeInternalizationRunOnce', () => {
|
|
|
381
403
|
});
|
|
382
404
|
|
|
383
405
|
it('--runtime openclaw-cli resolves OpenClawCliRuntimeAdapter', async () => {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
406
|
+
mockResolveRuntimeFromPdConfig.mockReturnValueOnce({
|
|
407
|
+
result: {
|
|
408
|
+
runtimeKind: 'openclaw-cli',
|
|
409
|
+
openclawMode: 'local',
|
|
410
|
+
timeoutMs: 300_000,
|
|
411
|
+
agentId: 'main',
|
|
412
|
+
},
|
|
413
|
+
legacyWarnings: [],
|
|
414
|
+
configSource: '.pd/config.yaml',
|
|
415
|
+
configLoadResult: { ok: true, effective: {}, defaults: {}, legacyFilesDetected: [] },
|
|
390
416
|
});
|
|
391
417
|
|
|
392
418
|
mockWakeOnce.mockResolvedValue({
|
|
@@ -412,31 +438,7 @@ describe('handleRuntimeInternalizationRunOnce', () => {
|
|
|
412
438
|
expect(OpenClawMock).toHaveBeenCalled();
|
|
413
439
|
});
|
|
414
440
|
|
|
415
|
-
it('--runtime config resolves adapter from
|
|
416
|
-
mockWakeOnce.mockResolvedValue({
|
|
417
|
-
decision: 'would_lease',
|
|
418
|
-
taskId: 'task-dreamer-008',
|
|
419
|
-
taskKind: 'dreamer',
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
mockRun.mockResolvedValue({
|
|
423
|
-
status: 'succeeded',
|
|
424
|
-
taskId: 'task-dreamer-008',
|
|
425
|
-
runId: 'run-008',
|
|
426
|
-
artifactId: 'pi-art-task-dreamer-008-run-008',
|
|
427
|
-
resultRef: 'dreamer://run-008',
|
|
428
|
-
attemptCount: 1,
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
await handleRuntimeInternalizationRunOnce({ workspace: WS, runtime: 'config', json: true });
|
|
432
|
-
|
|
433
|
-
const ResolveConfigMock = vi.mocked(
|
|
434
|
-
await import('@principles/core/runtime-v2').then(m => m.resolveRuntimeConfig),
|
|
435
|
-
);
|
|
436
|
-
expect(ResolveConfigMock).toHaveBeenCalled();
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
it('--runtime config reads from workspaceDir/.state (not .pd)', async () => {
|
|
441
|
+
it('--runtime config resolves adapter from .pd/config.yaml with workspace path (PRI-393)', async () => {
|
|
440
442
|
mockWakeOnce.mockResolvedValue({
|
|
441
443
|
decision: 'would_lease',
|
|
442
444
|
taskId: 'task-dreamer-009',
|
|
@@ -455,12 +457,10 @@ describe('handleRuntimeInternalizationRunOnce', () => {
|
|
|
455
457
|
const customWs = '/tmp/test-workspace';
|
|
456
458
|
await handleRuntimeInternalizationRunOnce({ workspace: customWs, runtime: 'config', json: true });
|
|
457
459
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
+
// PRI-393: verify resolveRuntimeFromPdConfig was called with workspace dir
|
|
461
|
+
expect(mockResolveRuntimeFromPdConfig).toHaveBeenCalledWith(
|
|
462
|
+
expect.stringContaining('test-workspace'),
|
|
460
463
|
);
|
|
461
|
-
const resolvedWorkspace = path.resolve(customWs);
|
|
462
|
-
const expectedStateDir = path.join(resolvedWorkspace, '.state');
|
|
463
|
-
expect(ResolveConfigMock).toHaveBeenCalledWith(expectedStateDir, { requestedRuntimeKind: 'config' });
|
|
464
464
|
});
|
|
465
465
|
|
|
466
466
|
it('--runner philosopher dispatches PhilosopherRunner', async () => {
|
|
@@ -810,7 +810,7 @@ describe('handleRuntimeInternalizationRunOnce', () => {
|
|
|
810
810
|
expect(output.timeoutSource).toBe('runner_poll');
|
|
811
811
|
});
|
|
812
812
|
|
|
813
|
-
it('--timeout-ms overrides
|
|
813
|
+
it('--timeout-ms overrides .pd/config.yaml timeoutMs for PiAiRuntimeAdapter (PRI-393)', async () => {
|
|
814
814
|
mockWakeOnce.mockResolvedValue({
|
|
815
815
|
decision: 'would_lease',
|
|
816
816
|
taskId: 'task-dreamer-ov',
|
|
@@ -1258,14 +1258,17 @@ describe('handleRuntimeInternalizationRunOnce', () => {
|
|
|
1258
1258
|
});
|
|
1259
1259
|
|
|
1260
1260
|
it('--runtime config with missing config outputs structured JSON error', async () => {
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1261
|
+
// PRI-393: mock resolveRuntimeFromPdConfig to return config error
|
|
1262
|
+
mockResolveRuntimeFromPdConfig.mockReturnValueOnce({
|
|
1263
|
+
result: {
|
|
1264
|
+
reason: 'explicit_config_missing',
|
|
1265
|
+
message: 'runtime=config requested but no .pd/config.yaml runtime binding found',
|
|
1266
|
+
nextAction: 'Add runtime binding to .pd/config.yaml',
|
|
1267
|
+
},
|
|
1268
|
+
legacyWarnings: [],
|
|
1269
|
+
configSource: '.pd/config.yaml',
|
|
1270
|
+
configLoadResult: { ok: false, effective: {}, defaults: {}, legacyFilesDetected: [] },
|
|
1267
1271
|
});
|
|
1268
|
-
vi.mocked(isRuntimeConfigError).mockReturnValue(true);
|
|
1269
1272
|
|
|
1270
1273
|
mockWakeOnce.mockResolvedValue({
|
|
1271
1274
|
decision: 'would_lease',
|
|
@@ -1283,14 +1286,17 @@ describe('handleRuntimeInternalizationRunOnce', () => {
|
|
|
1283
1286
|
});
|
|
1284
1287
|
|
|
1285
1288
|
it('--runtime config with missing config outputs text error', async () => {
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1289
|
+
// PRI-393: mock resolveRuntimeFromPdConfig to return config error
|
|
1290
|
+
mockResolveRuntimeFromPdConfig.mockReturnValueOnce({
|
|
1291
|
+
result: {
|
|
1292
|
+
reason: 'explicit_config_missing',
|
|
1293
|
+
message: 'runtime=config requested but no .pd/config.yaml runtime binding found',
|
|
1294
|
+
nextAction: 'Add runtime binding to .pd/config.yaml',
|
|
1295
|
+
},
|
|
1296
|
+
legacyWarnings: [],
|
|
1297
|
+
configSource: '.pd/config.yaml',
|
|
1298
|
+
configLoadResult: { ok: false, effective: {}, defaults: {}, legacyFilesDetected: [] },
|
|
1299
|
+
});
|
|
1294
1300
|
|
|
1295
1301
|
mockWakeOnce.mockResolvedValue({
|
|
1296
1302
|
decision: 'would_lease',
|
|
@@ -1,7 +1,36 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { handleRuntimeProbe, type RuntimeProbeOptions } from '../../src/commands/runtime.js';
|
|
3
3
|
|
|
4
|
-
// Mock
|
|
4
|
+
// Mock resolveRuntimeWithOverrides
|
|
5
|
+
const { mockResolveRuntimeWithOverrides } = vi.hoisted(() => {
|
|
6
|
+
const fn = vi.fn().mockReturnValue({
|
|
7
|
+
result: {
|
|
8
|
+
runtimeKind: 'pi-ai',
|
|
9
|
+
provider: 'test-provider',
|
|
10
|
+
model: 'test-model',
|
|
11
|
+
apiKeyEnv: 'TEST_API_KEY',
|
|
12
|
+
maxRetries: 2,
|
|
13
|
+
timeoutMs: 180_000,
|
|
14
|
+
},
|
|
15
|
+
mergedConfig: {
|
|
16
|
+
runtimeKind: 'pi-ai',
|
|
17
|
+
provider: 'test-provider',
|
|
18
|
+
model: 'test-model',
|
|
19
|
+
apiKeyEnv: 'TEST_API_KEY',
|
|
20
|
+
maxRetries: 2,
|
|
21
|
+
timeoutMs: 180_000,
|
|
22
|
+
},
|
|
23
|
+
legacyWarnings: [],
|
|
24
|
+
configSource: '.pd/config.yaml',
|
|
25
|
+
configLoadResult: { ok: true, effective: {}, defaults: {}, legacyFilesDetected: [] },
|
|
26
|
+
});
|
|
27
|
+
return { mockResolveRuntimeWithOverrides: fn };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
vi.mock('../../src/services/resolve-runtime-from-pd-config.js', () => ({
|
|
31
|
+
resolveRuntimeWithOverrides: mockResolveRuntimeWithOverrides,
|
|
32
|
+
}));
|
|
33
|
+
|
|
5
34
|
vi.mock('@principles/core/runtime-v2', () => ({
|
|
6
35
|
probeRuntime: vi.fn().mockResolvedValue({
|
|
7
36
|
runtimeKind: 'openclaw-cli',
|
|
@@ -260,4 +289,98 @@ describe('pd runtime probe', () => {
|
|
|
260
289
|
consoleErrorSpy.mockRestore();
|
|
261
290
|
exitSpy.mockRestore();
|
|
262
291
|
});
|
|
292
|
+
|
|
293
|
+
// ── pi-ai probe: maxRetries backfill from .pd/config.yaml ───────────────
|
|
294
|
+
it('PRI-393: pi-ai probe reads maxRetries from .pd/config.yaml when --maxRetries not passed', async () => {
|
|
295
|
+
process.env.TEST_API_KEY = 'test-value';
|
|
296
|
+
const { probeRuntime } = await import('@principles/core/runtime-v2');
|
|
297
|
+
vi.mocked(probeRuntime).mockResolvedValue({
|
|
298
|
+
runtimeKind: 'pi-ai',
|
|
299
|
+
provider: 'test-provider',
|
|
300
|
+
model: 'test-model',
|
|
301
|
+
health: { healthy: true, degraded: false, warnings: [], lastCheckedAt: '2026-06-14T00:00:00.000Z' },
|
|
302
|
+
capabilities: {},
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
306
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
307
|
+
|
|
308
|
+
// mockResolveRuntimeWithOverrides returns maxRetries: 2
|
|
309
|
+
// No --maxRetries CLI flag
|
|
310
|
+
await handleRuntimeProbe({
|
|
311
|
+
runtime: 'pi-ai',
|
|
312
|
+
provider: 'test-provider',
|
|
313
|
+
model: 'test-model',
|
|
314
|
+
apiKeyEnv: 'TEST_API_KEY',
|
|
315
|
+
workspace: '/tmp/ws',
|
|
316
|
+
json: true,
|
|
317
|
+
} as RuntimeProbeOptions);
|
|
318
|
+
|
|
319
|
+
expect(vi.mocked(probeRuntime)).toHaveBeenCalledWith(
|
|
320
|
+
expect.objectContaining({ maxRetries: 2 }),
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
delete process.env.TEST_API_KEY;
|
|
324
|
+
consoleSpy.mockRestore();
|
|
325
|
+
exitSpy.mockRestore();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('PRI-393: CLI --maxRetries overrides .pd/config.yaml maxRetries', async () => {
|
|
329
|
+
process.env.TEST_API_KEY = 'test-value';
|
|
330
|
+
const { probeRuntime } = await import('@principles/core/runtime-v2');
|
|
331
|
+
vi.mocked(probeRuntime).mockResolvedValue({
|
|
332
|
+
runtimeKind: 'pi-ai',
|
|
333
|
+
provider: 'test-provider',
|
|
334
|
+
model: 'test-model',
|
|
335
|
+
health: { healthy: true, degraded: false, warnings: [], lastCheckedAt: '2026-06-14T00:00:00.000Z' },
|
|
336
|
+
capabilities: {},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
340
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
341
|
+
|
|
342
|
+
await handleRuntimeProbe({
|
|
343
|
+
runtime: 'pi-ai',
|
|
344
|
+
provider: 'test-provider',
|
|
345
|
+
model: 'test-model',
|
|
346
|
+
apiKeyEnv: 'TEST_API_KEY',
|
|
347
|
+
maxRetries: 5,
|
|
348
|
+
workspace: '/tmp/ws',
|
|
349
|
+
json: true,
|
|
350
|
+
} as RuntimeProbeOptions);
|
|
351
|
+
|
|
352
|
+
expect(vi.mocked(probeRuntime)).toHaveBeenCalledWith(
|
|
353
|
+
expect.objectContaining({ maxRetries: 5 }),
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
delete process.env.TEST_API_KEY;
|
|
357
|
+
consoleSpy.mockRestore();
|
|
358
|
+
exitSpy.mockRestore();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('PRI-393: env var missing → process.exit(1) and does NOT call probeRuntime', async () => {
|
|
362
|
+
const { probeRuntime } = await import('@principles/core/runtime-v2');
|
|
363
|
+
vi.mocked(probeRuntime).mockClear();
|
|
364
|
+
|
|
365
|
+
// Ensure NONEXISTENT_VAR is NOT set
|
|
366
|
+
delete process.env.NONEXISTENT_VAR;
|
|
367
|
+
|
|
368
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
369
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
370
|
+
|
|
371
|
+
await handleRuntimeProbe({
|
|
372
|
+
runtime: 'pi-ai',
|
|
373
|
+
provider: 'test-provider',
|
|
374
|
+
model: 'test-model',
|
|
375
|
+
apiKeyEnv: 'NONEXISTENT_VAR',
|
|
376
|
+
json: true,
|
|
377
|
+
} as RuntimeProbeOptions);
|
|
378
|
+
|
|
379
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('NONEXISTENT_VAR'));
|
|
380
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
381
|
+
expect(vi.mocked(probeRuntime)).not.toHaveBeenCalled();
|
|
382
|
+
|
|
383
|
+
consoleErrorSpy.mockRestore();
|
|
384
|
+
exitSpy.mockRestore();
|
|
385
|
+
});
|
|
263
386
|
});
|
|
@@ -30,6 +30,7 @@ import { MalformedRunError } from '@principles/core';
|
|
|
30
30
|
describe('pd task show command handler', () => {
|
|
31
31
|
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
|
|
32
32
|
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
|
33
|
+
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
|
|
33
34
|
let processExitSpy: ReturnType<typeof vi.spyOn>;
|
|
34
35
|
|
|
35
36
|
beforeEach(() => {
|
|
@@ -55,6 +56,7 @@ describe('pd task show command handler', () => {
|
|
|
55
56
|
process.exitCode = 0;
|
|
56
57
|
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
57
58
|
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
59
|
+
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
58
60
|
processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
|
|
59
61
|
});
|
|
60
62
|
|
|
@@ -111,8 +113,11 @@ describe('pd task show command handler', () => {
|
|
|
111
113
|
runs: [{ runId: 'run-valid' }],
|
|
112
114
|
degradedRuns: [{ runId: 'run-bad', error: 'runtimeKind missing' }],
|
|
113
115
|
reason: expect.stringContaining('Malformed schema'),
|
|
114
|
-
nextAction:
|
|
116
|
+
// Honest nextAction: must NOT promise an auto-repair that doesn't exist,
|
|
117
|
+
// and must point at the real quarantine command (integrity-repair --confirm).
|
|
118
|
+
nextAction: expect.stringContaining('not auto-repaired'),
|
|
115
119
|
});
|
|
120
|
+
expect(output.nextAction).toContain('integrity-repair --confirm');
|
|
116
121
|
expect(process.exitCode).toBe(1);
|
|
117
122
|
expect(processExitSpy).not.toHaveBeenCalled();
|
|
118
123
|
});
|
|
@@ -139,6 +144,9 @@ describe('pd task show command handler', () => {
|
|
|
139
144
|
|
|
140
145
|
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
141
146
|
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Task: task-123'));
|
|
147
|
+
// The text-mode warning must also carry the honest nextAction.
|
|
148
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('not auto-repaired'));
|
|
149
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('integrity-repair --confirm'));
|
|
142
150
|
expect(process.exitCode).toBe(1);
|
|
143
151
|
expect(processExitSpy).not.toHaveBeenCalled();
|
|
144
152
|
});
|