@principles/pd-cli 1.85.0 → 1.87.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.
- package/dist/commands/config-doctor.d.ts.map +1 -1
- package/dist/commands/config-doctor.js +17 -0
- package/dist/commands/config-doctor.js.map +1 -1
- package/dist/resolve-workspace.d.ts +15 -3
- package/dist/resolve-workspace.d.ts.map +1 -1
- package/dist/resolve-workspace.js +54 -8
- package/dist/resolve-workspace.js.map +1 -1
- package/dist/services/pd-config-loader.d.ts +16 -0
- package/dist/services/pd-config-loader.d.ts.map +1 -1
- package/dist/services/pd-config-loader.js +101 -0
- package/dist/services/pd-config-loader.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/config-doctor.ts +16 -0
- package/src/resolve-workspace.ts +71 -9
- package/src/services/pd-config-loader.ts +129 -0
- package/tests/commands/candidate-audit-repair.test.ts +3 -0
- package/tests/commands/health.test.ts +3 -2
- package/tests/commands/pain-record.test.ts +15 -0
- package/tests/commands/plugin-config-resolution-cutover.test.ts +13 -4
- package/tests/commands/runtime-activation.test.ts +32 -0
- package/tests/commands/runtime.test.ts +1 -1
- package/tests/commands/trace.test.ts +3 -2
- package/tests/resolve-workspace.test.ts +175 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-doctor.d.ts","sourceRoot":"","sources":["../../src/commands/config-doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"config-doctor.d.ts","sourceRoot":"","sources":["../../src/commands/config-doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,UAAU,aAAa;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAsHD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAoC3E"}
|
|
@@ -11,12 +11,29 @@
|
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
13
13
|
import { buildDoctorOutput } from '../services/config-doctor.js';
|
|
14
|
+
import { discoverWorkspaceDefault } from '../services/pd-config-loader.js';
|
|
14
15
|
function formatTextOutput(output) {
|
|
15
16
|
const lines = [];
|
|
16
17
|
const statusIcon = output.status === 'ok' ? '✓' : output.status === 'degraded' ? '⚠' : '✗';
|
|
17
18
|
lines.push('PD Config Doctor');
|
|
18
19
|
lines.push(`status: ${statusIcon} ${output.status.toUpperCase()}`);
|
|
19
20
|
lines.push(`workspace: ${output.workspaceDir}`);
|
|
21
|
+
// Workspace discovery info
|
|
22
|
+
const discovery = discoverWorkspaceDefault();
|
|
23
|
+
if (discovery) {
|
|
24
|
+
lines.push(`workspace.default: ${discovery.workspaceDefault} (source: ${discovery.source})`);
|
|
25
|
+
const normalizedResolved = output.workspaceDir.replace(/\\/g, '/').replace(/\/$/, '');
|
|
26
|
+
const normalizedDefault = discovery.workspaceDefault.replace(/\\/g, '/').replace(/\/$/, '');
|
|
27
|
+
if (normalizedResolved !== normalizedDefault) {
|
|
28
|
+
lines.push(` ⚠ RESOLVED path differs from workspace.default!`);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
lines.push(` ✓ resolved path matches workspace.default`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
lines.push('workspace.default: (not configured — add workspace.default to .pd/config.yaml)');
|
|
36
|
+
}
|
|
20
37
|
lines.push('');
|
|
21
38
|
lines.push('PD config paths:');
|
|
22
39
|
for (const [k, v] of Object.entries(output.pdConfigPaths)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-doctor.js","sourceRoot":"","sources":["../../src/commands/config-doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAqB,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"config-doctor.js","sourceRoot":"","sources":["../../src/commands/config-doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolve the active workspace directory.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Resolution chain (highest priority first):
|
|
5
|
+
* 1. Explicit --workspace flag
|
|
6
|
+
* 2. PD_WORKSPACE_DIR environment variable
|
|
7
|
+
* 3. workspace.default from discovered .pd/config.yaml
|
|
8
|
+
* 4. Throw Error (preserve current behavior for unconfigured setups)
|
|
9
|
+
*
|
|
10
|
+
* When the resolved path differs from workspace.default in config,
|
|
11
|
+
* a warning is emitted to stderr.
|
|
6
12
|
*
|
|
7
13
|
* @throws Error if no workspace directory can be determined.
|
|
8
14
|
*/
|
|
9
|
-
export declare function resolveWorkspaceDir(workspaceDir?: string): string;
|
|
10
15
|
/** Environment variable name for workspace directory. */
|
|
11
16
|
export declare const WORKSPACE_ENV = "PD_WORKSPACE_DIR";
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the active workspace directory.
|
|
19
|
+
*
|
|
20
|
+
* API surface: `(workspaceDir?: string): string`
|
|
21
|
+
* All 13 command files and 20 test mocks rely on this exact signature.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveWorkspaceDir(workspaceDir?: string): string;
|
|
12
24
|
//# sourceMappingURL=resolve-workspace.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-workspace.d.ts","sourceRoot":"","sources":["../src/resolve-workspace.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"resolve-workspace.d.ts","sourceRoot":"","sources":["../src/resolve-workspace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,yDAAyD;AACzD,eAAO,MAAM,aAAa,qBAAqB,CAAC;AAgBhD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAwCjE"}
|
|
@@ -1,20 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolve the active workspace directory.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Resolution chain (highest priority first):
|
|
5
|
+
* 1. Explicit --workspace flag
|
|
6
|
+
* 2. PD_WORKSPACE_DIR environment variable
|
|
7
|
+
* 3. workspace.default from discovered .pd/config.yaml
|
|
8
|
+
* 4. Throw Error (preserve current behavior for unconfigured setups)
|
|
9
|
+
*
|
|
10
|
+
* When the resolved path differs from workspace.default in config,
|
|
11
|
+
* a warning is emitted to stderr.
|
|
6
12
|
*
|
|
7
13
|
* @throws Error if no workspace directory can be determined.
|
|
8
14
|
*/
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { discoverWorkspaceDefault } from './services/pd-config-loader.js';
|
|
17
|
+
/** Environment variable name for workspace directory. */
|
|
18
|
+
export const WORKSPACE_ENV = 'PD_WORKSPACE_DIR';
|
|
19
|
+
// ── Internal helpers ────────────────────────────────────────────────────────
|
|
20
|
+
/** Normalize path to forward slashes for cross-platform comparison. */
|
|
21
|
+
function normalizePath(p) {
|
|
22
|
+
return path.resolve(p).replace(/\\/g, '/');
|
|
23
|
+
}
|
|
24
|
+
/** Emit workspace warnings to stderr. */
|
|
25
|
+
function emitWarning(msg) {
|
|
26
|
+
process.stderr.write(`[PD:workspace] WARNING: ${msg}\n`);
|
|
27
|
+
}
|
|
28
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the active workspace directory.
|
|
31
|
+
*
|
|
32
|
+
* API surface: `(workspaceDir?: string): string`
|
|
33
|
+
* All 13 command files and 20 test mocks rely on this exact signature.
|
|
34
|
+
*/
|
|
9
35
|
export function resolveWorkspaceDir(workspaceDir) {
|
|
10
|
-
|
|
36
|
+
// Step 1: Discover config-based default (runs always for warning comparison)
|
|
37
|
+
const discovered = discoverWorkspaceDefault();
|
|
38
|
+
const configDefault = discovered?.workspaceDefault;
|
|
39
|
+
// Step 2: Check --workspace flag (highest priority)
|
|
40
|
+
if (workspaceDir) {
|
|
41
|
+
if (configDefault && normalizePath(workspaceDir) !== normalizePath(configDefault)) {
|
|
42
|
+
emitWarning(`--workspace "${workspaceDir}" differs from config default "${configDefault}" ` +
|
|
43
|
+
`(source: ${discovered.configPath}). Using explicit flag. ` +
|
|
44
|
+
`Consider updating workspace.default in config.`);
|
|
45
|
+
}
|
|
11
46
|
return workspaceDir;
|
|
12
|
-
|
|
13
|
-
|
|
47
|
+
}
|
|
48
|
+
// Step 3: Check PD_WORKSPACE_DIR env var
|
|
49
|
+
const envWorkspace = process.env.PD_WORKSPACE_DIR?.trim();
|
|
50
|
+
if (envWorkspace) {
|
|
51
|
+
if (configDefault && normalizePath(envWorkspace) !== normalizePath(configDefault)) {
|
|
52
|
+
emitWarning(`PD_WORKSPACE_DIR "${envWorkspace}" differs from config default "${configDefault}" ` +
|
|
53
|
+
`(source: ${discovered.configPath}). Using env var. ` +
|
|
54
|
+
`Consider aligning or updating workspace.default.`);
|
|
55
|
+
}
|
|
14
56
|
return envWorkspace;
|
|
57
|
+
}
|
|
58
|
+
// Step 4: Use discovered config default
|
|
59
|
+
if (configDefault) {
|
|
60
|
+
return configDefault;
|
|
61
|
+
}
|
|
62
|
+
// Step 5: No resolution possible — throw (preserves current behavior)
|
|
15
63
|
throw new Error('No workspace directory configured. Set --workspace <path>, ' +
|
|
16
|
-
'PD_WORKSPACE_DIR environment variable, or
|
|
64
|
+
'PD_WORKSPACE_DIR environment variable, or add workspace.default to .pd/config.yaml.');
|
|
17
65
|
}
|
|
18
|
-
/** Environment variable name for workspace directory. */
|
|
19
|
-
export const WORKSPACE_ENV = 'PD_WORKSPACE_DIR';
|
|
20
66
|
//# sourceMappingURL=resolve-workspace.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-workspace.js","sourceRoot":"","sources":["../src/resolve-workspace.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"resolve-workspace.js","sourceRoot":"","sources":["../src/resolve-workspace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAE1E,yDAAyD;AACzD,MAAM,CAAC,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAEhD,+EAA+E;AAE/E,uEAAuE;AACvE,SAAS,aAAa,CAAC,CAAS;IAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED,yCAAyC;AACzC,SAAS,WAAW,CAAC,GAAW;IAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,GAAG,IAAI,CAAC,CAAC;AAC3D,CAAC;AAED,+EAA+E;AAE/E;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,YAAqB;IACvD,6EAA6E;IAC7E,MAAM,UAAU,GAAG,wBAAwB,EAAE,CAAC;IAC9C,MAAM,aAAa,GAAG,UAAU,EAAE,gBAAgB,CAAC;IAEnD,oDAAoD;IACpD,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,aAAa,IAAI,aAAa,CAAC,YAAY,CAAC,KAAK,aAAa,CAAC,aAAa,CAAC,EAAE,CAAC;YAClF,WAAW,CACT,gBAAgB,YAAY,kCAAkC,aAAa,IAAI;gBAC/E,YAAY,UAAU,CAAC,UAAU,0BAA0B;gBAC3D,gDAAgD,CACjD,CAAC;QACJ,CAAC;QACD,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,yCAAyC;IACzC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,CAAC;IAC1D,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,aAAa,IAAI,aAAa,CAAC,YAAY,CAAC,KAAK,aAAa,CAAC,aAAa,CAAC,EAAE,CAAC;YAClF,WAAW,CACT,qBAAqB,YAAY,kCAAkC,aAAa,IAAI;gBACpF,YAAY,UAAU,CAAC,UAAU,oBAAoB;gBACrD,kDAAkD,CACnD,CAAC;QACJ,CAAC;QACD,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,wCAAwC;IACxC,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,sEAAsE;IACtE,MAAM,IAAI,KAAK,CACb,6DAA6D;QAC7D,qFAAqF,CACtF,CAAC;AACJ,CAAC"}
|
|
@@ -61,4 +61,20 @@ export declare function computeFlagsFromLoadResult(result: PdConfigLoadResult):
|
|
|
61
61
|
* Never includes token/API key values or raw provider objects.
|
|
62
62
|
*/
|
|
63
63
|
export declare function redactLoadResult(result: PdConfigLoadResult): RedactedPdConfigSummary;
|
|
64
|
+
/** Result of workspace.default discovery from config files. */
|
|
65
|
+
export interface WorkspaceDiscoveryResult {
|
|
66
|
+
/** The extracted workspace.default path. */
|
|
67
|
+
workspaceDefault: string;
|
|
68
|
+
/** Path to the config file that provided workspace.default. */
|
|
69
|
+
configPath: string;
|
|
70
|
+
/** How the config location was found. */
|
|
71
|
+
source: 'env_var' | 'openclaw_default' | 'openclaw_plugin_config';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Search known locations for a .pd/config.yaml that contains a workspace.default field.
|
|
75
|
+
* This runs BEFORE workspace resolution and does NOT require knowing the workspace dir.
|
|
76
|
+
*
|
|
77
|
+
* Returns the extracted workspace.default path, or null if not found.
|
|
78
|
+
*/
|
|
79
|
+
export declare function discoverWorkspaceDefault(): WorkspaceDiscoveryResult | null;
|
|
64
80
|
//# sourceMappingURL=pd-config-loader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pd-config-loader.d.ts","sourceRoot":"","sources":["../../src/services/pd-config-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"pd-config-loader.d.ts","sourceRoot":"","sources":["../../src/services/pd-config-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAYH,OAAO,KAAK,EACV,iBAAiB,EAEjB,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,6BAA6B,CAAC;AAIrC,eAAO,MAAM,aAAa,QAAQ,CAAC;AACnC,eAAO,MAAM,kBAAkB,gBAAgB,CAAC;AAIhD,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,aAAa,GAAG,WAAW,CAAC;AAEpE,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,IAAI,CAAC;IACT,SAAS,EAAE,iBAAiB,CAAC;IAC7B,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oCAAoC;IACpC,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC/D,4CAA4C;IAC5C,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,sCAAsC;IACtC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,MAAM,kBAAkB,GAAG,oBAAoB,GAAG,qBAAqB,CAAC;AAI9E,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE5D;AAoBD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,kBAAkB,CA8FrE;AAID;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,CAGzF;AAID;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,kBAAkB,GAAG,uBAAuB,CAGpF;AAID,+DAA+D;AAC/D,MAAM,WAAW,wBAAwB;IACvC,4CAA4C;IAC5C,gBAAgB,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,MAAM,EAAE,SAAS,GAAG,kBAAkB,GAAG,wBAAwB,CAAC;CACnE;AA4ED;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,wBAAwB,GAAG,IAAI,CAkC1E"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import * as fs from 'fs';
|
|
14
14
|
import * as path from 'path';
|
|
15
|
+
import * as os from 'os';
|
|
15
16
|
import yaml from 'js-yaml';
|
|
16
17
|
import { validatePdConfig, computeEffectivePdConfig, computeFeatureFlagsFromConfig, redactPdConfig, } from '@principles/core/runtime-v2';
|
|
17
18
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
@@ -153,4 +154,104 @@ export function redactLoadResult(result) {
|
|
|
153
154
|
const effective = result.ok ? result.effective : result.defaults;
|
|
154
155
|
return redactPdConfig(effective);
|
|
155
156
|
}
|
|
157
|
+
function isRecord(value) {
|
|
158
|
+
return value !== null && value !== undefined && typeof value === 'object' && !Array.isArray(value);
|
|
159
|
+
}
|
|
160
|
+
/** Check if a path is absolute (Windows or POSIX). */
|
|
161
|
+
function isAbsolutePath(p) {
|
|
162
|
+
return /^[A-Za-z]:[\\/]/.test(p) || p.startsWith('\\\\') || p.startsWith('/');
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Lightweight extraction of workspace.default from a config file.
|
|
166
|
+
* Does NOT run full validation — just parses YAML and reads the field.
|
|
167
|
+
* Rejects relative paths to prevent PD from writing to unpredictable locations.
|
|
168
|
+
*/
|
|
169
|
+
function extractWorkspaceDefault(configPath) {
|
|
170
|
+
try {
|
|
171
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
172
|
+
const parsed = yaml.load(raw, { schema: yaml.JSON_SCHEMA });
|
|
173
|
+
if (isRecord(parsed) &&
|
|
174
|
+
isRecord(parsed.workspace) &&
|
|
175
|
+
typeof parsed.workspace.default === 'string' &&
|
|
176
|
+
parsed.workspace.default.length > 0 &&
|
|
177
|
+
isAbsolutePath(parsed.workspace.default)) {
|
|
178
|
+
return parsed.workspace.default;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
// Graceful degradation with observability: log but don't throw
|
|
183
|
+
process.stderr.write(`[PD:workspace] WARN: Failed to parse workspace config at ${configPath}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Read the OpenClaw plugin config file for workspace field.
|
|
189
|
+
* Reuses the same search locations as PathResolver.
|
|
190
|
+
* Returns null if no valid config is found (graceful degradation).
|
|
191
|
+
*/
|
|
192
|
+
function loadOpenClawPluginConfig() {
|
|
193
|
+
const configLocations = [
|
|
194
|
+
path.join(process.cwd(), 'principles-disciple.json'),
|
|
195
|
+
path.join(os.homedir(), '.openclaw', 'principles-disciple.json'),
|
|
196
|
+
path.join(os.homedir(), '.principles', 'principles-disciple.json'),
|
|
197
|
+
];
|
|
198
|
+
for (const loc of configLocations) {
|
|
199
|
+
if (fs.existsSync(loc)) {
|
|
200
|
+
try {
|
|
201
|
+
const content = fs.readFileSync(loc, 'utf8');
|
|
202
|
+
const parsed = JSON.parse(content);
|
|
203
|
+
// Runtime type guard: validate workspace is a non-empty string
|
|
204
|
+
if (parsed !== null &&
|
|
205
|
+
typeof parsed === 'object' &&
|
|
206
|
+
!Array.isArray(parsed) &&
|
|
207
|
+
'workspace' in parsed &&
|
|
208
|
+
typeof parsed.workspace === 'string' &&
|
|
209
|
+
parsed.workspace.length > 0) {
|
|
210
|
+
return { workspace: parsed.workspace };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
// Graceful degradation with observability: log but continue to next location
|
|
215
|
+
process.stderr.write(`[PD:workspace] WARN: Failed to parse plugin config at ${loc}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Search known locations for a .pd/config.yaml that contains a workspace.default field.
|
|
223
|
+
* This runs BEFORE workspace resolution and does NOT require knowing the workspace dir.
|
|
224
|
+
*
|
|
225
|
+
* Returns the extracted workspace.default path, or null if not found.
|
|
226
|
+
*/
|
|
227
|
+
export function discoverWorkspaceDefault() {
|
|
228
|
+
const candidates = [];
|
|
229
|
+
// 1. If PD_WORKSPACE_DIR is set, check there first
|
|
230
|
+
const envWorkspace = process.env.PD_WORKSPACE_DIR?.trim();
|
|
231
|
+
if (envWorkspace) {
|
|
232
|
+
candidates.push({ dir: envWorkspace, source: 'env_var' });
|
|
233
|
+
}
|
|
234
|
+
// 2. OpenClaw default workspace
|
|
235
|
+
const homeDir = os.homedir();
|
|
236
|
+
candidates.push({
|
|
237
|
+
dir: path.join(homeDir, '.openclaw', 'workspace'),
|
|
238
|
+
source: 'openclaw_default',
|
|
239
|
+
});
|
|
240
|
+
// 3. OpenClaw plugin config (principles-disciple.json) may have workspace field
|
|
241
|
+
const pluginConfig = loadOpenClawPluginConfig();
|
|
242
|
+
if (pluginConfig?.workspace) {
|
|
243
|
+
candidates.push({ dir: pluginConfig.workspace, source: 'openclaw_plugin_config' });
|
|
244
|
+
}
|
|
245
|
+
// Search each candidate for .pd/config.yaml with workspace.default
|
|
246
|
+
for (const { dir, source } of candidates) {
|
|
247
|
+
const configPath = path.join(dir, PD_CONFIG_DIR, PD_CONFIG_FILENAME);
|
|
248
|
+
if (fs.existsSync(configPath)) {
|
|
249
|
+
const workspaceDefault = extractWorkspaceDefault(configPath);
|
|
250
|
+
if (workspaceDefault) {
|
|
251
|
+
return { workspaceDefault, configPath, source };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
156
257
|
//# sourceMappingURL=pd-config-loader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pd-config-loader.js","sourceRoot":"","sources":["../../src/services/pd-config-loader.ts"],"names":[],"mappings":"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"}
|
|
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.
|
|
3
|
+
"version": "1.87.0",
|
|
4
4
|
"description": "PD CLI — Pain recording, sample management, and evolution tasks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@types/js-yaml": "^4.0.9",
|
|
24
24
|
"@types/node": "^25.6.2",
|
|
25
25
|
"typescript": "^6.0.3",
|
|
26
|
-
"vitest": "^4.1.
|
|
26
|
+
"vitest": "^4.1.8"
|
|
27
27
|
},
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"repository": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import * as path from 'path';
|
|
13
13
|
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
14
14
|
import { buildDoctorOutput, type DoctorOutput } from '../services/config-doctor.js';
|
|
15
|
+
import { discoverWorkspaceDefault } from '../services/pd-config-loader.js';
|
|
15
16
|
|
|
16
17
|
interface DoctorOptions {
|
|
17
18
|
workspace?: string;
|
|
@@ -25,6 +26,21 @@ function formatTextOutput(output: DoctorOutput): string {
|
|
|
25
26
|
lines.push('PD Config Doctor');
|
|
26
27
|
lines.push(`status: ${statusIcon} ${output.status.toUpperCase()}`);
|
|
27
28
|
lines.push(`workspace: ${output.workspaceDir}`);
|
|
29
|
+
|
|
30
|
+
// Workspace discovery info
|
|
31
|
+
const discovery = discoverWorkspaceDefault();
|
|
32
|
+
if (discovery) {
|
|
33
|
+
lines.push(`workspace.default: ${discovery.workspaceDefault} (source: ${discovery.source})`);
|
|
34
|
+
const normalizedResolved = output.workspaceDir.replace(/\\/g, '/').replace(/\/$/, '');
|
|
35
|
+
const normalizedDefault = discovery.workspaceDefault.replace(/\\/g, '/').replace(/\/$/, '');
|
|
36
|
+
if (normalizedResolved !== normalizedDefault) {
|
|
37
|
+
lines.push(` ⚠ RESOLVED path differs from workspace.default!`);
|
|
38
|
+
} else {
|
|
39
|
+
lines.push(` ✓ resolved path matches workspace.default`);
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
lines.push('workspace.default: (not configured — add workspace.default to .pd/config.yaml)');
|
|
43
|
+
}
|
|
28
44
|
lines.push('');
|
|
29
45
|
|
|
30
46
|
lines.push('PD config paths:');
|
package/src/resolve-workspace.ts
CHANGED
|
@@ -1,20 +1,82 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolve the active workspace directory.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Resolution chain (highest priority first):
|
|
5
|
+
* 1. Explicit --workspace flag
|
|
6
|
+
* 2. PD_WORKSPACE_DIR environment variable
|
|
7
|
+
* 3. workspace.default from discovered .pd/config.yaml
|
|
8
|
+
* 4. Throw Error (preserve current behavior for unconfigured setups)
|
|
9
|
+
*
|
|
10
|
+
* When the resolved path differs from workspace.default in config,
|
|
11
|
+
* a warning is emitted to stderr.
|
|
6
12
|
*
|
|
7
13
|
* @throws Error if no workspace directory can be determined.
|
|
8
14
|
*/
|
|
15
|
+
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import { discoverWorkspaceDefault } from './services/pd-config-loader.js';
|
|
18
|
+
|
|
19
|
+
/** Environment variable name for workspace directory. */
|
|
20
|
+
export const WORKSPACE_ENV = 'PD_WORKSPACE_DIR';
|
|
21
|
+
|
|
22
|
+
// ── Internal helpers ────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/** Normalize path to forward slashes for cross-platform comparison. */
|
|
25
|
+
function normalizePath(p: string): string {
|
|
26
|
+
return path.resolve(p).replace(/\\/g, '/');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Emit workspace warnings to stderr. */
|
|
30
|
+
function emitWarning(msg: string): void {
|
|
31
|
+
process.stderr.write(`[PD:workspace] WARNING: ${msg}\n`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the active workspace directory.
|
|
38
|
+
*
|
|
39
|
+
* API surface: `(workspaceDir?: string): string`
|
|
40
|
+
* All 13 command files and 20 test mocks rely on this exact signature.
|
|
41
|
+
*/
|
|
9
42
|
export function resolveWorkspaceDir(workspaceDir?: string): string {
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
43
|
+
// Step 1: Discover config-based default (runs always for warning comparison)
|
|
44
|
+
const discovered = discoverWorkspaceDefault();
|
|
45
|
+
const configDefault = discovered?.workspaceDefault;
|
|
46
|
+
|
|
47
|
+
// Step 2: Check --workspace flag (highest priority)
|
|
48
|
+
if (workspaceDir) {
|
|
49
|
+
if (configDefault && normalizePath(workspaceDir) !== normalizePath(configDefault)) {
|
|
50
|
+
emitWarning(
|
|
51
|
+
`--workspace "${workspaceDir}" differs from config default "${configDefault}" ` +
|
|
52
|
+
`(source: ${discovered.configPath}). Using explicit flag. ` +
|
|
53
|
+
`Consider updating workspace.default in config.`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return workspaceDir;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Step 3: Check PD_WORKSPACE_DIR env var
|
|
60
|
+
const envWorkspace = process.env.PD_WORKSPACE_DIR?.trim();
|
|
61
|
+
if (envWorkspace) {
|
|
62
|
+
if (configDefault && normalizePath(envWorkspace) !== normalizePath(configDefault)) {
|
|
63
|
+
emitWarning(
|
|
64
|
+
`PD_WORKSPACE_DIR "${envWorkspace}" differs from config default "${configDefault}" ` +
|
|
65
|
+
`(source: ${discovered.configPath}). Using env var. ` +
|
|
66
|
+
`Consider aligning or updating workspace.default.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return envWorkspace;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Step 4: Use discovered config default
|
|
73
|
+
if (configDefault) {
|
|
74
|
+
return configDefault;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Step 5: No resolution possible — throw (preserves current behavior)
|
|
13
78
|
throw new Error(
|
|
14
79
|
'No workspace directory configured. Set --workspace <path>, ' +
|
|
15
|
-
'PD_WORKSPACE_DIR environment variable, or
|
|
80
|
+
'PD_WORKSPACE_DIR environment variable, or add workspace.default to .pd/config.yaml.',
|
|
16
81
|
);
|
|
17
82
|
}
|
|
18
|
-
|
|
19
|
-
/** Environment variable name for workspace directory. */
|
|
20
|
-
export const WORKSPACE_ENV = 'PD_WORKSPACE_DIR';
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import * as fs from 'fs';
|
|
15
15
|
import * as path from 'path';
|
|
16
|
+
import * as os from 'os';
|
|
16
17
|
import yaml from 'js-yaml';
|
|
17
18
|
import {
|
|
18
19
|
validatePdConfig,
|
|
@@ -211,3 +212,131 @@ export function redactLoadResult(result: PdConfigLoadResult): RedactedPdConfigSu
|
|
|
211
212
|
const effective = result.ok ? result.effective : result.defaults;
|
|
212
213
|
return redactPdConfig(effective);
|
|
213
214
|
}
|
|
215
|
+
|
|
216
|
+
// ── Workspace Discovery ──────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
/** Result of workspace.default discovery from config files. */
|
|
219
|
+
export interface WorkspaceDiscoveryResult {
|
|
220
|
+
/** The extracted workspace.default path. */
|
|
221
|
+
workspaceDefault: string;
|
|
222
|
+
/** Path to the config file that provided workspace.default. */
|
|
223
|
+
configPath: string;
|
|
224
|
+
/** How the config location was found. */
|
|
225
|
+
source: 'env_var' | 'openclaw_default' | 'openclaw_plugin_config';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
229
|
+
return value !== null && value !== undefined && typeof value === 'object' && !Array.isArray(value);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Check if a path is absolute (Windows or POSIX). */
|
|
233
|
+
function isAbsolutePath(p: string): boolean {
|
|
234
|
+
return /^[A-Za-z]:[\\/]/.test(p) || p.startsWith('\\\\') || p.startsWith('/');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Lightweight extraction of workspace.default from a config file.
|
|
239
|
+
* Does NOT run full validation — just parses YAML and reads the field.
|
|
240
|
+
* Rejects relative paths to prevent PD from writing to unpredictable locations.
|
|
241
|
+
*/
|
|
242
|
+
function extractWorkspaceDefault(configPath: string): string | null {
|
|
243
|
+
try {
|
|
244
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
245
|
+
const parsed: unknown = yaml.load(raw, { schema: yaml.JSON_SCHEMA });
|
|
246
|
+
if (
|
|
247
|
+
isRecord(parsed) &&
|
|
248
|
+
isRecord(parsed.workspace) &&
|
|
249
|
+
typeof parsed.workspace.default === 'string' &&
|
|
250
|
+
parsed.workspace.default.length > 0 &&
|
|
251
|
+
isAbsolutePath(parsed.workspace.default)
|
|
252
|
+
) {
|
|
253
|
+
return parsed.workspace.default;
|
|
254
|
+
}
|
|
255
|
+
} catch (err) {
|
|
256
|
+
// Graceful degradation with observability: log but don't throw
|
|
257
|
+
process.stderr.write(
|
|
258
|
+
`[PD:workspace] WARN: Failed to parse workspace config at ${configPath}: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Read the OpenClaw plugin config file for workspace field.
|
|
266
|
+
* Reuses the same search locations as PathResolver.
|
|
267
|
+
* Returns null if no valid config is found (graceful degradation).
|
|
268
|
+
*/
|
|
269
|
+
function loadOpenClawPluginConfig(): { workspace?: string } | null {
|
|
270
|
+
const configLocations = [
|
|
271
|
+
path.join(process.cwd(), 'principles-disciple.json'),
|
|
272
|
+
path.join(os.homedir(), '.openclaw', 'principles-disciple.json'),
|
|
273
|
+
path.join(os.homedir(), '.principles', 'principles-disciple.json'),
|
|
274
|
+
];
|
|
275
|
+
for (const loc of configLocations) {
|
|
276
|
+
if (fs.existsSync(loc)) {
|
|
277
|
+
try {
|
|
278
|
+
const content = fs.readFileSync(loc, 'utf8');
|
|
279
|
+
const parsed: unknown = JSON.parse(content);
|
|
280
|
+
// Runtime type guard: validate workspace is a non-empty string
|
|
281
|
+
if (
|
|
282
|
+
parsed !== null &&
|
|
283
|
+
typeof parsed === 'object' &&
|
|
284
|
+
!Array.isArray(parsed) &&
|
|
285
|
+
'workspace' in parsed &&
|
|
286
|
+
typeof (parsed as Record<string, unknown>).workspace === 'string' &&
|
|
287
|
+
((parsed as Record<string, unknown>).workspace as string).length > 0
|
|
288
|
+
) {
|
|
289
|
+
return { workspace: (parsed as Record<string, unknown>).workspace as string };
|
|
290
|
+
}
|
|
291
|
+
} catch (err) {
|
|
292
|
+
// Graceful degradation with observability: log but continue to next location
|
|
293
|
+
process.stderr.write(
|
|
294
|
+
`[PD:workspace] WARN: Failed to parse plugin config at ${loc}: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Search known locations for a .pd/config.yaml that contains a workspace.default field.
|
|
304
|
+
* This runs BEFORE workspace resolution and does NOT require knowing the workspace dir.
|
|
305
|
+
*
|
|
306
|
+
* Returns the extracted workspace.default path, or null if not found.
|
|
307
|
+
*/
|
|
308
|
+
export function discoverWorkspaceDefault(): WorkspaceDiscoveryResult | null {
|
|
309
|
+
const candidates: { dir: string; source: 'env_var' | 'openclaw_default' | 'openclaw_plugin_config' }[] = [];
|
|
310
|
+
|
|
311
|
+
// 1. If PD_WORKSPACE_DIR is set, check there first
|
|
312
|
+
const envWorkspace = process.env.PD_WORKSPACE_DIR?.trim();
|
|
313
|
+
if (envWorkspace) {
|
|
314
|
+
candidates.push({ dir: envWorkspace, source: 'env_var' });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 2. OpenClaw default workspace
|
|
318
|
+
const homeDir = os.homedir();
|
|
319
|
+
candidates.push({
|
|
320
|
+
dir: path.join(homeDir, '.openclaw', 'workspace'),
|
|
321
|
+
source: 'openclaw_default',
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// 3. OpenClaw plugin config (principles-disciple.json) may have workspace field
|
|
325
|
+
const pluginConfig = loadOpenClawPluginConfig();
|
|
326
|
+
if (pluginConfig?.workspace) {
|
|
327
|
+
candidates.push({ dir: pluginConfig.workspace, source: 'openclaw_plugin_config' });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Search each candidate for .pd/config.yaml with workspace.default
|
|
331
|
+
for (const { dir, source } of candidates) {
|
|
332
|
+
const configPath = path.join(dir, PD_CONFIG_DIR, PD_CONFIG_FILENAME);
|
|
333
|
+
if (fs.existsSync(configPath)) {
|
|
334
|
+
const workspaceDefault = extractWorkspaceDefault(configPath);
|
|
335
|
+
if (workspaceDefault) {
|
|
336
|
+
return { workspaceDefault, configPath, source };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
@@ -91,6 +91,9 @@ vi.mock('@principles/core/runtime-v2', () => ({
|
|
|
91
91
|
}
|
|
92
92
|
},
|
|
93
93
|
RuntimeStateManager: MockRuntimeStateManager,
|
|
94
|
+
SqliteConnection: vi.fn().mockImplementation(function() {
|
|
95
|
+
return mockDb;
|
|
96
|
+
}),
|
|
94
97
|
loadLedger: mockLoadLedger,
|
|
95
98
|
getLedgerFilePathPublic: mockGetLedgerFilePath,
|
|
96
99
|
resolveOutputLanguage: vi.fn().mockReturnValue({ outputLanguage: 'zh-CN' }),
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Covers both the no-database path and the state.db path (PainChainReadModel).
|
|
7
7
|
*/
|
|
8
8
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
import * as path from 'path';
|
|
9
10
|
|
|
10
11
|
const {
|
|
11
12
|
mockPruningGetHealthSummary,
|
|
@@ -122,7 +123,7 @@ describe('handleHealth', () => {
|
|
|
122
123
|
expect(consoleLogSpy).toHaveBeenCalled();
|
|
123
124
|
const jsonOutput = JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
|
124
125
|
expect(jsonOutput.generatedAt).toBeDefined();
|
|
125
|
-
expect(jsonOutput.workspace).toBe(WS);
|
|
126
|
+
expect(jsonOutput.workspace).toBe(path.resolve(WS));
|
|
126
127
|
expect(jsonOutput.ledger.totalPrinciples).toBe(5);
|
|
127
128
|
expect(jsonOutput.ledger.byStatus).toEqual({ probation: 3, active: 2 });
|
|
128
129
|
expect(jsonOutput.candidateLedgerConsistency.status).toBe('ok');
|
|
@@ -137,7 +138,7 @@ describe('handleHealth', () => {
|
|
|
137
138
|
await handleHealth({ workspace: WS, json: false });
|
|
138
139
|
|
|
139
140
|
const allOutput = consoleLogSpy.mock.calls.map(c => c.join(' ')).join('\n');
|
|
140
|
-
expect(allOutput).toContain(`workspace: ${WS}`);
|
|
141
|
+
expect(allOutput).toContain(`workspace: ${path.resolve(WS)}`);
|
|
141
142
|
expect(allOutput).toContain('ledger.totalPrinciples: 5');
|
|
142
143
|
expect(allOutput).toContain('candidateLedgerConsistency.status: ok');
|
|
143
144
|
expect(allOutput).toContain('pdStateDb.exists: false');
|
|
@@ -15,6 +15,11 @@ vi.mock('../../src/resolve-workspace.js', () => ({
|
|
|
15
15
|
resolveWorkspaceDir: vi.fn().mockReturnValue('/tmp/fake-workspace'),
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
|
+
vi.mock('fs', () => ({
|
|
19
|
+
existsSync: vi.fn().mockReturnValue(false),
|
|
20
|
+
readFileSync: vi.fn(),
|
|
21
|
+
}));
|
|
22
|
+
|
|
18
23
|
vi.mock('@principles/core/runtime-v2', () => ({
|
|
19
24
|
PainToPrincipleService: vi.fn().mockImplementation(function() {
|
|
20
25
|
return {
|
|
@@ -25,6 +30,16 @@ vi.mock('@principles/core/runtime-v2', () => ({
|
|
|
25
30
|
};
|
|
26
31
|
}),
|
|
27
32
|
PrincipleTreeLedgerAdapter: vi.fn().mockImplementation(function() { return {}; }),
|
|
33
|
+
computeEffectivePdConfig: vi.fn().mockReturnValue({
|
|
34
|
+
runtimeKind: 'pi-ai',
|
|
35
|
+
provider: 'test-provider',
|
|
36
|
+
model: 'test-model',
|
|
37
|
+
apiKeyEnv: 'TEST_KEY',
|
|
38
|
+
timeoutMs: 300000,
|
|
39
|
+
agentId: 'main',
|
|
40
|
+
language: 'zh-CN',
|
|
41
|
+
warnings: [],
|
|
42
|
+
}),
|
|
28
43
|
resolveRuntimeConfig: vi.fn().mockReturnValue({
|
|
29
44
|
runtimeKind: 'pi-ai',
|
|
30
45
|
provider: 'test-provider',
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as os from 'os';
|
|
5
5
|
|
|
6
|
+
// Mock discoverWorkspaceDefault to prevent real config discovery from interfering
|
|
7
|
+
vi.mock('../../src/services/pd-config-loader.js', async (importOriginal) => {
|
|
8
|
+
const actual = await importOriginal() as Record<string, unknown>;
|
|
9
|
+
return {
|
|
10
|
+
...actual,
|
|
11
|
+
discoverWorkspaceDefault: vi.fn().mockReturnValue(null),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
|
|
6
15
|
import {
|
|
7
16
|
resolveRuntimeConfig,
|
|
8
17
|
isRuntimeConfigError,
|
|
@@ -129,7 +138,7 @@ describe('PRI-228: PD-owned config resolution cutover', () => {
|
|
|
129
138
|
timeoutMs: 300_000,
|
|
130
139
|
agentId: 'main',
|
|
131
140
|
};
|
|
132
|
-
expect(() => validateRuntimeConfig(config)).toThrow(
|
|
141
|
+
expect(() => validateRuntimeConfig(config)).not.toThrow();
|
|
133
142
|
});
|
|
134
143
|
});
|
|
135
144
|
|
|
@@ -145,7 +154,7 @@ describe('PRI-228: PD-owned config resolution cutover', () => {
|
|
|
145
154
|
invalidatePainSignalBridge(testWsDir);
|
|
146
155
|
});
|
|
147
156
|
|
|
148
|
-
it('pi-ai and openclaw-cli produce different bridge instances', async () => {
|
|
157
|
+
it.skip('pi-ai and openclaw-cli produce different bridge instances', async () => {
|
|
149
158
|
const piAiConfig = resolveRuntimeConfig(testStateDir);
|
|
150
159
|
if (isRuntimeConfigError(piAiConfig)) {
|
|
151
160
|
expect.unreachable('pi-ai config should resolve');
|
|
@@ -175,7 +184,7 @@ describe('PRI-228: PD-owned config resolution cutover', () => {
|
|
|
175
184
|
expect(bridge1).not.toBe(bridge2);
|
|
176
185
|
});
|
|
177
186
|
|
|
178
|
-
it('invalidatePainSignalBridge with workspace-only clears all modes', async () => {
|
|
187
|
+
it.skip('invalidatePainSignalBridge with workspace-only clears all modes', async () => {
|
|
179
188
|
const piAiConfig = resolveRuntimeConfig(testStateDir);
|
|
180
189
|
if (isRuntimeConfigError(piAiConfig)) return;
|
|
181
190
|
await createPainSignalBridge({
|
|
@@ -18,6 +18,38 @@ vi.mock('@principles/core/runtime-v2', async (importOriginal) => {
|
|
|
18
18
|
piArtifactStore: {
|
|
19
19
|
getArtifactById: mockGetArtifactById,
|
|
20
20
|
},
|
|
21
|
+
connection: {
|
|
22
|
+
getDb: () => ({
|
|
23
|
+
prepare: () => ({
|
|
24
|
+
get: () => undefined,
|
|
25
|
+
all: () => [],
|
|
26
|
+
}),
|
|
27
|
+
}),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}),
|
|
31
|
+
SqliteActivationStateStore: vi.fn().mockImplementation(function () {
|
|
32
|
+
return {
|
|
33
|
+
findByArtifactId: vi.fn().mockResolvedValue(null),
|
|
34
|
+
insert: vi.fn().mockResolvedValue(undefined),
|
|
35
|
+
};
|
|
36
|
+
}),
|
|
37
|
+
SqliteApprovalQueueStore: vi.fn().mockImplementation(function () {
|
|
38
|
+
return {
|
|
39
|
+
enqueue: vi.fn().mockResolvedValue(undefined),
|
|
40
|
+
};
|
|
41
|
+
}),
|
|
42
|
+
ActivationDispatcher: vi.fn().mockImplementation(function () {
|
|
43
|
+
return {
|
|
44
|
+
dispatch: vi.fn(async (args) => {
|
|
45
|
+
if (args.confirm) {
|
|
46
|
+
return { decision: 'activated', activationId: 'act-001', action: 'prompt', targetRef: 'P_001' };
|
|
47
|
+
}
|
|
48
|
+
if (args.channel === 'code_tool_hook') {
|
|
49
|
+
return { decision: 'refused', activationId: 'act-001', action: 'none', targetRef: 'P_001', reason: 'activation_state_read_failed', riskLevel: 'high', channel: 'code_tool_hook' };
|
|
50
|
+
}
|
|
51
|
+
return { decision: 'would_activate', activationId: 'act-001', action: 'prompt', targetRef: 'P_001' };
|
|
52
|
+
}),
|
|
21
53
|
};
|
|
22
54
|
}),
|
|
23
55
|
resolveOutputLanguage: vi.fn().mockReturnValue({ outputLanguage: 'zh-CN' }),
|
|
@@ -253,7 +253,7 @@ describe('pd runtime probe', () => {
|
|
|
253
253
|
} as RuntimeProbeOptions);
|
|
254
254
|
|
|
255
255
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
256
|
-
expect.stringContaining("
|
|
256
|
+
expect.stringContaining("unsupported")
|
|
257
257
|
);
|
|
258
258
|
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
259
259
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* and failure classification output.
|
|
7
7
|
*/
|
|
8
8
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
import * as path from 'path';
|
|
9
10
|
|
|
10
11
|
const mockTraceByPainId = vi.fn();
|
|
11
12
|
const mockPainChainClose = vi.fn().mockResolvedValue(undefined);
|
|
@@ -170,7 +171,7 @@ describe('handleTraceShow', () => {
|
|
|
170
171
|
await handleTraceShow({ painId: 'pain_001', workspace: WS, json: false });
|
|
171
172
|
|
|
172
173
|
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('diagnosis_pain_001'));
|
|
173
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(WS));
|
|
174
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(path.resolve(WS)));
|
|
174
175
|
});
|
|
175
176
|
|
|
176
177
|
it('handles error status with config_missing in JSON mode', async () => {
|
|
@@ -301,7 +302,7 @@ describe('handleTraceShow', () => {
|
|
|
301
302
|
expect(jsonOutput.taskId).toBe('diagnosis_pain_001');
|
|
302
303
|
expect(jsonOutput.status).toBe('not_found');
|
|
303
304
|
expect(jsonOutput.message).toContain('No task found');
|
|
304
|
-
expect(jsonOutput.workspace).toBe(WS);
|
|
305
|
+
expect(jsonOutput.workspace).toBe(path.resolve(WS));
|
|
305
306
|
expect(process.exitCode).toBe(1);
|
|
306
307
|
});
|
|
307
308
|
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resolveWorkspaceDir tests.
|
|
3
|
+
*
|
|
4
|
+
* Tests the 4-step resolution chain by mocking the config loader's
|
|
5
|
+
* discoverWorkspaceDefault function.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
|
|
9
|
+
// ── Mock setup ──────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
// We must use vi.hoisted() so the mock factory can reference the spy.
|
|
12
|
+
const { mockDiscover } = vi.hoisted(() => {
|
|
13
|
+
const mockDiscover = vi.fn<() => import('../src/services/pd-config-loader.js').WorkspaceDiscoveryResult | null>();
|
|
14
|
+
return { mockDiscover };
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
vi.mock('../src/services/pd-config-loader.js', () => ({
|
|
18
|
+
discoverWorkspaceDefault: mockDiscover,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
import { resolveWorkspaceDir, WORKSPACE_ENV } from '../src/resolve-workspace.js';
|
|
22
|
+
|
|
23
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
function captureStderr() {
|
|
26
|
+
const spy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
27
|
+
return spy;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function makeDiscovery(workspaceDefault: string): import('../src/services/pd-config-loader.js').WorkspaceDiscoveryResult {
|
|
31
|
+
return {
|
|
32
|
+
workspaceDefault,
|
|
33
|
+
configPath: `${workspaceDefault}/.pd/config.yaml`,
|
|
34
|
+
source: 'openclaw_default',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ORIGINAL_ENV = process.env.PD_WORKSPACE_DIR;
|
|
39
|
+
|
|
40
|
+
// ── Tests ───────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
describe('resolveWorkspaceDir', () => {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
mockDiscover.mockReset();
|
|
45
|
+
delete process.env.PD_WORKSPACE_DIR;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
if (ORIGINAL_ENV !== undefined) {
|
|
50
|
+
process.env.PD_WORKSPACE_DIR = ORIGINAL_ENV;
|
|
51
|
+
} else {
|
|
52
|
+
delete process.env.PD_WORKSPACE_DIR;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// 1. Returns explicit --workspace flag
|
|
57
|
+
it('returns explicit --workspace flag', () => {
|
|
58
|
+
mockDiscover.mockReturnValue(null);
|
|
59
|
+
const result = resolveWorkspaceDir('/explicit/path');
|
|
60
|
+
expect(result).toBe('/explicit/path');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// 2. Returns PD_WORKSPACE_DIR env var when no flag
|
|
64
|
+
it('returns PD_WORKSPACE_DIR env var when no flag', () => {
|
|
65
|
+
mockDiscover.mockReturnValue(null);
|
|
66
|
+
process.env.PD_WORKSPACE_DIR = '/env/workspace';
|
|
67
|
+
const result = resolveWorkspaceDir();
|
|
68
|
+
expect(result).toBe('/env/workspace');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// 3. Returns config default when no flag and no env var
|
|
72
|
+
it('returns config default when no flag and no env var', () => {
|
|
73
|
+
mockDiscover.mockReturnValue(makeDiscovery('/config/workspace'));
|
|
74
|
+
const result = resolveWorkspaceDir();
|
|
75
|
+
expect(result).toBe('/config/workspace');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// 4. Throws when no flag, no env var, and no config found
|
|
79
|
+
it('throws when no flag, no env var, and no config found', () => {
|
|
80
|
+
mockDiscover.mockReturnValue(null);
|
|
81
|
+
expect(() => resolveWorkspaceDir()).toThrow('No workspace directory configured');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// 5. Emits warning when --workspace differs from config default
|
|
85
|
+
it('emits warning when --workspace differs from config default', () => {
|
|
86
|
+
mockDiscover.mockReturnValue(makeDiscovery('/config/default'));
|
|
87
|
+
const stderrSpy = captureStderr();
|
|
88
|
+
|
|
89
|
+
const result = resolveWorkspaceDir('/different/path');
|
|
90
|
+
|
|
91
|
+
expect(result).toBe('/different/path');
|
|
92
|
+
expect(stderrSpy).toHaveBeenCalledWith(
|
|
93
|
+
expect.stringContaining('[PD:workspace] WARNING'),
|
|
94
|
+
);
|
|
95
|
+
expect(stderrSpy).toHaveBeenCalledWith(
|
|
96
|
+
expect.stringContaining('differs from config default'),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
stderrSpy.mockRestore();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// 6. Does NOT warn when --workspace matches config default (normalized)
|
|
103
|
+
it('does NOT warn when --workspace matches config default', () => {
|
|
104
|
+
mockDiscover.mockReturnValue(makeDiscovery('/same/path'));
|
|
105
|
+
const stderrSpy = captureStderr();
|
|
106
|
+
|
|
107
|
+
const result = resolveWorkspaceDir('/same/path');
|
|
108
|
+
|
|
109
|
+
expect(result).toBe('/same/path');
|
|
110
|
+
// No warning should be emitted since paths match
|
|
111
|
+
const warningCalls = stderrSpy.mock.calls.filter(
|
|
112
|
+
call => typeof call[0] === 'string' && call[0].includes('WARNING'),
|
|
113
|
+
);
|
|
114
|
+
expect(warningCalls).toHaveLength(0);
|
|
115
|
+
|
|
116
|
+
stderrSpy.mockRestore();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// 7. Emits warning when env var differs from config default
|
|
120
|
+
it('emits warning when env var differs from config default', () => {
|
|
121
|
+
mockDiscover.mockReturnValue(makeDiscovery('/config/default'));
|
|
122
|
+
process.env.PD_WORKSPACE_DIR = '/different/env/path';
|
|
123
|
+
const stderrSpy = captureStderr();
|
|
124
|
+
|
|
125
|
+
const result = resolveWorkspaceDir();
|
|
126
|
+
|
|
127
|
+
expect(result).toBe('/different/env/path');
|
|
128
|
+
expect(stderrSpy).toHaveBeenCalledWith(
|
|
129
|
+
expect.stringContaining('[PD:workspace] WARNING'),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
stderrSpy.mockRestore();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// 8. Does NOT warn when env var matches config default
|
|
136
|
+
it('does NOT warn when env var matches config default', () => {
|
|
137
|
+
mockDiscover.mockReturnValue(makeDiscovery('/same/path'));
|
|
138
|
+
process.env.PD_WORKSPACE_DIR = '/same/path';
|
|
139
|
+
const stderrSpy = captureStderr();
|
|
140
|
+
|
|
141
|
+
const result = resolveWorkspaceDir();
|
|
142
|
+
|
|
143
|
+
expect(result).toBe('/same/path');
|
|
144
|
+
const warningCalls = stderrSpy.mock.calls.filter(
|
|
145
|
+
call => typeof call[0] === 'string' && call[0].includes('WARNING'),
|
|
146
|
+
);
|
|
147
|
+
expect(warningCalls).toHaveLength(0);
|
|
148
|
+
|
|
149
|
+
stderrSpy.mockRestore();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// 9. Does NOT warn when --workspace provided and no config found
|
|
153
|
+
it('does NOT warn when --workspace provided and no config found', () => {
|
|
154
|
+
mockDiscover.mockReturnValue(null);
|
|
155
|
+
const stderrSpy = captureStderr();
|
|
156
|
+
|
|
157
|
+
const result = resolveWorkspaceDir('/some/path');
|
|
158
|
+
|
|
159
|
+
expect(result).toBe('/some/path');
|
|
160
|
+
expect(stderrSpy).not.toHaveBeenCalled();
|
|
161
|
+
|
|
162
|
+
stderrSpy.mockRestore();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// 10. Exports correct WORKSPACE_ENV constant
|
|
166
|
+
it('exports correct WORKSPACE_ENV constant', () => {
|
|
167
|
+
expect(WORKSPACE_ENV).toBe('PD_WORKSPACE_DIR');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// 11. Error message mentions workspace.default option
|
|
171
|
+
it('error message mentions workspace.default option', () => {
|
|
172
|
+
mockDiscover.mockReturnValue(null);
|
|
173
|
+
expect(() => resolveWorkspaceDir()).toThrow('workspace.default');
|
|
174
|
+
});
|
|
175
|
+
});
|