@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.
Files changed (215) hide show
  1. package/dist/commands/__tests__/intent-flag-wiring.test.js +74 -3
  2. package/dist/commands/__tests__/intent-flag-wiring.test.js.map +1 -1
  3. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts +2 -0
  4. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts.map +1 -0
  5. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js +60 -0
  6. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js.map +1 -0
  7. package/dist/commands/__tests__/rulecode-flag-wiring.test.js +11 -4
  8. package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +1 -1
  9. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +9 -0
  10. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +1 -1
  11. package/dist/commands/admission-gate.d.ts +24 -0
  12. package/dist/commands/admission-gate.d.ts.map +1 -0
  13. package/dist/commands/admission-gate.js +29 -0
  14. package/dist/commands/admission-gate.js.map +1 -0
  15. package/dist/commands/candidate.d.ts.map +1 -1
  16. package/dist/commands/candidate.js +72 -6
  17. package/dist/commands/candidate.js.map +1 -1
  18. package/dist/commands/console.d.ts.map +1 -1
  19. package/dist/commands/console.js +42 -0
  20. package/dist/commands/console.js.map +1 -1
  21. package/dist/commands/demo-story-a.d.ts.map +1 -1
  22. package/dist/commands/demo-story-a.js +4 -2
  23. package/dist/commands/demo-story-a.js.map +1 -1
  24. package/dist/commands/diagnose.d.ts.map +1 -1
  25. package/dist/commands/diagnose.js +155 -8
  26. package/dist/commands/diagnose.js.map +1 -1
  27. package/dist/commands/errors-list.d.ts +25 -0
  28. package/dist/commands/errors-list.d.ts.map +1 -0
  29. package/dist/commands/errors-list.js +368 -0
  30. package/dist/commands/errors-list.js.map +1 -0
  31. package/dist/commands/health-codex.d.ts +7 -0
  32. package/dist/commands/health-codex.d.ts.map +1 -0
  33. package/dist/commands/health-codex.js +209 -0
  34. package/dist/commands/health-codex.js.map +1 -0
  35. package/dist/commands/health.d.ts +24 -0
  36. package/dist/commands/health.d.ts.map +1 -1
  37. package/dist/commands/health.js +48 -0
  38. package/dist/commands/health.js.map +1 -1
  39. package/dist/commands/intent.d.ts +21 -4
  40. package/dist/commands/intent.d.ts.map +1 -1
  41. package/dist/commands/intent.js +88 -27
  42. package/dist/commands/intent.js.map +1 -1
  43. package/dist/commands/pain-evidence.d.ts +6 -0
  44. package/dist/commands/pain-evidence.d.ts.map +1 -1
  45. package/dist/commands/pain-evidence.js +6 -0
  46. package/dist/commands/pain-evidence.js.map +1 -1
  47. package/dist/commands/pain-record.d.ts.map +1 -1
  48. package/dist/commands/pain-record.js +21 -0
  49. package/dist/commands/pain-record.js.map +1 -1
  50. package/dist/commands/pain-retry.d.ts +4 -0
  51. package/dist/commands/pain-retry.d.ts.map +1 -1
  52. package/dist/commands/pain-retry.js +327 -32
  53. package/dist/commands/pain-retry.js.map +1 -1
  54. package/dist/commands/proven-channel-baseline.d.ts.map +1 -1
  55. package/dist/commands/proven-channel-baseline.js +4 -2
  56. package/dist/commands/proven-channel-baseline.js.map +1 -1
  57. package/dist/commands/runtime-activation.d.ts +30 -0
  58. package/dist/commands/runtime-activation.d.ts.map +1 -1
  59. package/dist/commands/runtime-activation.js +567 -11
  60. package/dist/commands/runtime-activation.js.map +1 -1
  61. package/dist/commands/runtime-canary.d.ts.map +1 -1
  62. package/dist/commands/runtime-canary.js +32 -7
  63. package/dist/commands/runtime-canary.js.map +1 -1
  64. package/dist/commands/runtime-health-snapshot.d.ts.map +1 -1
  65. package/dist/commands/runtime-health-snapshot.js +4 -2
  66. package/dist/commands/runtime-health-snapshot.js.map +1 -1
  67. package/dist/commands/runtime-init.d.ts +43 -0
  68. package/dist/commands/runtime-init.d.ts.map +1 -0
  69. package/dist/commands/runtime-init.js +264 -0
  70. package/dist/commands/runtime-init.js.map +1 -0
  71. package/dist/commands/runtime-internalization-context-trace.d.ts +25 -0
  72. package/dist/commands/runtime-internalization-context-trace.d.ts.map +1 -0
  73. package/dist/commands/runtime-internalization-context-trace.js +374 -0
  74. package/dist/commands/runtime-internalization-context-trace.js.map +1 -0
  75. package/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -1
  76. package/dist/commands/runtime-internalization-enqueue-successors.js +3 -1
  77. package/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -1
  78. package/dist/commands/runtime-internalization-integrity.d.ts.map +1 -1
  79. package/dist/commands/runtime-internalization-integrity.js +4 -2
  80. package/dist/commands/runtime-internalization-integrity.js.map +1 -1
  81. package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
  82. package/dist/commands/runtime-internalization-run-once.js +20 -5
  83. package/dist/commands/runtime-internalization-run-once.js.map +1 -1
  84. package/dist/commands/runtime-internalization-run-rulehost.d.ts +1 -0
  85. package/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
  86. package/dist/commands/runtime-internalization-run-rulehost.js +149 -6
  87. package/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
  88. package/dist/commands/runtime-uat.d.ts.map +1 -1
  89. package/dist/commands/runtime-uat.guard.test.js +13 -15
  90. package/dist/commands/runtime-uat.guard.test.js.map +1 -1
  91. package/dist/commands/runtime-uat.js +4 -3
  92. package/dist/commands/runtime-uat.js.map +1 -1
  93. package/dist/config-reader.js +1 -1
  94. package/dist/config-reader.js.map +1 -1
  95. package/dist/index.js +54 -78
  96. package/dist/index.js.map +1 -1
  97. package/dist/services/__tests__/evaluator-runner-deps.test.d.ts +2 -0
  98. package/dist/services/__tests__/evaluator-runner-deps.test.d.ts.map +1 -0
  99. package/dist/services/__tests__/evaluator-runner-deps.test.js +304 -0
  100. package/dist/services/__tests__/evaluator-runner-deps.test.js.map +1 -0
  101. package/dist/services/__tests__/rulehost-readiness.test.js.map +1 -1
  102. package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +1 -1
  103. package/dist/services/config-doctor.d.ts.map +1 -1
  104. package/dist/services/config-doctor.js +9 -2
  105. package/dist/services/config-doctor.js.map +1 -1
  106. package/dist/services/console-launcher.d.ts +6 -0
  107. package/dist/services/console-launcher.d.ts.map +1 -1
  108. package/dist/services/console-launcher.js.map +1 -1
  109. package/dist/services/pain-flood-simulation-runner.d.ts.map +1 -1
  110. package/dist/services/pain-flood-simulation-runner.js +9 -0
  111. package/dist/services/pain-flood-simulation-runner.js.map +1 -1
  112. package/dist/services/pd-config-loader.js +2 -2
  113. package/dist/services/pd-config-loader.js.map +1 -1
  114. package/dist/services/proven-channel-baseline-runner.d.ts.map +1 -1
  115. package/dist/services/proven-channel-baseline-runner.js +10 -2
  116. package/dist/services/proven-channel-baseline-runner.js.map +1 -1
  117. package/dist/services/quality-scorecard/data-extractor.js.map +1 -1
  118. package/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
  119. package/dist/services/rulehost-pipeline-runner.d.ts +77 -1
  120. package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  121. package/dist/services/rulehost-pipeline-runner.js +111 -7
  122. package/dist/services/rulehost-pipeline-runner.js.map +1 -1
  123. package/dist/services/rulehost-readiness.js.map +1 -1
  124. package/dist/services/runtime-adapter-resolver.d.ts +1 -0
  125. package/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
  126. package/dist/services/runtime-adapter-resolver.js +3 -0
  127. package/dist/services/runtime-adapter-resolver.js.map +1 -1
  128. package/dist/services/synthetic-baseline-runner.d.ts.map +1 -1
  129. package/dist/services/synthetic-baseline-runner.js +11 -0
  130. package/dist/services/synthetic-baseline-runner.js.map +1 -1
  131. package/dist/utils/production-workspace-guard.d.ts +4 -4
  132. package/dist/utils/production-workspace-guard.d.ts.map +1 -1
  133. package/dist/utils/production-workspace-guard.js +19 -16
  134. package/dist/utils/production-workspace-guard.js.map +1 -1
  135. package/dist/utils/production-workspace-guard.test.js +54 -19
  136. package/dist/utils/production-workspace-guard.test.js.map +1 -1
  137. package/package.json +8 -5
  138. package/src/commands/__tests__/intent-flag-wiring.test.ts +88 -3
  139. package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +73 -0
  140. package/src/commands/__tests__/rulecode-flag-wiring.test.ts +31 -24
  141. package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +12 -0
  142. package/src/commands/admission-gate.ts +30 -0
  143. package/src/commands/candidate.ts +74 -6
  144. package/src/commands/console.ts +41 -0
  145. package/src/commands/demo-story-a.ts +4 -2
  146. package/src/commands/diagnose.ts +158 -10
  147. package/src/commands/errors-list.ts +440 -0
  148. package/src/commands/health-codex.ts +249 -0
  149. package/src/commands/health.ts +60 -0
  150. package/src/commands/intent.ts +98 -29
  151. package/src/commands/pain-evidence.ts +6 -0
  152. package/src/commands/pain-record.ts +21 -0
  153. package/src/commands/pain-retry.ts +353 -35
  154. package/src/commands/proven-channel-baseline.ts +4 -2
  155. package/src/commands/runtime-activation.ts +657 -12
  156. package/src/commands/runtime-canary.ts +31 -5
  157. package/src/commands/runtime-health-snapshot.ts +4 -2
  158. package/src/commands/runtime-init.ts +305 -0
  159. package/src/commands/runtime-internalization-context-trace.ts +445 -0
  160. package/src/commands/runtime-internalization-enqueue-successors.ts +3 -1
  161. package/src/commands/runtime-internalization-integrity.ts +4 -2
  162. package/src/commands/runtime-internalization-run-once.ts +20 -5
  163. package/src/commands/runtime-internalization-run-rulehost.ts +159 -6
  164. package/src/commands/runtime-uat.guard.test.ts +14 -16
  165. package/src/commands/runtime-uat.ts +4 -3
  166. package/src/config-reader.ts +1 -1
  167. package/src/index.ts +69 -78
  168. package/src/services/__tests__/evaluator-runner-deps.test.ts +341 -0
  169. package/src/services/config-doctor.ts +9 -2
  170. package/src/services/console-launcher.ts +6 -0
  171. package/src/services/pain-flood-simulation-runner.ts +9 -0
  172. package/src/services/pd-config-loader.ts +2 -2
  173. package/src/services/proven-channel-baseline-runner.ts +10 -3
  174. package/src/services/rulehost-pipeline-runner.ts +157 -6
  175. package/src/services/runtime-adapter-resolver.ts +4 -0
  176. package/src/services/synthetic-baseline-runner.ts +11 -0
  177. package/src/utils/production-workspace-guard.test.ts +62 -20
  178. package/src/utils/production-workspace-guard.ts +19 -16
  179. package/tests/bdd/cli-contract.steps.ts +343 -0
  180. package/tests/bdd/context-trace.steps.ts +229 -0
  181. package/tests/bdd/support/gherkin-loader.ts +147 -0
  182. package/tests/bdd/support/repo-root.ts +62 -0
  183. package/tests/bdd/support/vitest-bdd.ts +179 -0
  184. package/tests/commands/admission-gate-coverage.test.ts +79 -0
  185. package/tests/commands/candidate-audit-repair.test.ts +77 -0
  186. package/tests/commands/candidate-intake.test.ts +155 -10
  187. package/tests/commands/candidate-internalization-backfill.test.ts +30 -0
  188. package/tests/commands/cli-help-snapshot.test.ts +15 -1
  189. package/tests/commands/console-open.test.ts +229 -32
  190. package/tests/commands/diagnose.test.ts +619 -14
  191. package/tests/commands/errors-list.test.ts +678 -0
  192. package/tests/commands/health-codex.test.ts +243 -0
  193. package/tests/commands/health-host-wiring.test.ts +76 -0
  194. package/tests/commands/intent.test.ts +4 -4
  195. package/tests/commands/pain-record.test.ts +80 -0
  196. package/tests/commands/pain-retry.test.ts +94 -3
  197. package/tests/commands/run-rulehost-handler.test.ts +133 -0
  198. package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +227 -0
  199. package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +167 -0
  200. package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +262 -0
  201. package/tests/commands/runtime-activation-list-flag-wiring.test.ts +193 -0
  202. package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +222 -0
  203. package/tests/commands/runtime-activation.test.ts +1090 -5
  204. package/tests/commands/runtime-canary.test.ts +50 -0
  205. package/tests/commands/runtime-features.test.ts +2 -0
  206. package/tests/commands/runtime-init-empty-workspace.test.ts +234 -0
  207. package/tests/commands/runtime-init.test.ts +346 -0
  208. package/tests/commands/runtime-internalization-context-trace.test.ts +110 -0
  209. package/tests/commands/runtime-internalization.test.ts +2 -2
  210. package/tests/config-reader.test.ts +6 -5
  211. package/tests/e2e/cross-package-acceptance.test.ts +55 -13
  212. package/tests/services/mainline-snapshot-assembler.test.ts +6 -3
  213. package/tests/services/resolve-runtime-from-pd-config.test.ts +2 -0
  214. package/tests/services/rulehost-pipeline-runner.test.ts +1 -1
  215. package/vitest.config.ts +1 -1
@@ -0,0 +1,341 @@
1
+ /**
2
+ * PRI-510 (DEFECT-004): EvaluatorRunner repair-loop CLI wiring (EP-02).
3
+ *
4
+ * Tests that the CLI production path constructs `EvaluatorRunner` with the
5
+ * repair-loop deps (`isRepairLoopEnabled` + `seedArtificerRepairTask`) so the
6
+ * PRI-509 core logic is actually invoked at runtime. Without this wiring, the
7
+ * repair loop is dead code in production — `evaluator needs_revision` seeds
8
+ * nothing (EP-02: component exists with isolated tests but real path never
9
+ * calls it).
10
+ *
11
+ * Trust boundary (rc-1, rc-2): the seeder writes `repairPayload` (sourced from
12
+ * evaluator LLM output) into `diagnosticJson` via the validated
13
+ * `createPITaskDiagnosticJson` serializer — no `as` bypass.
14
+ *
15
+ * Loop state freshness (rc-7, EP-05): the seeder does NOT infer
16
+ * `repairIteration` from any cached value; it uses the `repairPayload`
17
+ * constructed by the evaluator runner (which reads it from the dependency
18
+ * artificer task's metadata, written at task creation time).
19
+ *
20
+ * Fail loud (rc-9, EP-03): when `isRepairLoopEnabled` returns false, the
21
+ * `EvaluatorRunner` follows the legacy path (no seeder call). This is
22
+ * observable backward-compatible behavior, not a silent fallback.
23
+ *
24
+ * ERR refs:
25
+ * - EP-02 / ERR-011 / ERR-024: production path wiring — fix the wiring,
26
+ * not just the leaf helper.
27
+ * - EP-04 / cli-7-test-wiring: tests exercise the real helper used by both
28
+ * CLI construction sites (`runtime-internalization-run-once.ts`,
29
+ * `rulehost-pipeline-runner.ts`).
30
+ */
31
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
32
+ import * as fs from 'node:fs';
33
+ import * as path from 'node:path';
34
+ import * as os from 'node:os';
35
+ import { createEvaluatorRunnerDeps } from '../rulehost-pipeline-runner.js';
36
+ import type { SeedArtificerRepairParams } from '@principles/core/runtime-v2';
37
+ import type { RuntimeStateManager, TaskRecord } from '@principles/core/runtime-v2';
38
+
39
+ // ─── Test workspace helpers ────────────────────────────────────────────────
40
+
41
+ function createTempWorkspace(featureFlagEnabled: boolean | null): string {
42
+ // CodeQL: use mkdtempSync for atomic, unpredictable temp dir creation
43
+ // (avoids path-prediction risk from Date.now() + Math.random()).
44
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-pri-510-'));
45
+ const pdDir = path.join(tmpDir, '.pd');
46
+ fs.mkdirSync(pdDir, { recursive: true });
47
+ // When featureFlagEnabled is null, write a config WITHOUT the
48
+ // evaluator_artificer_repair_loop entry (defaults apply → false).
49
+ // Otherwise include the 3 core features + the repair-loop flag.
50
+ const repairFlagEntry =
51
+ featureFlagEnabled === null
52
+ ? ''
53
+ : ` evaluator_artificer_repair_loop: { category: quiet, enabled: ${featureFlagEnabled}, since: '2026-07-04' }\n`;
54
+ const yaml = `
55
+ version: 1
56
+ features:
57
+ prompt: { category: core, enabled: true }
58
+ code_tool_hook: { category: core, enabled: true }
59
+ defer_archive: { category: core, enabled: true }
60
+ ${repairFlagEntry}workspace:
61
+ default: ${tmpDir.replace(/\\/g, '/')}
62
+ internalAgents:
63
+ defaultRuntime: pi-ai.lmstudio
64
+ agents:
65
+ diagnostician:
66
+ enabled: true
67
+ dreamer:
68
+ enabled: true
69
+ philosopher:
70
+ enabled: true
71
+ scribe:
72
+ enabled: true
73
+ artificer:
74
+ enabled: true
75
+ runtimeProfiles:
76
+ pi-ai.lmstudio:
77
+ type: pi-ai
78
+ provider: lmstudio
79
+ model: qwen3.6-27b-mtp
80
+ apiKeyEnv: LMSTUDIO_API_KEY
81
+ baseUrl: http://localhost:1234/v1
82
+ `;
83
+ fs.writeFileSync(path.join(pdDir, 'config.yaml'), yaml, 'utf-8');
84
+ return tmpDir;
85
+ }
86
+
87
+ function cleanupWorkspace(dir: string): void {
88
+ try {
89
+ fs.rmSync(dir, { recursive: true, force: true });
90
+ } catch {
91
+ // best effort
92
+ }
93
+ }
94
+
95
+ // ─── Mock stateManager ─────────────────────────────────────────────────────
96
+
97
+ function createMockStateManager(): {
98
+ stateManager: RuntimeStateManager;
99
+ createdTasks: TaskRecord[];
100
+ } {
101
+ const createdTasks: TaskRecord[] = [];
102
+ const stateManager = {
103
+ createTask: vi.fn(async (record: Omit<TaskRecord, 'createdAt' | 'updatedAt'>) => {
104
+ const now = new Date().toISOString();
105
+ const task: TaskRecord = { ...record, createdAt: now, updatedAt: now };
106
+ createdTasks.push(task);
107
+ return task;
108
+ }),
109
+ } as unknown as RuntimeStateManager;
110
+ return { stateManager, createdTasks };
111
+ }
112
+
113
+ // ─── Fixtures ───────────────────────────────────────────────────────────────
114
+
115
+ function makeValidSeedParams(): SeedArtificerRepairParams {
116
+ return {
117
+ repairPayload: {
118
+ requiredChanges: ['fix path validation', 'add error handling'],
119
+ concerns: ['code quality is poor'],
120
+ previousScore: 0.55,
121
+ repairIteration: 1,
122
+ sourceArtificerArtifactId: 'pi-art-artificer-original',
123
+ sourceEvaluatorTaskId: 'evaluator-r0',
124
+ },
125
+ inheritedDependencyTaskIds: ['scribe-task-001'],
126
+ inheritedChannel: 'prompt',
127
+ inheritedTimeoutMs: 300_000,
128
+ inheritedInputArtifactRefs: [
129
+ { artifactType: 'principle', ref: 'pi-art-scribe-001' },
130
+ ],
131
+ };
132
+ }
133
+
134
+ // ─── Tests ──────────────────────────────────────────────────────────────────
135
+
136
+ describe('PRI-510 (DEFECT-004): createEvaluatorRunnerDeps wires repair loop into CLI (EP-02)', () => {
137
+ let tmpWorkspaces: string[] = [];
138
+
139
+ beforeEach(() => {
140
+ tmpWorkspaces = [];
141
+ });
142
+
143
+ afterEach(() => {
144
+ for (const dir of tmpWorkspaces) cleanupWorkspace(dir);
145
+ tmpWorkspaces = [];
146
+ });
147
+
148
+ it('flag enabled → isRepairLoopEnabled() returns true (EP-02: production wiring works)', () => {
149
+ const workspaceDir = createTempWorkspace(true);
150
+ tmpWorkspaces.push(workspaceDir);
151
+ const { stateManager } = createMockStateManager();
152
+
153
+ const deps = createEvaluatorRunnerDeps({
154
+ stateManager,
155
+ runtimeAdapter: {} as never,
156
+ eventEmitter: {} as never,
157
+ validator: {} as never,
158
+ artifactStore: {} as never,
159
+ workspaceDir,
160
+ });
161
+
162
+ expect(deps.isRepairLoopEnabled).toBeDefined();
163
+ expect(typeof deps.isRepairLoopEnabled).toBe('function');
164
+ // Guard before call — rc-2: no `!` non-null assertion (lint rule).
165
+ if (typeof deps.isRepairLoopEnabled !== 'function') throw new Error('isRepairLoopEnabled missing');
166
+ expect(deps.isRepairLoopEnabled()).toBe(true);
167
+ });
168
+
169
+ it('flag disabled → isRepairLoopEnabled() returns false (backward compatible)', () => {
170
+ const workspaceDir = createTempWorkspace(false);
171
+ tmpWorkspaces.push(workspaceDir);
172
+ const { stateManager } = createMockStateManager();
173
+
174
+ const deps = createEvaluatorRunnerDeps({
175
+ stateManager,
176
+ runtimeAdapter: {} as never,
177
+ eventEmitter: {} as never,
178
+ validator: {} as never,
179
+ artifactStore: {} as never,
180
+ workspaceDir,
181
+ });
182
+
183
+ expect(typeof deps.isRepairLoopEnabled).toBe('function');
184
+ if (typeof deps.isRepairLoopEnabled !== 'function') throw new Error('isRepairLoopEnabled missing');
185
+ expect(deps.isRepairLoopEnabled()).toBe(false);
186
+ });
187
+
188
+ it('flag absent in config → isRepairLoopEnabled() returns false (defaults apply)', () => {
189
+ const workspaceDir = createTempWorkspace(null);
190
+ tmpWorkspaces.push(workspaceDir);
191
+ const { stateManager } = createMockStateManager();
192
+
193
+ const deps = createEvaluatorRunnerDeps({
194
+ stateManager,
195
+ runtimeAdapter: {} as never,
196
+ eventEmitter: {} as never,
197
+ validator: {} as never,
198
+ artifactStore: {} as never,
199
+ workspaceDir,
200
+ });
201
+
202
+ expect(typeof deps.isRepairLoopEnabled).toBe('function');
203
+ if (typeof deps.isRepairLoopEnabled !== 'function') throw new Error('isRepairLoopEnabled missing');
204
+ expect(deps.isRepairLoopEnabled()).toBe(false);
205
+ });
206
+
207
+ it('malformed config → isRepairLoopEnabled() returns false (rc-9: fail safe, not throw)', () => {
208
+ // CodeQL: use mkdtempSync for atomic, unpredictable temp dir creation.
209
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-pri-510-malformed-'));
210
+ tmpWorkspaces.push(tmpDir);
211
+ const pdDir = path.join(tmpDir, '.pd');
212
+ fs.mkdirSync(pdDir, { recursive: true });
213
+ fs.writeFileSync(path.join(pdDir, 'config.yaml'), 'this: is: not: valid: yaml: [', 'utf-8');
214
+
215
+ const { stateManager } = createMockStateManager();
216
+
217
+ const deps = createEvaluatorRunnerDeps({
218
+ stateManager,
219
+ runtimeAdapter: {} as never,
220
+ eventEmitter: {} as never,
221
+ validator: {} as never,
222
+ artifactStore: {} as never,
223
+ workspaceDir: tmpDir,
224
+ });
225
+
226
+ // Malformed config must NOT throw — return false so the legacy path runs.
227
+ expect(typeof deps.isRepairLoopEnabled).toBe('function');
228
+ if (typeof deps.isRepairLoopEnabled !== 'function') throw new Error('isRepairLoopEnabled missing');
229
+ expect(deps.isRepairLoopEnabled()).toBe(false);
230
+ });
231
+
232
+ it('seedArtificerRepairTask → creates artificer task with repairPayload in diagnosticJson (rc-1, rc-6)', async () => {
233
+ const workspaceDir = createTempWorkspace(true);
234
+ tmpWorkspaces.push(workspaceDir);
235
+ const { stateManager, createdTasks } = createMockStateManager();
236
+ const params = makeValidSeedParams();
237
+
238
+ const deps = createEvaluatorRunnerDeps({
239
+ stateManager,
240
+ runtimeAdapter: {} as never,
241
+ eventEmitter: {} as never,
242
+ validator: {} as never,
243
+ artifactStore: {} as never,
244
+ workspaceDir,
245
+ });
246
+
247
+ expect(deps.seedArtificerRepairTask).toBeDefined();
248
+ expect(typeof deps.seedArtificerRepairTask).toBe('function');
249
+ if (typeof deps.seedArtificerRepairTask !== 'function') throw new Error('seedArtificerRepairTask missing');
250
+ const repairTaskId = await deps.seedArtificerRepairTask(params);
251
+
252
+ // Returns a non-empty task ID
253
+ expect(typeof repairTaskId).toBe('string');
254
+ expect(repairTaskId.length).toBeGreaterThan(0);
255
+
256
+ // Exactly one task created
257
+ expect(createdTasks).toHaveLength(1);
258
+ const [task] = createdTasks;
259
+ if (!task) throw new Error('task not created');
260
+
261
+ // D1 (PRI-509): task kind is 'artificer' (reuses artificer-runner, not a
262
+ // new runner type)
263
+ expect(task.taskKind).toBe('artificer');
264
+
265
+ // Task starts pending so the orchestrator can pick it up
266
+ expect(task.status).toBe('pending');
267
+
268
+ // Task ID matches what the seeder returns
269
+ expect(task.taskId).toBe(repairTaskId);
270
+
271
+ // diagnosticJson must contain the repairPayload — round-trip via the
272
+ // parser to verify the validated shape (rc-1: treat parsed JSON as
273
+ // unknown; rc-2: no `as` bypass; rc-6: lineage fields consistent).
274
+ expect(task.diagnosticJson).toBeDefined();
275
+ expect(typeof task.diagnosticJson).toBe('string');
276
+ if (typeof task.diagnosticJson !== 'string') throw new Error('diagnosticJson not a string');
277
+ const parsed = JSON.parse(task.diagnosticJson);
278
+ const meta = parsed.pi_metadata;
279
+ expect(meta).toBeDefined();
280
+ expect(meta.repairPayload).toEqual(params.repairPayload);
281
+
282
+ // Inherited lineage must be preserved (PRI-508 context + PRI-509 deps)
283
+ expect(meta.dependencyTaskIds).toEqual(params.inheritedDependencyTaskIds);
284
+ expect(meta.channel).toBe(params.inheritedChannel);
285
+ expect(meta.timeoutMs).toBe(params.inheritedTimeoutMs);
286
+ expect(meta.inputArtifactRefs).toEqual(params.inheritedInputArtifactRefs);
287
+ });
288
+
289
+ it('seedArtificerRepairTask → each call returns a UNIQUE task ID (rc-7: no stale state)', async () => {
290
+ const workspaceDir = createTempWorkspace(true);
291
+ tmpWorkspaces.push(workspaceDir);
292
+ const { stateManager } = createMockStateManager();
293
+ const params = makeValidSeedParams();
294
+
295
+ const deps = createEvaluatorRunnerDeps({
296
+ stateManager,
297
+ runtimeAdapter: {} as never,
298
+ eventEmitter: {} as never,
299
+ validator: {} as never,
300
+ artifactStore: {} as never,
301
+ workspaceDir,
302
+ });
303
+
304
+ if (typeof deps.seedArtificerRepairTask !== 'function') throw new Error('seedArtificerRepairTask missing');
305
+ const id1 = await deps.seedArtificerRepairTask(params);
306
+ const id2 = await deps.seedArtificerRepairTask(params);
307
+
308
+ expect(id1).not.toBe(id2);
309
+ });
310
+
311
+ it('deps spread contains all required base PeerRunnerDeps fields (EP-02: real path gets full deps)', () => {
312
+ const workspaceDir = createTempWorkspace(true);
313
+ tmpWorkspaces.push(workspaceDir);
314
+ const { stateManager } = createMockStateManager();
315
+ const fakeRuntimeAdapter = { kind: () => 'pi-ai' } as never;
316
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
317
+ const fakeEventEmitter = { emit: () => {} } as never;
318
+ const fakeValidator = { validate: () => ({ ok: true, value: {} }) } as never;
319
+ const fakeArtifactStore = { get: () => null } as never;
320
+
321
+ const deps = createEvaluatorRunnerDeps({
322
+ stateManager,
323
+ runtimeAdapter: fakeRuntimeAdapter,
324
+ eventEmitter: fakeEventEmitter,
325
+ validator: fakeValidator,
326
+ artifactStore: fakeArtifactStore,
327
+ workspaceDir,
328
+ });
329
+
330
+ // Base PeerRunnerDeps preserved
331
+ expect(deps.stateManager).toBe(stateManager);
332
+ expect(deps.runtimeAdapter).toBe(fakeRuntimeAdapter);
333
+ expect(deps.eventEmitter).toBe(fakeEventEmitter);
334
+ expect(deps.validator).toBe(fakeValidator);
335
+ expect(deps.artifactStore).toBe(fakeArtifactStore);
336
+
337
+ // Repair-loop deps present
338
+ expect(deps.isRepairLoopEnabled).toBeTypeOf('function');
339
+ expect(deps.seedArtificerRepairTask).toBeTypeOf('function');
340
+ });
341
+ });
@@ -517,12 +517,19 @@ export async function buildDoctorOutput(input: BuildDoctorInput): Promise<Doctor
517
517
  }
518
518
 
519
519
  // 5) Compute overall status
520
+ // Classification → status mapping:
521
+ // - config_missing (profile exists but fields empty/placeholder) → degraded
522
+ // This is the expected initial state for the default `pd.default` profile
523
+ // that ships with empty provider/model/apiKeyEnv. Users fill via console.
524
+ // - auth_missing (apiKeyEnv set but env var not in environment) → failed
525
+ // User configured the profile but the secret is missing at runtime.
526
+ // - needs_probe / rate_limit / unavailable → degraded
520
527
  let status: DoctorStatus = 'ok';
521
528
  const classifications = providerHealth.map((p) => p.classification);
522
- if (classifications.includes('rate_limit') || classifications.includes('unavailable') || classifications.includes('needs_probe')) {
529
+ if (classifications.includes('rate_limit') || classifications.includes('unavailable') || classifications.includes('needs_probe') || classifications.includes('config_missing')) {
523
530
  status = 'degraded';
524
531
  }
525
- if (classifications.includes('auth_missing') || classifications.includes('config_missing')) {
532
+ if (classifications.includes('auth_missing')) {
526
533
  status = 'failed';
527
534
  }
528
535
  if (!loadResult.ok) {
@@ -34,6 +34,12 @@ export interface ConsoleLaunchResult {
34
34
  reused: boolean;
35
35
  /** True when a browser should/has been opened (skipped in --json mode). */
36
36
  browserOpened: boolean;
37
+ /**
38
+ * PID of the freshly spawned console server process. Present only when
39
+ * status === 'started'; absent on 'reused' (the server was started by
40
+ * another process, so this launcher cannot know its PID).
41
+ */
42
+ serverPid?: number;
37
43
  }
38
44
 
39
45
  export interface ConsoleLaunchOptions {
@@ -18,6 +18,7 @@ import { PainSignalBridge } from '@principles/core/runtime-v2';
18
18
  import { CandidateIntakeService } from '@principles/core/runtime-v2';
19
19
  import { PrincipleTreeLedgerAdapter } from '@principles/core/runtime-v2';
20
20
  import { makeDeterministicDiagnosticianOutput } from '@principles/core/runtime-v2';
21
+ import { contentHashFn } from './rulehost-pipeline-runner.js';
21
22
  import type {
22
23
  PainFloodSimulationSummary,
23
24
  PainFloodStage,
@@ -83,6 +84,11 @@ async function runScenario(
83
84
  painType: signal.painType,
84
85
  source: 'pain-flood-simulation',
85
86
  reason: signal.reason,
87
+ // Synthetic flood is a deterministic dedup/stress tool — it has no real
88
+ // host session. Provide synthetic evidence so the PEAT-B1 admission gate
89
+ // (default-on) does not short-circuit every signal before diagnosis,
90
+ // which would mask the dedup behaviour this tool exists to verify.
91
+ evidence: [{ sourceRef: `synthetic-flood:${signal.painId}`, note: 'Synthetic flood deterministic evidence anchor' }],
86
92
  });
87
93
 
88
94
  if (result.status === 'failed' || result.status === 'retried') {
@@ -246,6 +252,7 @@ export async function runPainFloodSimulation(opts: PainFloodSimulationRunnerOpti
246
252
  artifactStore: stateManager.piArtifactStore,
247
253
  validator: new DefaultDiagRootCauseValidator(),
248
254
  contextAssembler,
255
+ contentHashFn,
249
256
  },
250
257
  {
251
258
  owner: 'pain-flood-simulation',
@@ -262,6 +269,7 @@ export async function runPainFloodSimulation(opts: PainFloodSimulationRunnerOpti
262
269
  eventEmitter,
263
270
  artifactStore: stateManager.piArtifactStore,
264
271
  validator: new DefaultDiagDistillerValidator(),
272
+ contentHashFn,
265
273
  },
266
274
  {
267
275
  owner: 'pain-flood-simulation',
@@ -278,6 +286,7 @@ export async function runPainFloodSimulation(opts: PainFloodSimulationRunnerOpti
278
286
  eventEmitter,
279
287
  artifactStore: stateManager.piArtifactStore,
280
288
  committer,
289
+ contentHashFn,
281
290
  },
282
291
  {
283
292
  owner: 'pain-flood-simulation',
@@ -14,7 +14,7 @@
14
14
  import * as fs from 'fs';
15
15
  import * as path from 'path';
16
16
  import * as os from 'os';
17
- import yaml from 'js-yaml';
17
+ import * as yaml from 'js-yaml';
18
18
  import {
19
19
  validatePdConfig,
20
20
  computeEffectivePdConfig,
@@ -299,7 +299,7 @@ function loadOpenClawPluginConfig(): { workspace?: string } | null {
299
299
  parsed !== null &&
300
300
  typeof parsed === 'object' &&
301
301
  !Array.isArray(parsed) &&
302
- 'workspace' in parsed &&
302
+ Object.hasOwn(parsed, 'workspace') &&
303
303
  typeof (parsed as Record<string, unknown>).workspace === 'string' &&
304
304
  ((parsed as Record<string, unknown>).workspace as string).length > 0
305
305
  ) {
@@ -26,11 +26,18 @@ export interface ProvenChannelBaselineRunnerOptions {
26
26
 
27
27
  function isProductionWorkspace(workspaceDir: string): boolean {
28
28
  const normalized = path.resolve(workspaceDir).toLowerCase();
29
- const productionPrefixes = [
30
- path.resolve('D:\\.openclaw\\workspace').toLowerCase(),
31
- path.resolve('C:\\Users\\Administrator\\.openclaw\\workspace').toLowerCase(),
29
+ const productionPrefixes: string[] = [
32
30
  path.resolve(path.join(os.homedir(), '.openclaw', 'workspace')).toLowerCase(),
33
31
  ];
32
+ // Allow operator to declare additional production paths (cross-platform).
33
+ // Use path.delimiter (; on Windows, : on Unix) to separate multiple paths.
34
+ const extra = process.env.PD_PRODUCTION_WORKSPACE;
35
+ if (extra) {
36
+ for (const p of extra.split(path.delimiter)) {
37
+ const trimmed = p.trim();
38
+ if (trimmed) productionPrefixes.push(path.resolve(trimmed).toLowerCase());
39
+ }
40
+ }
34
41
  for (const prefix of productionPrefixes) {
35
42
  if (normalized === prefix || normalized.startsWith(prefix + path.sep)) {
36
43
  return true;