@principles/pd-cli 1.118.0 → 1.120.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.
Files changed (48) hide show
  1. package/dist/commands/__tests__/legacy-cleanup.test.d.ts +18 -0
  2. package/dist/commands/__tests__/legacy-cleanup.test.d.ts.map +1 -0
  3. package/dist/commands/__tests__/legacy-cleanup.test.js +459 -0
  4. package/dist/commands/__tests__/legacy-cleanup.test.js.map +1 -0
  5. package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts +21 -0
  6. package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts.map +1 -0
  7. package/dist/commands/__tests__/rulecode-flag-wiring.test.js +179 -0
  8. package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +1 -0
  9. package/dist/commands/__tests__/rulecode-handler.test.d.ts +16 -0
  10. package/dist/commands/__tests__/rulecode-handler.test.d.ts.map +1 -0
  11. package/dist/commands/__tests__/rulecode-handler.test.js +285 -0
  12. package/dist/commands/__tests__/rulecode-handler.test.js.map +1 -0
  13. package/dist/commands/candidate.d.ts +1 -0
  14. package/dist/commands/candidate.d.ts.map +1 -1
  15. package/dist/commands/candidate.js +32 -6
  16. package/dist/commands/candidate.js.map +1 -1
  17. package/dist/commands/legacy-cleanup.d.ts +72 -6
  18. package/dist/commands/legacy-cleanup.d.ts.map +1 -1
  19. package/dist/commands/legacy-cleanup.js +243 -23
  20. package/dist/commands/legacy-cleanup.js.map +1 -1
  21. package/dist/commands/rulecode.d.ts +85 -0
  22. package/dist/commands/rulecode.d.ts.map +1 -0
  23. package/dist/commands/rulecode.js +356 -0
  24. package/dist/commands/rulecode.js.map +1 -0
  25. package/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
  26. package/dist/commands/runtime-internalization-run-rulehost.js +4 -7
  27. package/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
  28. package/dist/index.js +30 -9
  29. package/dist/index.js.map +1 -1
  30. package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  31. package/dist/services/rulehost-pipeline-runner.js +31 -15
  32. package/dist/services/rulehost-pipeline-runner.js.map +1 -1
  33. package/package.json +1 -1
  34. package/scripts/llm-dogfood.ts +8 -12
  35. package/src/commands/__tests__/legacy-cleanup.test.ts +596 -0
  36. package/src/commands/__tests__/rulecode-flag-wiring.test.ts +230 -0
  37. package/src/commands/__tests__/rulecode-handler.test.ts +369 -0
  38. package/src/commands/candidate.ts +29 -7
  39. package/src/commands/legacy-cleanup.ts +335 -27
  40. package/src/commands/rulecode.ts +434 -0
  41. package/src/commands/runtime-internalization-run-rulehost.ts +3 -8
  42. package/src/index.ts +31 -9
  43. package/src/services/rulehost-pipeline-runner.ts +36 -18
  44. package/tests/commands/candidate-internalize-lineage.test.ts +44 -0
  45. package/tests/commands/cli-command-tree.test.ts +40 -0
  46. package/tests/commands/runtime.test.ts +9 -3
  47. package/tests/e2e/cross-package-acceptance.test.ts +1 -1
  48. package/tests/services/rulehost-pipeline-runner.test.ts +86 -2
@@ -76,4 +76,44 @@ describe('CLI command tree structure', () => {
76
76
  const output = runPdHelp(['runtime', 'activation', '--help']);
77
77
  expect(output).toMatch(/edit\s/);
78
78
  });
79
+
80
+ it('rulecode command exists with spec/validate/replay subcommands (pd rulecode --help)', () => {
81
+ const output = runPdHelp(['rulecode', '--help']);
82
+ expect(output).toContain('spec');
83
+ expect(output).toContain('validate');
84
+ expect(output).toContain('replay');
85
+ });
86
+
87
+ it('rulecode spec subcommand has --json and --workspace (pd rulecode spec --help)', () => {
88
+ const output = runPdHelp(['rulecode', 'spec', '--help']);
89
+ expect(output).toContain('--json');
90
+ expect(output).toContain('--workspace');
91
+ });
92
+
93
+ it('rulecode validate subcommand has --code, --code-file, --json (pd rulecode validate --help)', () => {
94
+ const output = runPdHelp(['rulecode', 'validate', '--help']);
95
+ expect(output).toContain('--code');
96
+ expect(output).toContain('--code-file');
97
+ expect(output).toContain('--json');
98
+ });
99
+
100
+ it('rulecode replay subcommand has --golden-trace (required), --code, --json (pd rulecode replay --help)', () => {
101
+ const output = runPdHelp(['rulecode', 'replay', '--help']);
102
+ expect(output).toContain('--golden-trace');
103
+ expect(output).toContain('--code');
104
+ expect(output).toContain('--json');
105
+ });
106
+
107
+ it('legacy cleanup subcommand has --dry-run, --apply, --json (pd legacy cleanup --help)', () => {
108
+ const output = runPdHelp(['legacy', 'cleanup', '--help']);
109
+ expect(output).toContain('--dry-run');
110
+ expect(output).toContain('--apply');
111
+ expect(output).toContain('--json');
112
+ expect(output).toContain('--workspace');
113
+ });
114
+
115
+ it('legacy cleanup description mentions V1 Artificer artifacts', () => {
116
+ const output = runPdHelp(['legacy', 'cleanup', '--help']);
117
+ expect(output).toContain('V1 Artificer');
118
+ });
79
119
  });
@@ -365,7 +365,7 @@ describe('pd runtime probe', () => {
365
365
  // Ensure NONEXISTENT_VAR is NOT set
366
366
  delete process.env.NONEXISTENT_VAR;
367
367
 
368
- const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
368
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
369
369
  const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
370
370
 
371
371
  await handleRuntimeProbe({
@@ -376,11 +376,17 @@ describe('pd runtime probe', () => {
376
376
  json: true,
377
377
  } as RuntimeProbeOptions);
378
378
 
379
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('NONEXISTENT_VAR'));
379
+ const output = consoleSpy.mock.calls.map(([chunk]) => String(chunk)).join('');
380
+ expect(JSON.parse(output)).toEqual(expect.objectContaining({
381
+ status: 'failed',
382
+ reason: 'api_key_not_set',
383
+ message: expect.stringContaining('NONEXISTENT_VAR'),
384
+ nextAction: expect.any(String),
385
+ }));
380
386
  expect(exitSpy).toHaveBeenCalledWith(1);
381
387
  expect(vi.mocked(probeRuntime)).not.toHaveBeenCalled();
382
388
 
383
- consoleErrorSpy.mockRestore();
389
+ consoleSpy.mockRestore();
384
390
  exitSpy.mockRestore();
385
391
  });
386
392
  });
@@ -208,7 +208,7 @@ function makeTmpDir(): string {
208
208
 
209
209
  async function seedDreamerWithId(sm: RuntimeStateManager, taskId: string, painId: string): Promise<void> {
210
210
  const baseMetadata = JSON.parse(createPITaskDiagnosticJson({
211
- dependencyTaskIds: [], channel: 'prompt', timeoutMs: 1000, inputArtifactRefs: [], outputArtifactRefs: [],
211
+ dependencyTaskIds: [], channel: 'code_tool_hook', timeoutMs: 1000, inputArtifactRefs: [], outputArtifactRefs: [],
212
212
  })) as Record<string, unknown>;
213
213
  const diagnosticJson = JSON.stringify({ ...baseMetadata, sourcePainId: painId });
214
214
  await sm.createTask({ taskId, taskKind: 'dreamer', status: 'pending', attemptCount: 0, maxAttempts: 3, diagnosticJson });
@@ -197,9 +197,14 @@ function makeAdapter(opts?: { evaluator?: EvaluatorFactory }): ScriptedAdapter {
197
197
  * diagnosticJson (outside the pi_metadata envelope). This mirrors the pattern
198
198
  * from source-trace-locator.test.ts and PainSignalBridge.
199
199
  */
200
- async function seedDreamerWithId(sm: RuntimeStateManager, taskId: string, painId: string): Promise<void> {
200
+ async function seedDreamerWithId(
201
+ sm: RuntimeStateManager,
202
+ taskId: string,
203
+ painId: string,
204
+ channel: 'prompt' | 'code_tool_hook' | 'defer_archive' = 'code_tool_hook',
205
+ ): Promise<void> {
201
206
  const baseMetadata = JSON.parse(createPITaskDiagnosticJson({
202
- dependencyTaskIds: [], channel: 'prompt', timeoutMs: 1000, inputArtifactRefs: [], outputArtifactRefs: [],
207
+ dependencyTaskIds: [], channel, timeoutMs: 1000, inputArtifactRefs: [], outputArtifactRefs: [],
203
208
  })) as Record<string, unknown>;
204
209
  const diagnosticJson = JSON.stringify({ ...baseMetadata, sourcePainId: painId });
205
210
  await sm.createTask({ taskId, taskKind: 'dreamer', status: 'pending', attemptCount: 0, maxAttempts: 3, diagnosticJson });
@@ -441,6 +446,85 @@ describe('runRuleHostPipeline (PRI-429) — atomic capability + exact pain match
441
446
  expect(result.stages[0]?.status).toBe('failed');
442
447
  }, 60_000);
443
448
 
449
+ it('selects the Dreamer task matching the requested activation channel', async () => {
450
+ tmpDir = makeTmpDir();
451
+ const sm = new RuntimeStateManager({ workspaceDir: tmpDir });
452
+ await sm.initialize();
453
+ await seedDreamerWithId(sm, 'dreamer-prompt', 'pain-multi-channel', 'prompt');
454
+ await seedDreamerWithId(sm, 'dreamer-code-hook', 'pain-multi-channel', 'code_tool_hook');
455
+ await sm.close();
456
+
457
+ const adapter = makeAdapter();
458
+ const result = await runRuleHostPipeline({
459
+ workspaceDir: tmpDir,
460
+ painId: 'pain-multi-channel',
461
+ runtimeAdapter: adapter,
462
+ channel: 'code_tool_hook',
463
+ pollIntervalMs: 5,
464
+ timeoutMs: 1000,
465
+ onStoreReady: (store) => { adapter.artifactStore = store; },
466
+ });
467
+
468
+ expect(result.stages[0]).toMatchObject({
469
+ name: 'pain_lookup',
470
+ status: 'succeeded',
471
+ taskId: 'dreamer-code-hook',
472
+ });
473
+ }, 60_000);
474
+
475
+ it('uses the sole pain-linked Dreamer seed when its source channel differs from the target channel', async () => {
476
+ tmpDir = makeTmpDir();
477
+ const sm = new RuntimeStateManager({ workspaceDir: tmpDir });
478
+ await sm.initialize();
479
+ await seedDreamerWithId(sm, 'dreamer-prompt-seed', 'pain-cross-channel', 'prompt');
480
+ await sm.close();
481
+
482
+ const adapter = makeAdapter();
483
+ const result = await runRuleHostPipeline({
484
+ workspaceDir: tmpDir,
485
+ painId: 'pain-cross-channel',
486
+ runtimeAdapter: adapter,
487
+ channel: 'code_tool_hook',
488
+ pollIntervalMs: 5,
489
+ timeoutMs: 1000,
490
+ onStoreReady: (store) => { adapter.artifactStore = store; },
491
+ });
492
+
493
+ expect(result.stages[0]).toMatchObject({
494
+ name: 'pain_lookup',
495
+ status: 'succeeded',
496
+ taskId: 'dreamer-prompt-seed',
497
+ });
498
+ }, 60_000);
499
+
500
+ it('resumes from a succeeded Dreamer seed instead of trying to lease it again', async () => {
501
+ tmpDir = makeTmpDir();
502
+ const sm = new RuntimeStateManager({ workspaceDir: tmpDir });
503
+ await sm.initialize();
504
+ await seedDreamerWithId(sm, 'dreamer-resume', 'pain-resume');
505
+ await sm.close();
506
+
507
+ const firstAdapter = makeAdapter();
508
+ await runRuleHostPipeline({
509
+ workspaceDir: tmpDir, painId: 'pain-resume', runtimeAdapter: firstAdapter,
510
+ pollIntervalMs: 5, timeoutMs: 1000,
511
+ onStoreReady: (store) => { firstAdapter.artifactStore = store; },
512
+ });
513
+
514
+ const resumedAdapter = makeAdapter();
515
+ const resumed = await runRuleHostPipeline({
516
+ workspaceDir: tmpDir, painId: 'pain-resume', runtimeAdapter: resumedAdapter,
517
+ pollIntervalMs: 5, timeoutMs: 1000,
518
+ onStoreReady: (store) => { resumedAdapter.artifactStore = store; },
519
+ });
520
+
521
+ expect(resumed.stages.slice(0, 2)).toEqual([
522
+ { name: 'pain_lookup', status: 'succeeded', taskId: 'dreamer-resume' },
523
+ { name: 'dreamer', taskId: 'dreamer-resume', status: 'succeeded' },
524
+ ]);
525
+ expect(resumed.degradationReason).not.toContain('lease');
526
+ }, 60_000);
527
+
444
528
  // ── Test 8 (E fix): retried status is NOT terminal — bounded retry succeeds ──
445
529
  it('retried status triggers bounded retry and eventually succeeds (E fix)', async () => {
446
530
  tmpDir = makeTmpDir();