@principles/pd-cli 1.147.11 → 1.147.13

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.
@@ -191,8 +191,10 @@ function evaluatorNeedsRevision(taskId: string, artificerArtifactId: string): un
191
191
  let tmpDir = '';
192
192
 
193
193
  function makeTmpDir(): string {
194
- const dir = path.join(os.tmpdir(), `pd-pipe-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
195
- fs.mkdirSync(dir, { recursive: true });
194
+ // mkdtempSync's random suffix keeps the path unpredictable (CodeQL
195
+ // js/insecure-temporary-file: the pipeline language tests WRITE
196
+ // .pd/config.yaml under this dir).
197
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-pipe-'));
196
198
  // PRI-661: the pipeline's evaluator replay resolves the production gate
197
199
  // context from durable workspace provenance — seed a declaration like every
198
200
  // real host does on startup.
@@ -642,3 +644,131 @@ describe('runRuleHostPipeline (PRI-429) — atomic capability + exact pain match
642
644
  expect(dreamerStage?.reason).toBeTruthy();
643
645
  }, 60_000);
644
646
  });
647
+
648
+ // ── PRI-714 (review fix #3): language wiring ─────────────────────────────────
649
+ //
650
+ // The PR's original fault class was "the parameter exists but is not wired".
651
+ // These regressions drive the REAL pipeline and assert the LANGUAGE DIRECTIVE
652
+ // on the actual inputPayload handed to adapter.startRun:
653
+ // - explicit `principles.outputLanguage: 'en'` in the workspace config
654
+ // overrides the zh-CN default;
655
+ // - no config → the zh-CN default applies.
656
+ // Deleting the pipeline's language resolution makes these fail.
657
+
658
+ function writeLanguageConfig(dir: string, outputLanguage?: 'en'): void {
659
+ fs.mkdirSync(path.join(dir, '.pd'), { recursive: true });
660
+ // Full valid-config shape (mirrors pd-config-loader.test.ts) so validation
661
+ // keeps `principles` intact instead of degrading to defaults.
662
+ const config: Record<string, unknown> = {
663
+ version: 1,
664
+ features: {
665
+ prompt: { category: 'core', enabled: true },
666
+ code_tool_hook: { category: 'core', enabled: true },
667
+ defer_archive: { category: 'core', enabled: true },
668
+ correction_observer: { category: 'quiet', enabled: false },
669
+ empathy_observer: { category: 'quiet', enabled: false },
670
+ },
671
+ runtimeProfiles: {
672
+ 'openclaw.default': { type: 'openclaw', source: 'default' },
673
+ },
674
+ internalAgents: {
675
+ defaultRuntime: 'openclaw.default',
676
+ agents: {
677
+ diagnostician: { enabled: true, runtimeProfile: 'openclaw.default' },
678
+ dreamer: { enabled: true },
679
+ scribe: { enabled: true },
680
+ artificer: { enabled: true },
681
+ philosopher: { enabled: false },
682
+ evaluator: { enabled: false },
683
+ rolloutReviewer: { enabled: false },
684
+ correctionObserver: { enabled: false },
685
+ empathyObserver: { enabled: false },
686
+ },
687
+ },
688
+ ui: { diagnostics: { mode: 'simple' } },
689
+ };
690
+ if (outputLanguage !== undefined) config.principles = { outputLanguage };
691
+ // JSON is valid YAML — same trick the shared executor tests use.
692
+ fs.writeFileSync(path.join(dir, '.pd', 'config.yaml'), JSON.stringify(config));
693
+ }
694
+
695
+ function collectStartRunPayloads(adapter: ScriptedAdapter): string[] {
696
+ const payloads: string[] = [];
697
+ for (const input of adapter.startRunInputs.values()) {
698
+ if (typeof input.inputPayload === 'string') payloads.push(input.inputPayload);
699
+ }
700
+ return payloads;
701
+ }
702
+
703
+ describe('runRuleHostPipeline — outputLanguage reaches adapter.startRun messages (PRI-714 review fix)', () => {
704
+ afterEach(() => {
705
+ vi.restoreAllMocks();
706
+ if (tmpDir) { try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch { /* ignore */ } tmpDir = ''; }
707
+ });
708
+
709
+ it('explicit principles.outputLanguage=en overrides the zh-CN default on stage messages', async () => {
710
+ tmpDir = makeTmpDir();
711
+ writeLanguageConfig(tmpDir, 'en');
712
+ const sm = new RuntimeStateManager({ workspaceDir: tmpDir });
713
+ await sm.initialize();
714
+ await seedDreamerWithId(sm, 'dreamer-lang-en-001', 'pain-lang-en');
715
+ await sm.close();
716
+
717
+ const adapter = makeAdapter();
718
+ const result = await runRuleHostPipeline({
719
+ workspaceDir: tmpDir, painId: 'pain-lang-en', runtimeAdapter: adapter,
720
+ channel: 'code_tool_hook', pollIntervalMs: 5, timeoutMs: 1000,
721
+ codeRuleCapability: { enabled: true, artificerAdapter: adapter },
722
+ onStoreReady: (store) => { adapter.artifactStore = store; },
723
+ });
724
+ expect(result.decision, JSON.stringify(result)).toBe('candidate_ready_for_owner_review');
725
+
726
+ const payloads = collectStartRunPayloads(adapter);
727
+ // Dreamer: dreamer-subject field list, English directive on the wire.
728
+ const dreamerMsg = payloads.find((p) => p.includes('dreamerInstruction') && p.includes('dreamer-lang-en-001'));
729
+ expect(dreamerMsg).toBeDefined();
730
+ expect(dreamerMsg).toContain('LANGUAGE DIRECTIVE');
731
+ expect(dreamerMsg).toContain('English');
732
+ expect(dreamerMsg).toContain('(candidates[].badDecision, candidates[].betterDecision, candidates[].rationale, candidates[].strategicPerspective)');
733
+ // Philosopher: philosopher-subject field list, English directive.
734
+ const philosopherMsg = payloads.find((p) => p.includes('philosopherInstruction'));
735
+ expect(philosopherMsg).toBeDefined();
736
+ expect(philosopherMsg).toContain('LANGUAGE DIRECTIVE');
737
+ expect(philosopherMsg).toContain('English');
738
+ expect(philosopherMsg).toContain('(thesis, principleCandidate.title, principleCandidate.rationale, principleCandidate.scope, risks[])');
739
+ // Evaluator: review-subject explicit nested paths + PRI-630 ledger echo rule.
740
+ const evaluatorMsg = payloads.find((p) => p.includes('evaluatorInstruction'));
741
+ expect(evaluatorMsg).toBeDefined();
742
+ expect(evaluatorMsg).toContain('LANGUAGE DIRECTIVE');
743
+ expect(evaluatorMsg).toContain('English');
744
+ expect(evaluatorMsg).toContain('codeReview.traceCoverage.gaps');
745
+ expect(evaluatorMsg).toContain('adversarialCases[].rationale');
746
+ expect(evaluatorMsg).toContain('requirementLedger[].statement MUST be copied verbatim');
747
+ // The explicit en must have fully replaced the zh-CN default everywhere.
748
+ for (const p of payloads) {
749
+ expect(p, 'Simplified Chinese leaked into an en-configured pipeline').not.toContain('Simplified Chinese');
750
+ }
751
+ }, 60_000);
752
+
753
+ it('no config file resolves the zh-CN default on the dreamer message', async () => {
754
+ tmpDir = makeTmpDir();
755
+ const sm = new RuntimeStateManager({ workspaceDir: tmpDir });
756
+ await sm.initialize();
757
+ await seedDreamerWithId(sm, 'dreamer-lang-zh-001', 'pain-lang-zh');
758
+ await sm.close();
759
+
760
+ const adapter = makeAdapter();
761
+ const result = await runRuleHostPipeline({
762
+ workspaceDir: tmpDir, painId: 'pain-lang-zh', runtimeAdapter: adapter,
763
+ channel: 'code_tool_hook', pollIntervalMs: 5, timeoutMs: 1000,
764
+ codeRuleCapability: { enabled: true, artificerAdapter: adapter },
765
+ onStoreReady: (store) => { adapter.artifactStore = store; },
766
+ });
767
+ expect(result.decision, JSON.stringify(result)).toBe('candidate_ready_for_owner_review');
768
+
769
+ const dreamerMsg = collectStartRunPayloads(adapter).find((p) => p.includes('dreamer-lang-zh-001'));
770
+ expect(dreamerMsg).toBeDefined();
771
+ expect(dreamerMsg).toContain('LANGUAGE DIRECTIVE');
772
+ expect(dreamerMsg).toContain('Simplified Chinese');
773
+ }, 60_000);
774
+ });