@principles/pd-cli 1.74.0 → 1.76.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/config-doctor.d.ts +3 -6
  2. package/dist/commands/config-doctor.d.ts.map +1 -1
  3. package/dist/commands/config-doctor.js +30 -31
  4. package/dist/commands/config-doctor.js.map +1 -1
  5. package/dist/commands/console.d.ts +18 -0
  6. package/dist/commands/console.d.ts.map +1 -1
  7. package/dist/commands/console.js +439 -0
  8. package/dist/commands/console.js.map +1 -1
  9. package/dist/commands/runtime-features.d.ts +23 -8
  10. package/dist/commands/runtime-features.d.ts.map +1 -1
  11. package/dist/commands/runtime-features.js +72 -31
  12. package/dist/commands/runtime-features.js.map +1 -1
  13. package/dist/index.js +51 -15
  14. package/dist/index.js.map +1 -1
  15. package/dist/services/config-doctor.d.ts +26 -66
  16. package/dist/services/config-doctor.d.ts.map +1 -1
  17. package/dist/services/config-doctor.js +197 -374
  18. package/dist/services/config-doctor.js.map +1 -1
  19. package/dist/services/console-launcher.d.ts +110 -0
  20. package/dist/services/console-launcher.d.ts.map +1 -0
  21. package/dist/services/console-launcher.js +282 -0
  22. package/dist/services/console-launcher.js.map +1 -0
  23. package/dist/services/pd-config-loader.d.ts +64 -0
  24. package/dist/services/pd-config-loader.d.ts.map +1 -0
  25. package/dist/services/pd-config-loader.js +156 -0
  26. package/dist/services/pd-config-loader.js.map +1 -0
  27. package/package.json +1 -1
  28. package/src/commands/config-doctor.ts +30 -30
  29. package/src/commands/console.ts +445 -1
  30. package/src/commands/runtime-features.ts +98 -44
  31. package/src/index.ts +55 -16
  32. package/src/services/config-doctor.ts +236 -425
  33. package/src/services/console-launcher.ts +373 -0
  34. package/src/services/pd-config-loader.ts +213 -0
  35. package/tests/commands/config-doctor.test.ts +207 -506
  36. package/tests/commands/console-open.test.ts +773 -0
  37. package/tests/commands/runtime-features.test.ts +220 -85
  38. package/tests/services/pd-config-loader.test.ts +479 -0
@@ -0,0 +1,156 @@
1
+ /**
2
+ * PD Config Loader — PRI-305
3
+ *
4
+ * I/O boundary: reads `.pd/config.yaml`, validates via core, computes effective config.
5
+ * This replaces the old `feature-flag-loader.ts` and `workflows.yaml` reading
6
+ * for CLI production paths.
7
+ *
8
+ * ADR-0016: PD owns exactly one user config file.
9
+ * - Missing config → defaults with nextAction
10
+ * - Malformed config → fail loud with errors and nextAction
11
+ * - No secrets in output
12
+ */
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ import yaml from 'js-yaml';
16
+ import { validatePdConfig, computeEffectivePdConfig, computeFeatureFlagsFromConfig, redactPdConfig, } from '@principles/core/runtime-v2';
17
+ // ── Constants ────────────────────────────────────────────────────────────────
18
+ export const PD_CONFIG_DIR = '.pd';
19
+ export const PD_CONFIG_FILENAME = 'config.yaml';
20
+ // ── Config Path ──────────────────────────────────────────────────────────────
21
+ export function getPdConfigPath(workspaceDir) {
22
+ return path.join(workspaceDir, PD_CONFIG_DIR, PD_CONFIG_FILENAME);
23
+ }
24
+ // ── Legacy File Detection ────────────────────────────────────────────────────
25
+ function detectLegacyFiles(workspaceDir) {
26
+ const detected = [];
27
+ const legacyPaths = [
28
+ path.join(workspaceDir, PD_CONFIG_DIR, 'feature-flags.yaml'),
29
+ path.join(workspaceDir, '.state', 'workflows.yaml'),
30
+ ];
31
+ for (const p of legacyPaths) {
32
+ if (fs.existsSync(p)) {
33
+ detected.push(p);
34
+ }
35
+ }
36
+ return detected;
37
+ }
38
+ // ── Load PD Config ───────────────────────────────────────────────────────────
39
+ /**
40
+ * Load and validate `.pd/config.yaml` from the workspace.
41
+ *
42
+ * - Missing file → returns defaults with source='defaults'
43
+ * - Malformed file → returns error result with defaults fallback
44
+ * - Valid file → returns effective config with source='user_config'
45
+ *
46
+ * Never throws on malformed input. Always provides a usable fallback.
47
+ */
48
+ export function loadPdConfig(workspaceDir) {
49
+ const configPath = getPdConfigPath(workspaceDir);
50
+ const legacyFilesDetected = detectLegacyFiles(workspaceDir);
51
+ // 1) Config file missing → use defaults
52
+ if (!fs.existsSync(configPath)) {
53
+ const effective = computeEffectivePdConfig(null);
54
+ return {
55
+ ok: true,
56
+ effective,
57
+ source: 'defaults',
58
+ configPath,
59
+ warnings: [
60
+ ...effective.warnings,
61
+ ...(legacyFilesDetected.length > 0
62
+ ? [`Legacy config files detected (${legacyFilesDetected.length}): ${legacyFilesDetected.join(', ')}. PD now uses .pd/config.yaml.`]
63
+ : []),
64
+ ],
65
+ legacyFilesDetected,
66
+ };
67
+ }
68
+ // 2) Read the file
69
+ let raw;
70
+ try {
71
+ raw = fs.readFileSync(configPath, 'utf8');
72
+ }
73
+ catch (err) {
74
+ const message = err instanceof Error ? err.message : String(err);
75
+ const effective = computeEffectivePdConfig(null);
76
+ return {
77
+ ok: false,
78
+ source: 'malformed',
79
+ configPath,
80
+ errors: [{ path: '', reason: `Failed to read .pd/config.yaml: ${message}`, nextAction: 'Check file permissions for .pd/config.yaml' }],
81
+ warnings: [],
82
+ defaults: effective,
83
+ legacyFilesDetected,
84
+ };
85
+ }
86
+ // 3) Parse YAML — treat as unknown (ERR-001)
87
+ let parsed;
88
+ try {
89
+ parsed = yaml.load(raw, { schema: yaml.JSON_SCHEMA });
90
+ }
91
+ catch (err) {
92
+ const message = err instanceof Error ? err.message : String(err);
93
+ const effective = computeEffectivePdConfig(null);
94
+ return {
95
+ ok: false,
96
+ source: 'malformed',
97
+ configPath,
98
+ errors: [{ path: '', reason: `YAML parse error in .pd/config.yaml: ${message}`, nextAction: 'Fix YAML syntax in .pd/config.yaml' }],
99
+ warnings: [],
100
+ defaults: effective,
101
+ legacyFilesDetected,
102
+ };
103
+ }
104
+ // 4) Validate via core (ERR-001, ERR-005: no `as` bypasses)
105
+ const validationResult = validatePdConfig(parsed);
106
+ if (!validationResult.ok) {
107
+ const effective = computeEffectivePdConfig(null);
108
+ return {
109
+ ok: false,
110
+ source: 'malformed',
111
+ configPath,
112
+ errors: validationResult.errors.map(e => ({
113
+ path: e.path,
114
+ reason: e.reason,
115
+ nextAction: e.nextAction,
116
+ })),
117
+ warnings: [],
118
+ defaults: effective,
119
+ legacyFilesDetected,
120
+ };
121
+ }
122
+ // 5) Compute effective config
123
+ const effective = computeEffectivePdConfig(validationResult.value);
124
+ return {
125
+ ok: true,
126
+ effective,
127
+ source: 'user_config',
128
+ configPath,
129
+ warnings: [
130
+ ...effective.warnings,
131
+ ...(legacyFilesDetected.length > 0
132
+ ? [`Legacy config files detected (${legacyFilesDetected.length}): ${legacyFilesDetected.join(', ')}. PD now uses .pd/config.yaml.`]
133
+ : []),
134
+ ],
135
+ legacyFilesDetected,
136
+ };
137
+ }
138
+ // ── Feature Flags from Config ────────────────────────────────────────────────
139
+ /**
140
+ * Compute feature flags from the loaded PD config.
141
+ * Works with both ok and error results (uses defaults for errors).
142
+ */
143
+ export function computeFlagsFromLoadResult(result) {
144
+ const effective = result.ok ? result.effective : result.defaults;
145
+ return computeFeatureFlagsFromConfig(effective);
146
+ }
147
+ // ── Redacted Summary from Config ─────────────────────────────────────────────
148
+ /**
149
+ * Produce a redacted summary of the PD config for CLI/Console display.
150
+ * Never includes token/API key values or raw provider objects.
151
+ */
152
+ export function redactLoadResult(result) {
153
+ const effective = result.ok ? result.effective : result.defaults;
154
+ return redactPdConfig(effective);
155
+ }
156
+ //# sourceMappingURL=pd-config-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pd-config-loader.js","sourceRoot":"","sources":["../../src/services/pd-config-loader.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principles/pd-cli",
3
- "version": "1.74.0",
3
+ "version": "1.76.0",
4
4
  "description": "PD CLI — Pain recording, sample management, and evolution tasks",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,12 +1,9 @@
1
1
  /**
2
2
  * pd config doctor — Discover and explain PD / OpenClaw configuration state.
3
3
  *
4
- * PRI-299 MVP UX:
5
- * - Reports workspace + OpenClaw config paths and existence
6
- * - Lists effective feature flags and enabled MVP channels
7
- * - Classifies provider/model/auth connectivity (healthy, auth_missing, rate_limit, etc.)
8
- * - Emits `reason` + `nextActions` for failures
9
- * - NEVER leaks tokens, env var values, or raw config bytes
4
+ * PRI-305: Cutover to .pd/config.yaml.
5
+ * - Feature flags and internal agent runtime bindings come from .pd/config.yaml
6
+ * - .pd/feature-flags.yaml and .state/workflows.yaml are no longer production inputs
10
7
  *
11
8
  * Usage:
12
9
  * pd config doctor [--workspace <path>] [--json]
@@ -33,7 +30,8 @@ function formatTextOutput(output: DoctorOutput): string {
33
30
  lines.push('PD config paths:');
34
31
  for (const [k, v] of Object.entries(output.pdConfigPaths)) {
35
32
  const exists = v.exists ? '[exists]' : '[missing]';
36
- lines.push(` ${k.padEnd(16)} ${exists.padEnd(10)} ${v.path}`);
33
+ const parseable = v.parseable === false ? ' [unparseable]' : '';
34
+ lines.push(` ${k.padEnd(16)} ${exists.padEnd(10)}${parseable} ${v.path}`);
37
35
  }
38
36
  lines.push('');
39
37
 
@@ -54,17 +52,34 @@ function formatTextOutput(output: DoctorOutput): string {
54
52
  }
55
53
  lines.push('');
56
54
 
55
+ lines.push('Internal agents:');
56
+ if (output.internalAgents.length === 0) {
57
+ lines.push(' (no internal agents diagnosed)');
58
+ } else {
59
+ for (const agent of output.internalAgents) {
60
+ const readiness = agent.readiness.toUpperCase();
61
+ const enabledLabel = agent.enabled ? 'enabled' : 'disabled';
62
+ lines.push(` ${agent.name}: [${readiness}] (${enabledLabel})`);
63
+ lines.push(` profile: ${agent.runtimeProfileLabel} (${agent.runtimeProfileId})`);
64
+ if (agent.apiKeyEnv) {
65
+ const apiKeyState = agent.apiKeyPresent ? 'present' : 'absent';
66
+ lines.push(` apiKeyEnv: ${agent.apiKeyEnv} (${apiKeyState})`);
67
+ }
68
+ lines.push(` reason: ${agent.reason}`);
69
+ lines.push(` nextAction: ${agent.nextAction}`);
70
+ }
71
+ }
72
+ lines.push('');
73
+
57
74
  lines.push('Provider health:');
58
75
  if (output.providerHealth.length === 0) {
59
76
  lines.push(' (no providers discovered)');
60
77
  } else {
61
78
  for (const p of output.providerHealth) {
62
79
  const cls = p.classification.toUpperCase();
63
- const provider = p.provider ?? '(unset)';
64
- const model = p.model ?? '(unset)';
65
80
  const apiKeyEnv = p.apiKeyEnv ?? '(unset)';
66
81
  const apiKeyState = p.apiKeyPresent ? 'present' : 'absent';
67
- lines.push(` [${cls}] ${provider} / ${model}`);
82
+ lines.push(` [${cls}]`);
68
83
  lines.push(` apiKeyEnv: ${apiKeyEnv} (${apiKeyState})`);
69
84
  lines.push(` source: ${p.source}`);
70
85
  lines.push(` reason: ${p.reason}`);
@@ -73,28 +88,13 @@ function formatTextOutput(output: DoctorOutput): string {
73
88
  }
74
89
  lines.push('');
75
90
 
76
- lines.push('Internal agents:');
77
- if (output.internalAgents && output.internalAgents.correctionObserver) {
78
- const co = output.internalAgents.correctionObserver;
79
- const coStatus = co.status.toUpperCase();
80
- const coProvider = co.provider ?? '(unset)';
81
- const coModel = co.model ?? '(unset)';
82
- const coApiKeyEnv = co.apiKeyEnv ?? '(unset)';
83
- const coApiKeyState = co.apiKeyPresent ? 'present' : 'absent';
84
- lines.push(` correctionObserver: [${coStatus}]`);
85
- lines.push(` enabled: ${co.enabled}`);
86
- lines.push(` flagSource: ${co.flagSource}`);
87
- lines.push(` configSource:${co.configSource}`);
88
- if (co.provider || co.model) {
89
- lines.push(` provider: ${coProvider} / ${coModel}`);
91
+ if (output.legacyFilesDetected.length > 0) {
92
+ lines.push('Legacy files detected (not used for resolution):');
93
+ for (const f of output.legacyFilesDetected) {
94
+ lines.push(` [!] ${f}`);
90
95
  }
91
- lines.push(` apiKeyEnv: ${coApiKeyEnv} (${coApiKeyState})`);
92
- lines.push(` reason: ${co.reason}`);
93
- lines.push(` nextAction: ${co.nextAction}`);
94
- } else {
95
- lines.push(' (no internal agents diagnosed)');
96
+ lines.push('');
96
97
  }
97
- lines.push('');
98
98
 
99
99
  if (output.warnings.length > 0) {
100
100
  lines.push('Warnings:');