@piewf/cli 5.0.0 → 5.1.1

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.
@@ -328,6 +328,8 @@ function roleMarkdown(role) {
328
328
  metadata.push(`tools: ${JSON.stringify(role.tools)}`);
329
329
  if (role.overrideSystemPrompt !== undefined)
330
330
  metadata.push(`overrideSystemPrompt: ${String(role.overrideSystemPrompt)}`);
331
+ if (role.contextFiles !== undefined)
332
+ metadata.push(`contextFiles: ${JSON.stringify(role.contextFiles)}`);
331
333
  if (role.disabledAgentResources !== undefined)
332
334
  metadata.push(`disabledAgentResources: ${JSON.stringify(role.disabledAgentResources)}`);
333
335
  metadata.push("---");
package/dist/src/cli.d.ts CHANGED
@@ -15,5 +15,9 @@ export interface CliOptions extends DoctorOptions {
15
15
  }
16
16
  export declare function formatWorkflowCliHelp(fn: WorkflowCatalogFunction, command?: string): string;
17
17
  export declare function parseWorkflowCliArgs(schema: JsonSchema, rawArgs: readonly string[]): Record<string, JsonValue>;
18
+ export declare function parseDoctorArgs(rawArgs: readonly string[]): {
19
+ role?: string;
20
+ prompt?: string;
21
+ };
18
22
  export declare function parseDoctorCleanupArgs(rawArgs: readonly string[]): Required<Pick<DoctorCleanupOptions, "olderThanDays" | "yes">>;
19
23
  export declare function runCli(args: readonly string[], options?: CliOptions, write?: (text: string) => void): Promise<number>;
package/dist/src/cli.js CHANGED
@@ -218,6 +218,41 @@ function parseInspectArgs(rawArgs) {
218
218
  }
219
219
  return { ...(sessionId ? { sessionId } : {}), mode: failedOnly && mode === "tui" ? "summary" : mode, failedOnly };
220
220
  }
221
+ export function parseDoctorArgs(rawArgs) {
222
+ let role;
223
+ let prompt;
224
+ for (let index = 0; index < rawArgs.length; index += 1) {
225
+ const token = rawArgs[index];
226
+ const equals = token.indexOf("=");
227
+ const option = equals >= 0 ? token.slice(0, equals) : token;
228
+ if (option === "--role" || option === "--prompt") {
229
+ const value = equals >= 0 ? token.slice(equals + 1) : rawArgs[++index];
230
+ if (!value)
231
+ throw new Error(`Missing value for ${option}`);
232
+ if (option === "--role") {
233
+ if (role !== undefined)
234
+ throw new Error("--role may only be provided once");
235
+ role = value;
236
+ }
237
+ else {
238
+ if (prompt !== undefined)
239
+ throw new Error("--prompt may only be provided once");
240
+ prompt = value;
241
+ }
242
+ continue;
243
+ }
244
+ if (token === "--help" || token === "-h")
245
+ throw new Error("help");
246
+ if (token.startsWith("--"))
247
+ throw new Error(`Unknown doctor option: ${token}`);
248
+ if (role !== undefined)
249
+ throw new Error(`Unexpected argument: ${token}`);
250
+ role = token;
251
+ }
252
+ if (prompt !== undefined && role === undefined)
253
+ throw new Error("--prompt requires --role");
254
+ return { ...(role === undefined ? {} : { role }), ...(prompt === undefined ? {} : { prompt }) };
255
+ }
221
256
  export function parseDoctorCleanupArgs(rawArgs) {
222
257
  let olderThanDays = 90;
223
258
  let yes = false;
@@ -360,8 +395,8 @@ import { homedir } from "node:os";
360
395
  import { join } from "node:path";
361
396
  import { pathToFileURL } from "node:url";
362
397
  let cli;
363
- try { cli = await import(import.meta.resolve("@piewf/cli")); } catch {}
364
- if (!cli) try { cli = await import(pathToFileURL(join(process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent"), "npm", "node_modules", "@piewf/cli", "dist", "src", "cli.js")).href); } catch {}
398
+ try { cli = await import(pathToFileURL(join(process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent"), "npm", "node_modules", "@piewf/cli", "dist", "src", "cli.js")).href); } catch {}
399
+ if (!cli) try { cli = await import(import.meta.resolve("@piewf/cli")); } catch {}
365
400
  if (cli) process.exitCode = await cli.runCli(["run", ${JSON.stringify(workflowName)}, ...process.argv.slice(2)]);
366
401
  else { const result = spawnSync("piewf", ["run", ${JSON.stringify(workflowName)}, ...process.argv.slice(2)], { stdio: "inherit" }); if (result.error) { console.error("Could not resolve @piewf/cli; install it or put piewf on PATH."); process.exitCode = 1; } else process.exitCode = result.status ?? 1; }
367
402
  `;
@@ -674,10 +709,21 @@ async function bundleWorkflowCli(rawArgs, options) {
674
709
  }
675
710
  export async function runCli(args, options = {}, write = (text) => { process.stdout.write(text); }) {
676
711
  const stderr = options.stderr ?? ((text) => { process.stderr.write(text); });
677
- if (args[0] === "doctor" && args.length === 1) {
678
- const report = await doctor(options);
679
- write(formatDoctorReport(report));
680
- return doctorExitCode(report);
712
+ if (args[0] === "doctor" && args[1] !== "cleanup") {
713
+ if (args.slice(1).some((arg) => arg === "--help" || arg === "-h")) {
714
+ write("Usage: piewf doctor [role] [--role <role>] [--prompt <text>]\n");
715
+ return 0;
716
+ }
717
+ try {
718
+ const parsed = parseDoctorArgs(args.slice(1));
719
+ const report = await doctor({ ...options, ...parsed });
720
+ write(formatDoctorReport(report));
721
+ return doctorExitCode(report);
722
+ }
723
+ catch (error) {
724
+ stderr(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
725
+ return 1;
726
+ }
681
727
  }
682
728
  if (args[0] === "doctor" && args[1] === "cleanup") {
683
729
  if (args.slice(2).some((arg) => arg === "--help" || arg === "-h")) {
@@ -735,7 +781,7 @@ export async function runCli(args, options = {}, write = (text) => { process.std
735
781
  return 1;
736
782
  }
737
783
  }
738
- write("Usage: piewf doctor | inspect [session-id] [--json|--summary] [--failed] | transcript <session-file> | bundle <workflow-name> [--name <command>] [--output <path>] [--force] | run <workflow-name> [workflow arguments] | export <workflow-name> [--name <command>] [--output <path>] [--force] [--bundle]\n");
784
+ write("Usage: piewf doctor [role] [--role <role>] [--prompt <text>] | inspect [session-id] [--json|--summary] [--failed] | transcript <session-file> | bundle <workflow-name> [--name <command>] [--output <path>] [--force] | run <workflow-name> [workflow arguments] | export <workflow-name> [--name <command>] [--output <path>] [--force] [--bundle]\n");
739
785
  return 1;
740
786
  }
741
787
  if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
@@ -50,9 +50,33 @@ function agentDefinition(value, label) { if (!object(value))
50
50
  throw new Error(`${label} is invalid`); optionalString(value.prompt, `${label}.prompt`); optionalString(value.description, `${label}.description`); optionalString(value.model, `${label}.model`); if (value.thinking !== undefined && !["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(value.thinking))
51
51
  throw new Error(`${label}.thinking is invalid`); if (value.tools !== undefined)
52
52
  stringList(value.tools, `${label}.tools`); resourceExclusions(value.disabledAgentResources, `${label}.disabledAgentResources`); }
53
+ function optionalRole(value, label) {
54
+ if (value === undefined)
55
+ return;
56
+ if (typeof value === "string") {
57
+ if (!value.trim())
58
+ throw new Error(`${label} is invalid`);
59
+ return;
60
+ }
61
+ if (!object(value) || typeof value.name !== "string" || !value.name.trim())
62
+ throw new Error(`${label} is invalid`);
63
+ for (const key of ["model", "description"])
64
+ if (value[key] !== undefined && value[key] !== null && typeof value[key] !== "string")
65
+ throw new Error(`${label}.${key} is invalid`);
66
+ if (value.thinking !== undefined && value.thinking !== null && !["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(value.thinking))
67
+ throw new Error(`${label}.thinking is invalid`);
68
+ if (value.tools !== undefined && value.tools !== null)
69
+ stringList(value.tools, `${label}.tools`);
70
+ if (value.overrideSystemPrompt !== undefined && value.overrideSystemPrompt !== null && typeof value.overrideSystemPrompt !== "boolean")
71
+ throw new Error(`${label}.overrideSystemPrompt is invalid`);
72
+ if (value.contextFiles !== undefined && value.contextFiles !== null)
73
+ stringList(value.contextFiles, `${label}.contextFiles`);
74
+ if (value.disabledAgentResources !== undefined && value.disabledAgentResources !== null)
75
+ resourceExclusions(value.disabledAgentResources, `${label}.disabledAgentResources`);
76
+ }
53
77
  function validateScheduledOptions(value, label) { if (!object(value) || typeof value.label !== "string" || !value.label || typeof value.cwd !== "string" || !value.cwd)
54
78
  throw new Error(`${label} is invalid`); optionalString(value.requestedLabel, `${label}.requestedLabel`); optionalString(value.parentBreadcrumb, `${label}.parentBreadcrumb`); stringList(value.tools, `${label}.tools`); optionalString(value.worktreeOwner, `${label}.worktreeOwner`); optionalString(value.model, `${label}.model`); if (value.thinking !== undefined && !["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(value.thinking))
55
- throw new Error(`${label}.thinking is invalid`); optionalString(value.role, `${label}.role`); if (value.schema !== undefined)
79
+ throw new Error(`${label}.thinking is invalid`); optionalRole(value.role, `${label}.role`); if (value.schema !== undefined)
56
80
  validateSchema(value.schema, `${label}.schema`); if (value.retries !== undefined)
57
81
  nonNegativeInteger(value.retries, `${label}.retries`); if (value.timeoutMs !== undefined && value.timeoutMs !== null)
58
82
  positiveInteger(value.timeoutMs, `${label}.timeoutMs`); if (value.agentOptions !== undefined && (!object(value.agentOptions) || !jsonValue(value.agentOptions)))
@@ -27,8 +27,42 @@ export interface DoctorTrust {
27
27
  trusted: boolean;
28
28
  source: string;
29
29
  }
30
+ export interface DoctorRoleInspection {
31
+ role: string;
32
+ path: string;
33
+ model: {
34
+ provider: string;
35
+ model: string;
36
+ thinking?: string;
37
+ inherited?: boolean;
38
+ };
39
+ tools: readonly string[];
40
+ resources: {
41
+ skills: readonly string[];
42
+ extensions: readonly string[];
43
+ excludedSkills: readonly string[];
44
+ excludedExtensions: readonly string[];
45
+ unmatchedSkills: readonly string[];
46
+ unmatchedExtensions: readonly string[];
47
+ };
48
+ systemPrompt: {
49
+ probe: string;
50
+ expandedProbe: string;
51
+ text: string;
52
+ source?: string;
53
+ };
54
+ setup: {
55
+ hooks: readonly string[];
56
+ diagnostics: readonly DoctorDiagnostic[];
57
+ };
58
+ }
30
59
  export interface DoctorPiState {
31
60
  trust: DoctorTrust;
61
+ model?: {
62
+ provider: string;
63
+ model: string;
64
+ thinking?: string;
65
+ };
32
66
  activeTools: readonly string[];
33
67
  knownModels: readonly string[];
34
68
  availableModels: readonly string[];
@@ -48,16 +82,22 @@ export interface DoctorReport {
48
82
  settingsSources: WorkflowSettingsSources;
49
83
  trust: DoctorTrust;
50
84
  activeTools: readonly string[];
85
+ piExtensions: readonly string[];
86
+ piSkills: readonly string[];
51
87
  roles: readonly DoctorRole[];
52
88
  functions: readonly DoctorFunction[];
53
89
  resourcePolicy: AgentResourcePolicy;
54
90
  modelAliases: readonly WorkflowCatalogModelAlias[];
91
+ roleTarget?: string;
92
+ roleInspection?: DoctorRoleInspection;
55
93
  diagnostics: readonly DoctorDiagnostic[];
56
94
  }
57
95
  export interface DoctorOptions {
58
96
  cwd?: string;
59
97
  agentDir?: string;
60
98
  settingsPath?: string;
99
+ role?: string;
100
+ prompt?: string;
61
101
  discoverPi?: (cwd: string, agentDir: string) => Promise<DoctorPiState>;
62
102
  activeTools?: readonly string[];
63
103
  registry?: WorkflowRegistryApi;
@@ -2,7 +2,7 @@ import { readFileSync, readdirSync, realpathSync } from "node:fs";
2
2
  import { basename, dirname, extname, join, resolve } from "node:path";
3
3
  import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
4
4
  import { ModelRuntime, createAgentSessionFromServices, createAgentSessionServices, getAgentDir, hasTrustRequiringProjectResources, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
5
- import { DEFAULT_SETTINGS, loadSettings, resolveAgentResourcePolicy, resolveWorkflowSettings, resolveModelReference, parseThinking, parseRoleMarkdown, registeredWorkflowFunctions, registeredWorkflowRoleDirectoryRegistrations, workflowRoleDirectories, workflowProjectSettingsPath, workflowSettingsPath, } from "pi-extensible-workflows";
5
+ import { DEFAULT_SETTINGS, createLocalPiSession, loadSettings, prepareAgentSetupForInspection, resolveAgentResourcePolicy, resolveWorkflowSettings, resolveModelReference, parseThinking, parseRoleMarkdown, registeredWorkflowFunctions, registeredWorkflowRoleDirectoryRegistrations, workflowRoleDirectories, workflowProjectSettingsPath, workflowSettingsPath, } from "pi-extensible-workflows";
6
6
  import { loadingRegistry } from "pi-extensible-workflows";
7
7
  import { disabledResources, unmatchedResourcePatterns } from "pi-extensible-workflows";
8
8
  const THINKING_HINT = "Use off, minimal, low, medium, high, xhigh, or max.";
@@ -86,6 +86,7 @@ async function discoverPi(cwd, agentDir) {
86
86
  const skills = services.resourceLoader.getSkills().skills;
87
87
  return {
88
88
  trust: { required, trusted, source },
89
+ model: { provider: model.provider, model: model.id, thinking: session.thinkingLevel },
89
90
  activeTools,
90
91
  knownModels: allModels.map(({ provider, id }) => `${provider}/${id}`),
91
92
  availableModels: availableModels.map(({ provider, id }) => `${provider}/${id}`),
@@ -184,7 +185,7 @@ function inspectRole(path, activeTools, knownModels, availableModels, diagnostic
184
185
  validateModel(definition.model, knownModels, availableModels, path, diagnostics, aliases, dynamicAliases, settingsPath);
185
186
  for (const tool of definition.tools ?? [])
186
187
  if (!activeTools.has(tool))
187
- diagnostics.push(diagnostic("error", "ROLE_TOOL_INACTIVE", `Tool is unknown or inactive: ${tool}`, path, "Use a tool listed under Active tools or enable its Pi extension."));
188
+ diagnostics.push(diagnostic("error", "ROLE_TOOL_INACTIVE", `Tool is unknown or inactive: ${tool}`, path, "Use a tool listed under Pi active tools or enable its Pi extension."));
188
189
  return definition;
189
190
  }
190
191
  function matchResourcePolicy(policy, pi) {
@@ -192,6 +193,60 @@ function matchResourcePolicy(policy, pi) {
192
193
  const skills = [...new Set(pi.skills ?? [])];
193
194
  return { ...policy, excludedSkills: disabledResources(policy.effective.skills, skills), excludedExtensions: disabledResources(policy.effective.extensions, extensions), unmatchedSkills: unmatchedResourcePatterns(policy.effective.skills, skills), unmatchedExtensions: unmatchedResourcePatterns(policy.effective.extensions, extensions) };
194
195
  }
196
+ async function inspectRoleSession(cwd, agentDir, roleName, definition, rolePath, basePolicy, rootModel, activeTools, aliases, knownModels, availableModels, settingsPath, prompt, hooks, diagnostics) {
197
+ const setupDiagnostics = [];
198
+ const signal = new AbortController().signal;
199
+ const transport = { id: "doctor-local", createSession: async () => { throw new Error("Doctor inspection does not create transport sessions"); } };
200
+ const run = { cwd, sessionId: "doctor", runId: "doctor", workflow: { name: "doctor" }, args: null, signal };
201
+ const root = { cwd, model: { ...rootModel }, tools: new Set(activeTools), agentDefinitions: { [roleName]: definition }, agentDir, modelAliases: aliases, knownModels, availableModels, settingsPath, agentSetupHooks: hooks, agentResourcePolicy: () => structuredClone(basePolicy), runContext: run };
202
+ const options = { label: roleName, workflowName: "doctor", role: roleName };
203
+ let prepared;
204
+ try {
205
+ prepared = await prepareAgentSetupForInspection(root, prompt, options, transport);
206
+ }
207
+ catch (error) {
208
+ setupDiagnostics.push(diagnostic("error", "ROLE_INSPECTION", error instanceof Error ? error.message : String(error), rolePath));
209
+ diagnostics.push(...setupDiagnostics);
210
+ return undefined;
211
+ }
212
+ if (prepared.failure) {
213
+ const error = prepared.failure.error;
214
+ const code = prepared.failure.hook ? "ROLE_SETUP_HOOK" : "ROLE_INSPECTION";
215
+ setupDiagnostics.push(diagnostic("error", code, `${prepared.failure.hook ? `Role setup hook ${prepared.failure.hook} failed: ` : ""}${error instanceof Error ? error.message : String(error)}`, prepared.failure.hook ?? rolePath));
216
+ diagnostics.push(...setupDiagnostics);
217
+ return undefined;
218
+ }
219
+ const session = await (async () => { try {
220
+ return await createLocalPiSession({ ...prepared.setup.sessionInput, sessionManager: SessionManager.inMemory() });
221
+ }
222
+ catch (error) {
223
+ setupDiagnostics.push(diagnostic("error", "ROLE_INSPECTION", error instanceof Error ? error.message : String(error), rolePath));
224
+ return undefined;
225
+ } })();
226
+ if (!session) {
227
+ diagnostics.push(...setupDiagnostics);
228
+ return undefined;
229
+ }
230
+ try {
231
+ const promptResult = await session.preparePrompt(prompt);
232
+ const resources = session.getResourceInspection();
233
+ const state = session.agent?.state;
234
+ const inherited = prepared.setup.sessionInput.model.provider === rootModel.provider && prepared.setup.sessionInput.model.model === rootModel.model && prepared.setup.sessionInput.model.thinking === rootModel.thinking;
235
+ const actualModel = session.model?.provider && (session.model.model ?? session.model.id) ? { provider: session.model.provider, model: session.model.model ?? session.model.id ?? prepared.setup.sessionInput.model.model, ...(session.thinkingLevel ? { thinking: session.thinkingLevel } : {}), ...(inherited ? { inherited: true } : {}) } : { ...prepared.setup.sessionInput.model, ...(inherited ? { inherited: true } : {}) };
236
+ const policy = prepared.setup.sessionInput.resourcePolicy ?? basePolicy;
237
+ for (const item of [...resources.diagnostics, ...promptResult.diagnostics])
238
+ setupDiagnostics.push(diagnostic(item.type === "error" ? "error" : "warning", "ROLE_INSPECTION", item.message, item.source));
239
+ return { role: roleName, path: rolePath, model: actualModel, tools: state?.tools.map(({ name }) => name) ?? [...prepared.setup.sessionInput.tools], resources: { skills: resources.skills, extensions: resources.extensions, excludedSkills: policy.excludedSkills ?? [], excludedExtensions: policy.excludedExtensions ?? [], unmatchedSkills: policy.unmatchedSkills, unmatchedExtensions: policy.unmatchedExtensions }, systemPrompt: { probe: prompt, expandedProbe: promptResult.expandedPrompt, text: promptResult.systemPrompt, ...(resources.systemPromptSource ? { source: resources.systemPromptSource } : {}) }, setup: { hooks: prepared.summary.hookNames, diagnostics: setupDiagnostics } };
240
+ }
241
+ catch (error) {
242
+ setupDiagnostics.push(diagnostic("error", "ROLE_INSPECTION", error instanceof Error ? error.message : String(error), rolePath));
243
+ diagnostics.push(...setupDiagnostics);
244
+ return undefined;
245
+ }
246
+ finally {
247
+ session.dispose();
248
+ }
249
+ }
195
250
  function resourcePolicySource(settingsSource) { return settingsSource; }
196
251
  export async function doctor(options = {}) {
197
252
  const cwd = canonical(options.cwd ?? process.cwd());
@@ -335,6 +390,40 @@ export async function doctor(options = {}) {
335
390
  else
336
391
  definitions.delete(name);
337
392
  }
393
+ let roleInspection;
394
+ if (options.role !== undefined) {
395
+ const activeRole = roles.find(({ name, active }) => name === options.role && active);
396
+ const definition = activeRole ? definitions.get(options.role) : undefined;
397
+ if (!activeRole || !definition)
398
+ diagnostics.push(diagnostic("error", "ROLE_NOT_FOUND", `Active role not found: ${options.role}`, options.role));
399
+ else {
400
+ const rootReference = pi.model ? `${pi.model.provider}/${pi.model.model}` : pi.availableModels[0] ?? pi.knownModels[0];
401
+ if (!rootReference)
402
+ diagnostics.push(diagnostic("error", "ROLE_INSPECTION_MODEL", "Cannot inspect a role because Pi has no registered model"));
403
+ else {
404
+ let rootModel;
405
+ try {
406
+ if (pi.model) {
407
+ const thinking = parseThinking(pi.model.thinking);
408
+ rootModel = { provider: pi.model.provider, model: pi.model.model, ...(thinking ? { thinking } : {}) };
409
+ }
410
+ else
411
+ rootModel = resolveModelReference(rootReference, aliases, knownModels, settingsPath);
412
+ let roleAliases = aliases;
413
+ if (definition.model && isDynamicModelAlias(definition.model, dynamicAliases)) {
414
+ const dynamic = await registry.resolveModelAliases({ cwd, projectTrusted: pi.trust.trusted, rootModel, knownModels, availableModels, signal: new AbortController().signal });
415
+ roleAliases = { ...aliases, ...dynamic };
416
+ }
417
+ roleInspection = await inspectRoleSession(cwd, agentDir, options.role, definition, activeRole.path, resourcePolicy, rootModel, [...activeTools], roleAliases, knownModels, availableModels, settingsPath, options.prompt ?? "", registry.agentSetupHooks(), diagnostics);
418
+ if (roleInspection)
419
+ diagnostics.push(...roleInspection.setup.diagnostics);
420
+ }
421
+ catch (error) {
422
+ diagnostics.push(diagnostic("error", "ROLE_INSPECTION_MODEL", error instanceof Error ? error.message : String(error), activeRole.path));
423
+ }
424
+ }
425
+ }
426
+ }
338
427
  const functions = [];
339
428
  for (const [name, fn] of Object.entries(pi.functions).sort(([left], [right]) => left.localeCompare(right))) {
340
429
  functions.push({ name, description: fn.description, valid: true });
@@ -342,11 +431,55 @@ export async function doctor(options = {}) {
342
431
  const severityOrder = { error: 0, warning: 1 };
343
432
  diagnostics.sort((left, right) => severityOrder[left.severity] - severityOrder[right.severity] || (left.source ?? "").localeCompare(right.source ?? "") || left.code.localeCompare(right.code) || left.message.localeCompare(right.message));
344
433
  roles.sort((left, right) => left.name.localeCompare(right.name) || left.scope.localeCompare(right.scope));
345
- return { cwd, agentDir, settingsPath, settings, settingsSources, trust: pi.trust, activeTools: [...activeTools].sort(), roles, functions, modelAliases, resourcePolicy, diagnostics };
434
+ return { cwd, agentDir, settingsPath, settings, settingsSources, trust: pi.trust, activeTools: [...activeTools].sort(), piExtensions: [...new Set((pi.extensions ?? []).map(canonical))].sort(), piSkills: [...new Set(pi.skills ?? [])].sort(), roles, functions, modelAliases, resourcePolicy, ...(options.role !== undefined ? { roleTarget: options.role } : {}), ...(roleInspection ? { roleInspection } : {}), diagnostics };
346
435
  }
347
436
  function count(report, severity) { return report.diagnostics.filter((item) => item.severity === severity).length; }
348
437
  export function doctorExitCode(report) { return count(report, "error") > 0 ? 1 : 0; }
438
+ function exposedValues(values, excluded) {
439
+ const disabled = new Set(excluded);
440
+ return values.filter((value) => !disabled.has(value));
441
+ }
442
+ function nestedValues(label, values) {
443
+ return [`- ${label}:`, ...(values.length ? values.map((value) => ` - \`${value}\``) : [" - (none)"])];
444
+ }
445
+ function roleInspectionLines(inspection) {
446
+ return [
447
+ `- Role: \`${inspection.role}\` - \`${inspection.path}\``,
448
+ `- Model: \`${inspection.model.provider}/${inspection.model.model}\` (${inspection.model.inherited ? "inherited, " : ""}${inspection.model.thinking ?? "off"})`,
449
+ ...nestedValues("Tools", inspection.tools),
450
+ ...nestedValues("Effective skills", inspection.resources.skills),
451
+ ...nestedValues("Effective extensions", inspection.resources.extensions),
452
+ ...nestedValues("Excluded skills", inspection.resources.excludedSkills),
453
+ ...nestedValues("Excluded extensions", inspection.resources.excludedExtensions),
454
+ ...nestedValues("Unmatched skills", inspection.resources.unmatchedSkills),
455
+ ...nestedValues("Unmatched extensions", inspection.resources.unmatchedExtensions),
456
+ `- Prompt probe: ${inspection.systemPrompt.probe ? JSON.stringify(inspection.systemPrompt.probe) : "empty"}`,
457
+ `- Expanded probe: ${JSON.stringify(inspection.systemPrompt.expandedProbe)}`,
458
+ `- System prompt source: ${inspection.systemPrompt.source ?? "(none)"}`,
459
+ "### Final system prompt",
460
+ "```",
461
+ inspection.systemPrompt.text,
462
+ "```",
463
+ ...nestedValues("Applied setup hooks", inspection.setup.hooks),
464
+ `- Setup diagnostics: ${String(inspection.setup.diagnostics.length)}`,
465
+ ];
466
+ }
349
467
  export function formatDoctorReport(report) {
468
+ if (report.roleInspection || report.roleTarget !== undefined) {
469
+ const lines = [
470
+ "# pi-extensible-workflows doctor",
471
+ "",
472
+ "## Role inspection",
473
+ ...(report.roleInspection ? roleInspectionLines(report.roleInspection) : [`- Role: \`${report.roleTarget ?? "(unknown)"}\``, "- Inspection unavailable"]),
474
+ "",
475
+ "## Diagnostics",
476
+ ...(report.diagnostics.length ? report.diagnostics.map((item) => `- [${item.severity}] ${item.code}${item.source ? ` \`${item.source}\`` : ""}: ${item.message}${item.hint ? ` Fix: ${item.hint}` : ""}`) : ["- [ok] No diagnostics"]),
477
+ "",
478
+ "## Summary",
479
+ `- ${String(count(report, "error"))} error(s), ${String(count(report, "warning"))} warning(s)`,
480
+ ];
481
+ return `${lines.join("\n")}\n`;
482
+ }
350
483
  const lines = [
351
484
  "# pi-extensible-workflows doctor",
352
485
  "",
@@ -361,7 +494,16 @@ export function formatDoctorReport(report) {
361
494
  "## Trust/resources",
362
495
  `- [${report.trust.trusted ? "ok" : "warning"}] ${report.trust.source}`,
363
496
  "",
364
- "## Agent resource exclusions",
497
+ "## Pi active tools",
498
+ ...(report.activeTools.length ? report.activeTools.map((tool) => `- \`${tool}\``) : ["- None resolved"]),
499
+ "",
500
+ "## Pi active extensions",
501
+ ...(report.piExtensions.length ? report.piExtensions.map((extension) => `- \`${extension}\``) : ["- None resolved"]),
502
+ "",
503
+ "## Pi active skills",
504
+ ...(report.piSkills.length ? report.piSkills.map((skill) => `- \`${skill}\``) : ["- None resolved"]),
505
+ "",
506
+ "## Workflow agent resources (after disabledAgentResources)",
365
507
  `- Global settings: \`${report.resourcePolicy.globalSettingsPath}\``,
366
508
  `- Global skills: ${report.resourcePolicy.global.skills.join(", ") || "(none)"}`,
367
509
  `- Global extensions: ${report.resourcePolicy.global.extensions.join(", ") || "(none)"}`,
@@ -370,14 +512,13 @@ export function formatDoctorReport(report) {
370
512
  `- Project extensions: ${report.resourcePolicy.project.extensions.join(", ") || "(none)"}`,
371
513
  `- Effective skills: ${report.resourcePolicy.effective.skills.join(", ") || "(none)"}`,
372
514
  `- Effective extensions: ${report.resourcePolicy.effective.extensions.join(", ") || "(none)"}`,
373
- `- Excluded skills: ${report.resourcePolicy.excludedSkills?.join(", ") || "(none)"}`,
374
- `- Excluded extensions: ${report.resourcePolicy.excludedExtensions?.join(", ") || "(none)"}`,
515
+ ...nestedValues("Exposed skills", exposedValues(report.piSkills, report.resourcePolicy.excludedSkills ?? [])),
516
+ ...nestedValues("Exposed extensions", exposedValues(report.piExtensions, report.resourcePolicy.excludedExtensions ?? [])),
517
+ ...nestedValues("Disabled skills", report.resourcePolicy.excludedSkills ?? []),
518
+ ...nestedValues("Disabled extensions", report.resourcePolicy.excludedExtensions ?? []),
375
519
  `- Unmatched skills: ${report.resourcePolicy.unmatchedSkills.join(", ") || "(none)"}`,
376
520
  `- Unmatched extensions: ${report.resourcePolicy.unmatchedExtensions.join(", ") || "(none)"}`,
377
521
  "",
378
- "## Active tools",
379
- ...(report.activeTools.length ? report.activeTools.map((tool) => `- \`${tool}\``) : ["- None resolved"]),
380
- "",
381
522
  "## Roles",
382
523
  ...(report.roles.length ? report.roles.map((role) => `- \`${role.name}\` (${role.scope}, ${role.active ? "active" : role.overriddenBy ? `overridden by ${role.overriddenBy}` : "inactive: project untrusted"}) - \`${role.path}\`${role.extension ? `; ${extensionLabel(role.extension)} role directory "${dirname(role.path)}"` : ""}${role.overrides ? `; overrides \`${role.overrides}\`` : ""}`) : ["- None found"]),
383
524
  "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piewf/cli",
3
- "version": "5.0.0",
3
+ "version": "5.1.1",
4
4
  "description": "CLI for pi-extensible-workflows",
5
5
  "homepage": "https://vekexasia.github.io/pi-extensible-workflows/",
6
6
  "repository": {
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "license": "MIT",
47
47
  "dependencies": {
48
- "pi-extensible-workflows": "5.0.0"
48
+ "pi-extensible-workflows": "5.1.1"
49
49
  },
50
50
  "publishConfig": {
51
51
  "access": "public"