@principles/pd-cli 1.128.1 → 1.129.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 (38) hide show
  1. package/dist/commands/mvp-smoke.js +1 -1
  2. package/dist/commands/mvp-smoke.js.map +1 -1
  3. package/dist/commands/rulecode.js +1 -1
  4. package/dist/commands/rulecode.js.map +1 -1
  5. package/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
  6. package/dist/commands/runtime-internalization-run-rulehost.js +61 -7
  7. package/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
  8. package/dist/commands/runtime.js +1 -1
  9. package/dist/commands/runtime.js.map +1 -1
  10. package/dist/index.js +67 -34
  11. package/dist/index.js.map +1 -1
  12. package/dist/services/__tests__/rulehost-readiness.test.d.ts +2 -0
  13. package/dist/services/__tests__/rulehost-readiness.test.d.ts.map +1 -0
  14. package/dist/services/__tests__/rulehost-readiness.test.js +314 -0
  15. package/dist/services/__tests__/rulehost-readiness.test.js.map +1 -0
  16. package/dist/services/rulehost-readiness.d.ts +62 -0
  17. package/dist/services/rulehost-readiness.d.ts.map +1 -0
  18. package/dist/services/rulehost-readiness.js +214 -0
  19. package/dist/services/rulehost-readiness.js.map +1 -0
  20. package/package.json +1 -1
  21. package/src/commands/mvp-smoke.ts +1 -1
  22. package/src/commands/rulecode.ts +1 -1
  23. package/src/commands/runtime-internalization-run-rulehost.ts +68 -6
  24. package/src/commands/runtime.ts +1 -1
  25. package/src/index.ts +73 -36
  26. package/src/services/__tests__/rulehost-readiness.test.ts +366 -0
  27. package/src/services/rulehost-readiness.ts +326 -0
  28. package/tests/commands/cli-command-tree.test.ts +2 -2
  29. package/tests/commands/cli-help-snapshot.test.ts +135 -0
  30. package/tests/commands/cli-skill-contract.test.ts +121 -0
  31. package/tests/commands/run-rulehost-handler.test.ts +277 -0
  32. package/tests/commands/runtime-internalization.test.ts +2 -2
  33. package/tests/services/rulehost-pipeline-e2e.test.ts +71 -61
  34. package/dist/commands/central-sync.d.ts +0 -10
  35. package/dist/commands/central-sync.d.ts.map +0 -1
  36. package/dist/commands/central-sync.js +0 -32
  37. package/dist/commands/central-sync.js.map +0 -1
  38. package/src/commands/central-sync.ts +0 -44
@@ -52,6 +52,18 @@ function writeConfig(workspaceDir: string, content: object): void {
52
52
  );
53
53
  }
54
54
 
55
+ /** Type guard: narrows `unknown` to `Record<string, unknown>` without `as`. */
56
+ function isRecord(value: unknown): value is Record<string, unknown> {
57
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
58
+ }
59
+
60
+ function requireRecord(value: unknown, label: string): Record<string, unknown> {
61
+ if (!isRecord(value)) {
62
+ throw new Error(`${label} is not a record`);
63
+ }
64
+ return value;
65
+ }
66
+
55
67
  /** Config with both artificer+evaluator enabled, pi-ai profile, API key env set. */
56
68
  function makeCapabilityOnConfig(workspaceDir: string): object {
57
69
  return {
@@ -90,17 +102,19 @@ function makeCapabilityOnConfig(workspaceDir: string): object {
90
102
 
91
103
  /** Config with artificer disabled (capability must be OFF). */
92
104
  function makeArtificerDisabledConfig(workspaceDir: string): object {
93
- const cfg = makeCapabilityOnConfig(workspaceDir) as Record<string, unknown>;
94
- const internalAgents = cfg.internalAgents as { agents: Record<string, { enabled: boolean; runtimeProfile?: string }> };
95
- internalAgents.agents.artificer = { enabled: false };
105
+ const cfg = makeCapabilityOnConfig(workspaceDir);
106
+ const internalAgents = requireRecord(Reflect.get(cfg, 'internalAgents'), 'internalAgents');
107
+ const agents = requireRecord(Reflect.get(internalAgents, 'agents'), 'internalAgents.agents');
108
+ Reflect.set(agents, 'artificer', { enabled: false });
96
109
  return cfg;
97
110
  }
98
111
 
99
112
  /** Config with evaluator disabled (capability must be OFF). */
100
113
  function makeEvaluatorDisabledConfig(workspaceDir: string): object {
101
- const cfg = makeCapabilityOnConfig(workspaceDir) as Record<string, unknown>;
102
- const internalAgents = cfg.internalAgents as { agents: Record<string, { enabled: boolean; runtimeProfile?: string }> };
103
- internalAgents.agents.evaluator = { enabled: false };
114
+ const cfg = makeCapabilityOnConfig(workspaceDir);
115
+ const internalAgents = requireRecord(Reflect.get(cfg, 'internalAgents'), 'internalAgents');
116
+ const agents = requireRecord(Reflect.get(internalAgents, 'agents'), 'internalAgents.agents');
117
+ Reflect.set(agents, 'evaluator', { enabled: false });
104
118
  return cfg;
105
119
  }
106
120
 
@@ -158,10 +172,11 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
158
172
  }
159
173
 
160
174
  /** Extract the single JSON object written to stdout. Fails if not exactly one write. */
161
- function parseJsonOutput(): unknown {
175
+ function parseJsonOutput(): Record<string, unknown> {
162
176
  expect(stdoutSpy).toHaveBeenCalledTimes(1);
163
177
  const raw = stdoutSpy.mock.calls[0][0] as string;
164
- return JSON.parse(raw);
178
+ const parsed: unknown = JSON.parse(raw);
179
+ return requireRecord(parsed, 'stdout JSON');
165
180
  }
166
181
 
167
182
  // ── Capability ON: both agents enabled, API key set ──────────────────────
@@ -178,14 +193,11 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
178
193
  });
179
194
 
180
195
  const output = parseJsonOutput();
181
- expect(typeof output).toBe('object');
182
- expect(output).not.toBeNull();
183
- const obj = output as { status: string; codeRuleCapability?: { enabled: boolean; disabledReason?: string }; capabilityStatus?: string };
184
- expect(obj.status).toBe('dry_run');
185
- expect(obj.codeRuleCapability).toBeDefined();
186
- expect(obj.codeRuleCapability?.enabled).toBe(true);
187
- expect(obj.codeRuleCapability?.disabledReason).toBeUndefined();
188
- expect(obj.capabilityStatus).toContain('ON');
196
+ const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
197
+ expect(output.status).toBe('dry_run');
198
+ expect(codeRuleCapability.enabled).toBe(true);
199
+ expect(codeRuleCapability.disabledReason).toBeUndefined();
200
+ expect(String(output.capabilityStatus)).toContain('ON');
189
201
  // exitCode must NOT be set for dry_run success
190
202
  expect(process.exitCode).toBeUndefined();
191
203
  });
@@ -203,16 +215,13 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
203
215
  await handleRunRuleHost({ workspace, painId: 'pain-flag-default', dryRun: true, json: true });
204
216
 
205
217
  const output = parseJsonOutput();
206
- // PRI-435 (CodeRabbit P3): narrow unknown before property access
207
- expect(typeof output).toBe('object');
208
- expect(output).not.toBeNull();
209
- const obj = output as { status: string; codeRuleCapability?: { enabled: boolean; disabledReason?: string }; capabilityStatus?: string };
210
- expect(obj.status).toBe('dry_run');
218
+ const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
219
+ expect(output.status).toBe('dry_run');
211
220
  // PRI-435: code_rule_capability is now a core flag — defaults ON even when omitted from config.
212
221
  // It cannot be disabled by config. The capability is ON when artificer+evaluator are configured.
213
- expect(obj.codeRuleCapability).toEqual(expect.objectContaining({ enabled: true }));
214
- expect(obj.codeRuleCapability?.disabledReason).toBeUndefined();
215
- expect(String(obj.capabilityStatus)).toContain('ON');
222
+ expect(codeRuleCapability).toEqual(expect.objectContaining({ enabled: true }));
223
+ expect(codeRuleCapability.disabledReason).toBeUndefined();
224
+ expect(String(output.capabilityStatus)).toContain('ON');
216
225
  });
217
226
 
218
227
  it('PRI-435: explicit emergency disable via code_rule_capability.enabled=false is observable', async () => {
@@ -229,16 +238,13 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
229
238
  await handleRunRuleHost({ workspace, painId: 'pain-emergency-disable', dryRun: true, json: true });
230
239
 
231
240
  const output = parseJsonOutput();
232
- // PRI-435 (CodeRabbit P3): narrow unknown before property access
233
- expect(typeof output).toBe('object');
234
- expect(output).not.toBeNull();
235
- const obj = output as { status: string; codeRuleCapability?: { enabled: boolean; disabledReason?: string }; capabilityStatus?: string };
236
- expect(obj.status).toBe('dry_run');
241
+ const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
242
+ expect(output.status).toBe('dry_run');
237
243
  // PRI-435: Emergency disable via code_rule_capability.enabled=false is preserved.
238
244
  // The capability is OFF with a structured reason.
239
- expect(obj.codeRuleCapability).toEqual(expect.objectContaining({ enabled: false }));
240
- expect(String(obj.codeRuleCapability?.disabledReason)).toContain('feature flag');
241
- expect(String(obj.capabilityStatus)).toContain('OFF');
245
+ expect(codeRuleCapability).toEqual(expect.objectContaining({ enabled: false }));
246
+ expect(String(codeRuleCapability.disabledReason)).toContain('feature flag');
247
+ expect(String(output.capabilityStatus)).toContain('OFF');
242
248
  });
243
249
 
244
250
  it('reports the resolved runtime profile for every executed agent', async () => {
@@ -271,10 +277,17 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
271
277
 
272
278
  await handleRunRuleHost({ workspace, painId: 'pain-disabled-philosopher', confirm: true, json: true });
273
279
 
280
+ // PRI-461: readiness gate catches disabled required agents BEFORE adapter
281
+ // construction, returning status='refused' with a structured reason instead
282
+ // of the old opaque 'agent_runtime_resolution_failed' error.
274
283
  const output = parseJsonOutput();
275
- expect(output.status).toBe('failed');
276
- expect(output.reason).toBe('agent_runtime_resolution_failed');
277
- expect(String(output.message)).toContain('philosopher');
284
+ expect(isRecord(output)).toBe(true);
285
+ if (!isRecord(output)) {
286
+ throw new Error('output is not a record');
287
+ }
288
+ expect(output.status).toBe('refused');
289
+ expect(String(output.reason)).toContain('philosopher');
290
+ expect(typeof output.nextAction).toBe('string');
278
291
  expect(process.exitCode).toBe(1);
279
292
  expect(fs.existsSync(path.join(workspace, '.state', 'runtime-v2.sqlite'))).toBe(false);
280
293
  });
@@ -308,10 +321,10 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
308
321
  });
309
322
 
310
323
  const output = parseJsonOutput();
311
- const obj = output as { status: string; codeRuleCapability: { enabled: boolean; disabledReason?: string } };
312
- expect(obj.status).toBe('dry_run');
313
- expect(obj.codeRuleCapability.enabled).toBe(false);
314
- expect(obj.codeRuleCapability.disabledReason).toContain('artificer');
324
+ const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
325
+ expect(output.status).toBe('dry_run');
326
+ expect(codeRuleCapability.enabled).toBe(false);
327
+ expect(String(codeRuleCapability.disabledReason)).toContain('artificer');
315
328
  });
316
329
 
317
330
  // ── Capability OFF: evaluator disabled ───────────────────────────────────
@@ -328,10 +341,10 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
328
341
  });
329
342
 
330
343
  const output = parseJsonOutput();
331
- const obj = output as { status: string; codeRuleCapability: { enabled: boolean; disabledReason?: string } };
332
- expect(obj.status).toBe('dry_run');
333
- expect(obj.codeRuleCapability.enabled).toBe(false);
334
- expect(obj.codeRuleCapability.disabledReason).toContain('evaluator');
344
+ const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
345
+ expect(output.status).toBe('dry_run');
346
+ expect(codeRuleCapability.enabled).toBe(false);
347
+ expect(String(codeRuleCapability.disabledReason)).toContain('evaluator');
335
348
  });
336
349
 
337
350
  // ── Capability OFF: API key not set ──────────────────────────────────────
@@ -393,10 +406,11 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
393
406
  });
394
407
 
395
408
  const output = parseJsonOutput();
396
- const obj = output as { status: string; codeRuleCapability: { enabled: boolean; disabledReason?: string } };
397
- expect(obj.status).toBe('dry_run');
398
- expect(obj.codeRuleCapability.enabled).toBe(false);
399
- expect(obj.codeRuleCapability.disabledReason).toContain('apiKeyEnv');
409
+ const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
410
+ expect(output.status).toBe('dry_run');
411
+ expect(codeRuleCapability.enabled).toBe(false);
412
+ expect(String(codeRuleCapability.disabledReason)).toContain('TEST_RULEHOST_ARTIFICER_KEY');
413
+ expect(String(codeRuleCapability.disabledReason)).toContain('not set');
400
414
  });
401
415
 
402
416
  // ── CLI gate: mutual exclusivity ─────────────────────────────────────────
@@ -414,10 +428,9 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
414
428
  });
415
429
 
416
430
  const output = parseJsonOutput();
417
- const obj = output as { status: string; reason: string; nextAction: string };
418
- expect(obj.status).toBe('failed');
419
- expect(obj.reason).toContain('mutually exclusive');
420
- expect(obj.nextAction).toBeTruthy();
431
+ expect(output.status).toBe('failed');
432
+ expect(String(output.reason)).toContain('mutually exclusive');
433
+ expect(output.nextAction).toBeTruthy();
421
434
  expect(process.exitCode).toBe(1);
422
435
  });
423
436
 
@@ -435,10 +448,9 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
435
448
  });
436
449
 
437
450
  const output = parseJsonOutput();
438
- const obj = output as { status: string; reason: string; nextAction: string };
439
- expect(obj.status).toBe('failed');
440
- expect(obj.reason).toContain('painId');
441
- expect(obj.nextAction).toBeTruthy();
451
+ expect(output.status).toBe('failed');
452
+ expect(String(output.reason)).toContain('painId');
453
+ expect(output.nextAction).toBeTruthy();
442
454
  expect(process.exitCode).toBe(1);
443
455
  });
444
456
 
@@ -480,8 +492,7 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
480
492
  });
481
493
 
482
494
  const output = parseJsonOutput();
483
- const obj = output as { status: string };
484
- expect(obj.status).toBe('dry_run');
495
+ expect(output.status).toBe('dry_run');
485
496
  // Must NOT set exitCode for dry_run
486
497
  expect(process.exitCode).toBeUndefined();
487
498
  });
@@ -501,10 +512,9 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
501
512
  });
502
513
 
503
514
  const output = parseJsonOutput();
504
- const obj = output as { status: string; reason: string; nextAction: string };
505
- expect(obj.status).toBe('failed');
506
- expect(obj.reason).toContain('unsupported channel');
507
- expect(obj.nextAction).toBeTruthy();
515
+ expect(output.status).toBe('failed');
516
+ expect(String(output.reason)).toContain('unsupported channel');
517
+ expect(output.nextAction).toBeTruthy();
508
518
  expect(process.exitCode).toBe(1);
509
519
  });
510
520
  });
@@ -1,10 +0,0 @@
1
- /**
2
- * pd central sync command implementation.
3
- *
4
- * Usage: pd central sync
5
- *
6
- * Triggers a sync cycle via CentralDatabase.syncAll() and reports
7
- * per-workspace sync results with exit code 0 on success, non-zero on failure.
8
- */
9
- export declare function handleCentralSync(): Promise<void>;
10
- //# sourceMappingURL=central-sync.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"central-sync.d.ts","sourceRoot":"","sources":["../../src/commands/central-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAqBvD"}
@@ -1,32 +0,0 @@
1
- /**
2
- * pd central sync command implementation.
3
- *
4
- * Usage: pd central sync
5
- *
6
- * Triggers a sync cycle via CentralDatabase.syncAll() and reports
7
- * per-workspace sync results with exit code 0 on success, non-zero on failure.
8
- */
9
- async function loadCentralDatabase() {
10
- const importModule = Function('specifier', 'return import(specifier)');
11
- return importModule('../../../openclaw-plugin/src/service/central-database.js');
12
- }
13
- export async function handleCentralSync() {
14
- try {
15
- const { CentralDatabase } = await loadCentralDatabase();
16
- const centralDb = new CentralDatabase();
17
- const results = centralDb.syncAll();
18
- const totalRecords = Array.from(results.values()).reduce((sum, count) => sum + count, 0);
19
- const workspaceCount = results.size;
20
- console.log(`Sync complete — ${totalRecords} records across ${workspaceCount} workspace(s).`);
21
- for (const [workspaceName, count] of results.entries()) {
22
- console.log(` ${workspaceName}: ${count} records`);
23
- }
24
- centralDb.dispose();
25
- }
26
- catch (err) {
27
- const message = err instanceof Error ? err.message : String(err);
28
- console.error(`Error: Sync failed — ${message}`);
29
- process.exit(1);
30
- }
31
- }
32
- //# sourceMappingURL=central-sync.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"central-sync.js","sourceRoot":"","sources":["../../src/commands/central-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,KAAK,UAAU,mBAAmB;IAIhC,MAAM,YAAY,GAAG,QAAQ,CAAC,WAAW,EAAE,0BAA0B,CAKnE,CAAC;IACH,OAAO,YAAY,CAAC,0DAA0D,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC;QACH,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,mBAAmB,EAAE,CAAC;QACxD,MAAM,SAAS,GAAG,IAAI,eAAe,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC;QAEpC,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;QACzF,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;QAEpC,OAAO,CAAC,GAAG,CAAC,mBAAmB,YAAY,mBAAmB,cAAc,gBAAgB,CAAC,CAAC;QAE9F,KAAK,MAAM,CAAC,aAAa,EAAE,KAAK,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,KAAK,aAAa,KAAK,KAAK,UAAU,CAAC,CAAC;QACtD,CAAC;QAED,SAAS,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,KAAK,CAAC,wBAAwB,OAAO,EAAE,CAAC,CAAC;QACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -1,44 +0,0 @@
1
- /**
2
- * pd central sync command implementation.
3
- *
4
- * Usage: pd central sync
5
- *
6
- * Triggers a sync cycle via CentralDatabase.syncAll() and reports
7
- * per-workspace sync results with exit code 0 on success, non-zero on failure.
8
- */
9
-
10
- async function loadCentralDatabase(): Promise<{ CentralDatabase: new () => {
11
- syncAll(): Map<string, number>;
12
- dispose(): void;
13
- } }> {
14
- const importModule = Function('specifier', 'return import(specifier)') as (specifier: string) => Promise<{
15
- CentralDatabase: new () => {
16
- syncAll(): Map<string, number>;
17
- dispose(): void;
18
- };
19
- }>;
20
- return importModule('../../../openclaw-plugin/src/service/central-database.js');
21
- }
22
-
23
- export async function handleCentralSync(): Promise<void> {
24
- try {
25
- const { CentralDatabase } = await loadCentralDatabase();
26
- const centralDb = new CentralDatabase();
27
- const results = centralDb.syncAll();
28
-
29
- const totalRecords = Array.from(results.values()).reduce((sum, count) => sum + count, 0);
30
- const workspaceCount = results.size;
31
-
32
- console.log(`Sync complete — ${totalRecords} records across ${workspaceCount} workspace(s).`);
33
-
34
- for (const [workspaceName, count] of results.entries()) {
35
- console.log(` ${workspaceName}: ${count} records`);
36
- }
37
-
38
- centralDb.dispose();
39
- } catch (err) {
40
- const message = err instanceof Error ? err.message : String(err);
41
- console.error(`Error: Sync failed — ${message}`);
42
- process.exit(1);
43
- }
44
- }