@principal-ai/principal-studio-cli 0.35.3

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 (184) hide show
  1. package/README.md +172 -0
  2. package/dist/commands/agent-session.d.ts +3 -0
  3. package/dist/commands/agent-session.d.ts.map +1 -0
  4. package/dist/commands/agent-session.js +67 -0
  5. package/dist/commands/agent-sessions.d.ts +11 -0
  6. package/dist/commands/agent-sessions.d.ts.map +1 -0
  7. package/dist/commands/agent-sessions.js +39 -0
  8. package/dist/commands/auxiliary/index.d.ts +3 -0
  9. package/dist/commands/auxiliary/index.d.ts.map +1 -0
  10. package/dist/commands/auxiliary/index.js +9 -0
  11. package/dist/commands/auxiliary/validate.d.ts +3 -0
  12. package/dist/commands/auxiliary/validate.d.ts.map +1 -0
  13. package/dist/commands/auxiliary/validate.js +123 -0
  14. package/dist/commands/collector/check.d.ts +6 -0
  15. package/dist/commands/collector/check.d.ts.map +1 -0
  16. package/dist/commands/collector/check.js +153 -0
  17. package/dist/commands/collector/diagnose.d.ts +6 -0
  18. package/dist/commands/collector/diagnose.d.ts.map +1 -0
  19. package/dist/commands/collector/diagnose.js +229 -0
  20. package/dist/commands/collector/index.d.ts +6 -0
  21. package/dist/commands/collector/index.d.ts.map +1 -0
  22. package/dist/commands/collector/index.js +16 -0
  23. package/dist/commands/collector/status.d.ts +6 -0
  24. package/dist/commands/collector/status.d.ts.map +1 -0
  25. package/dist/commands/collector/status.js +77 -0
  26. package/dist/commands/collector/utils.d.ts +92 -0
  27. package/dist/commands/collector/utils.d.ts.map +1 -0
  28. package/dist/commands/collector/utils.js +154 -0
  29. package/dist/commands/coverage.d.ts +9 -0
  30. package/dist/commands/coverage.d.ts.map +1 -0
  31. package/dist/commands/coverage.js +252 -0
  32. package/dist/commands/create.d.ts +6 -0
  33. package/dist/commands/create.d.ts.map +1 -0
  34. package/dist/commands/create.js +50 -0
  35. package/dist/commands/events/index.d.ts +3 -0
  36. package/dist/commands/events/index.d.ts.map +1 -0
  37. package/dist/commands/events/index.js +9 -0
  38. package/dist/commands/events/validate.d.ts +3 -0
  39. package/dist/commands/events/validate.d.ts.map +1 -0
  40. package/dist/commands/events/validate.js +124 -0
  41. package/dist/commands/formats.d.ts +6 -0
  42. package/dist/commands/formats.d.ts.map +1 -0
  43. package/dist/commands/formats.js +1118 -0
  44. package/dist/commands/hooks.d.ts +9 -0
  45. package/dist/commands/hooks.d.ts.map +1 -0
  46. package/dist/commands/hooks.js +295 -0
  47. package/dist/commands/inbox.d.ts +13 -0
  48. package/dist/commands/inbox.d.ts.map +1 -0
  49. package/dist/commands/inbox.js +179 -0
  50. package/dist/commands/init.d.ts +6 -0
  51. package/dist/commands/init.d.ts.map +1 -0
  52. package/dist/commands/init.js +220 -0
  53. package/dist/commands/lint.d.ts +11 -0
  54. package/dist/commands/lint.d.ts.map +1 -0
  55. package/dist/commands/lint.js +71 -0
  56. package/dist/commands/list.d.ts +6 -0
  57. package/dist/commands/list.d.ts.map +1 -0
  58. package/dist/commands/list.js +78 -0
  59. package/dist/commands/migrate-nodes.d.ts +9 -0
  60. package/dist/commands/migrate-nodes.d.ts.map +1 -0
  61. package/dist/commands/migrate-nodes.js +363 -0
  62. package/dist/commands/migrate-scopes-to-canvas.d.ts +3 -0
  63. package/dist/commands/migrate-scopes-to-canvas.d.ts.map +1 -0
  64. package/dist/commands/migrate-scopes-to-canvas.js +143 -0
  65. package/dist/commands/migration.d.ts +6 -0
  66. package/dist/commands/migration.d.ts.map +1 -0
  67. package/dist/commands/migration.js +247 -0
  68. package/dist/commands/open-studio.d.ts +10 -0
  69. package/dist/commands/open-studio.d.ts.map +1 -0
  70. package/dist/commands/open-studio.js +32 -0
  71. package/dist/commands/opencode/fetch.d.ts +3 -0
  72. package/dist/commands/opencode/fetch.d.ts.map +1 -0
  73. package/dist/commands/opencode/fetch.js +25 -0
  74. package/dist/commands/opencode/index.d.ts +3 -0
  75. package/dist/commands/opencode/index.d.ts.map +1 -0
  76. package/dist/commands/opencode/index.js +11 -0
  77. package/dist/commands/opencode/list-sessions.d.ts +3 -0
  78. package/dist/commands/opencode/list-sessions.d.ts.map +1 -0
  79. package/dist/commands/opencode/list-sessions.js +20 -0
  80. package/dist/commands/repo.d.ts +18 -0
  81. package/dist/commands/repo.d.ts.map +1 -0
  82. package/dist/commands/repo.js +233 -0
  83. package/dist/commands/schema.d.ts +6 -0
  84. package/dist/commands/schema.d.ts.map +1 -0
  85. package/dist/commands/schema.js +367 -0
  86. package/dist/commands/scopes/index.d.ts +3 -0
  87. package/dist/commands/scopes/index.d.ts.map +1 -0
  88. package/dist/commands/scopes/index.js +9 -0
  89. package/dist/commands/scopes/validate.d.ts +3 -0
  90. package/dist/commands/scopes/validate.d.ts.map +1 -0
  91. package/dist/commands/scopes/validate.js +187 -0
  92. package/dist/commands/setup.d.ts +6 -0
  93. package/dist/commands/setup.d.ts.map +1 -0
  94. package/dist/commands/setup.js +406 -0
  95. package/dist/commands/starred-collections.d.ts +12 -0
  96. package/dist/commands/starred-collections.d.ts.map +1 -0
  97. package/dist/commands/starred-collections.js +310 -0
  98. package/dist/commands/subsystem-model.d.ts +11 -0
  99. package/dist/commands/subsystem-model.d.ts.map +1 -0
  100. package/dist/commands/subsystem-model.js +249 -0
  101. package/dist/commands/topic.d.ts +15 -0
  102. package/dist/commands/topic.d.ts.map +1 -0
  103. package/dist/commands/topic.js +683 -0
  104. package/dist/commands/tour.d.ts +23 -0
  105. package/dist/commands/tour.d.ts.map +1 -0
  106. package/dist/commands/tour.js +783 -0
  107. package/dist/commands/trace/flow.d.ts +6 -0
  108. package/dist/commands/trace/flow.d.ts.map +1 -0
  109. package/dist/commands/trace/flow.js +158 -0
  110. package/dist/commands/trace/index.d.ts +6 -0
  111. package/dist/commands/trace/index.d.ts.map +1 -0
  112. package/dist/commands/trace/index.js +20 -0
  113. package/dist/commands/trace/inspect.d.ts +6 -0
  114. package/dist/commands/trace/inspect.d.ts.map +1 -0
  115. package/dist/commands/trace/inspect.js +187 -0
  116. package/dist/commands/trace/list.d.ts +6 -0
  117. package/dist/commands/trace/list.d.ts.map +1 -0
  118. package/dist/commands/trace/list.js +145 -0
  119. package/dist/commands/trace/registrations.d.ts +6 -0
  120. package/dist/commands/trace/registrations.d.ts.map +1 -0
  121. package/dist/commands/trace/registrations.js +81 -0
  122. package/dist/commands/trace/validate.d.ts +6 -0
  123. package/dist/commands/trace/validate.d.ts.map +1 -0
  124. package/dist/commands/trace/validate.js +276 -0
  125. package/dist/commands/trail.d.ts +36 -0
  126. package/dist/commands/trail.d.ts.map +1 -0
  127. package/dist/commands/trail.js +622 -0
  128. package/dist/commands/validate.d.ts +12 -0
  129. package/dist/commands/validate.d.ts.map +1 -0
  130. package/dist/commands/validate.js +3334 -0
  131. package/dist/commands/workflow/implementation.d.ts +3 -0
  132. package/dist/commands/workflow/implementation.d.ts.map +1 -0
  133. package/dist/commands/workflow/implementation.js +51 -0
  134. package/dist/commands/workflow/index.d.ts +3 -0
  135. package/dist/commands/workflow/index.d.ts.map +1 -0
  136. package/dist/commands/workflow/index.js +19 -0
  137. package/dist/commands/workflow/inspect.d.ts +3 -0
  138. package/dist/commands/workflow/inspect.d.ts.map +1 -0
  139. package/dist/commands/workflow/inspect.js +123 -0
  140. package/dist/commands/workflow/list.d.ts +3 -0
  141. package/dist/commands/workflow/list.d.ts.map +1 -0
  142. package/dist/commands/workflow/list.js +95 -0
  143. package/dist/commands/workflow/render.d.ts +3 -0
  144. package/dist/commands/workflow/render.d.ts.map +1 -0
  145. package/dist/commands/workflow/render.js +88 -0
  146. package/dist/commands/workflow/test.d.ts +3 -0
  147. package/dist/commands/workflow/test.d.ts.map +1 -0
  148. package/dist/commands/workflow/test.js +141 -0
  149. package/dist/commands/workflow/utils.d.ts +49 -0
  150. package/dist/commands/workflow/utils.d.ts.map +1 -0
  151. package/dist/commands/workflow/utils.js +164 -0
  152. package/dist/commands/workflow/validate.d.ts +3 -0
  153. package/dist/commands/workflow/validate.d.ts.map +1 -0
  154. package/dist/commands/workflow/validate.js +344 -0
  155. package/dist/file-utils.d.ts +15 -0
  156. package/dist/file-utils.d.ts.map +1 -0
  157. package/dist/file-utils.js +29 -0
  158. package/dist/index.cjs +285718 -0
  159. package/dist/index.cjs.map +7 -0
  160. package/dist/index.d.ts +8 -0
  161. package/dist/index.d.ts.map +1 -0
  162. package/dist/index.js +81 -0
  163. package/dist/lib/bridge-ipc.d.ts +29 -0
  164. package/dist/lib/bridge-ipc.d.ts.map +1 -0
  165. package/dist/lib/bridge-ipc.js +128 -0
  166. package/dist/lib/github-user.d.ts +17 -0
  167. package/dist/lib/github-user.d.ts.map +1 -0
  168. package/dist/lib/github-user.js +53 -0
  169. package/dist/lib/open-url.d.ts +11 -0
  170. package/dist/lib/open-url.d.ts.map +1 -0
  171. package/dist/lib/open-url.js +45 -0
  172. package/dist/lib/subsystem-model-store.d.ts +74 -0
  173. package/dist/lib/subsystem-model-store.d.ts.map +1 -0
  174. package/dist/lib/subsystem-model-store.js +292 -0
  175. package/dist/lib/tour-cache.d.ts +26 -0
  176. package/dist/lib/tour-cache.d.ts.map +1 -0
  177. package/dist/lib/tour-cache.js +46 -0
  178. package/dist/lib/trail-cache.d.ts +40 -0
  179. package/dist/lib/trail-cache.d.ts.map +1 -0
  180. package/dist/lib/trail-cache.js +97 -0
  181. package/dist/lib/viewer-ipc.d.ts +44 -0
  182. package/dist/lib/viewer-ipc.d.ts.map +1 -0
  183. package/dist/lib/viewer-ipc.js +68 -0
  184. package/package.json +68 -0
@@ -0,0 +1,164 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { ExecutionValidator } from '@principal-ai/subsystems-core';
4
+ /**
5
+ * Load a workflow template from a file
6
+ */
7
+ export async function loadWorkflow(filePath) {
8
+ try {
9
+ const content = await readFile(filePath, 'utf-8');
10
+ return JSON.parse(content);
11
+ }
12
+ catch (error) {
13
+ if (error.code === 'ENOENT') {
14
+ throw new Error(`Workflow file not found: ${filePath}`);
15
+ }
16
+ throw new Error(`Failed to parse workflow file: ${error.message}`);
17
+ }
18
+ }
19
+ /**
20
+ * Load execution data from a .otel.json file
21
+ *
22
+ * Automatically handles OTLP standard format conversion via ExecutionValidator.
23
+ */
24
+ export async function loadExecution(filePath) {
25
+ try {
26
+ const content = await readFile(filePath, 'utf-8');
27
+ const parsed = JSON.parse(content);
28
+ // Use ExecutionValidator to handle OTLP format conversion
29
+ const validator = new ExecutionValidator();
30
+ return validator.validateOrThrow(parsed, filePath);
31
+ }
32
+ catch (error) {
33
+ if (error.code === 'ENOENT') {
34
+ throw new Error(`Execution file not found: ${filePath}`);
35
+ }
36
+ throw new Error(`Failed to parse execution file: ${error.message}`);
37
+ }
38
+ }
39
+ /**
40
+ * Convert execution data to OtelEvent array format expected by workflow APIs
41
+ */
42
+ export function executionToEvents(execution) {
43
+ const events = [];
44
+ for (const span of execution.spans) {
45
+ for (const event of span.events) {
46
+ events.push({
47
+ name: event.name,
48
+ timestamp: event.time,
49
+ type: 'log',
50
+ attributes: {
51
+ ...event.attributes,
52
+ 'span.id': span.id,
53
+ 'span.name': span.name,
54
+ },
55
+ });
56
+ }
57
+ }
58
+ return events;
59
+ }
60
+ /**
61
+ * Resolve a file path relative to a base directory
62
+ */
63
+ export function resolvePath(filePath, baseDir) {
64
+ if (baseDir) {
65
+ return resolve(baseDir, filePath);
66
+ }
67
+ return resolve(filePath);
68
+ }
69
+ /**
70
+ * Format a timestamp as human-readable date/time
71
+ */
72
+ export function formatTimestamp(timestamp) {
73
+ const date = new Date(timestamp);
74
+ return date.toLocaleString('en-US', {
75
+ year: 'numeric',
76
+ month: '2-digit',
77
+ day: '2-digit',
78
+ hour: '2-digit',
79
+ minute: '2-digit',
80
+ second: '2-digit',
81
+ hour12: false,
82
+ });
83
+ }
84
+ /**
85
+ * Format duration in milliseconds
86
+ */
87
+ export function formatDuration(ms) {
88
+ if (ms < 1000) {
89
+ return `${ms}ms`;
90
+ }
91
+ const seconds = (ms / 1000).toFixed(1);
92
+ return `${seconds}s`;
93
+ }
94
+ /**
95
+ * Group attributes by prefix (e.g., 'auth.method' -> 'auth')
96
+ */
97
+ export function groupAttributesByPrefix(attributes) {
98
+ const grouped = {};
99
+ for (const [key, value] of Object.entries(attributes)) {
100
+ const parts = key.split('.');
101
+ if (parts.length > 1) {
102
+ const prefix = parts[0];
103
+ if (!grouped[prefix]) {
104
+ grouped[prefix] = {};
105
+ }
106
+ grouped[prefix][key] = value;
107
+ }
108
+ else {
109
+ if (!grouped['Other']) {
110
+ grouped['Other'] = {};
111
+ }
112
+ grouped['Other'][key] = value;
113
+ }
114
+ }
115
+ return grouped;
116
+ }
117
+ /**
118
+ * Filter attributes by pattern (supports glob-like patterns)
119
+ */
120
+ export function filterAttributes(attributes, pattern) {
121
+ const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
122
+ const filtered = {};
123
+ for (const [key, value] of Object.entries(attributes)) {
124
+ if (regex.test(key)) {
125
+ filtered[key] = value;
126
+ }
127
+ }
128
+ return filtered;
129
+ }
130
+ /**
131
+ * Count events by type/name
132
+ */
133
+ export function countEventsByType(events) {
134
+ const counts = new Map();
135
+ for (const event of events) {
136
+ const count = counts.get(event.name) || 0;
137
+ counts.set(event.name, count + 1);
138
+ }
139
+ return counts;
140
+ }
141
+ /**
142
+ * Format attribute value for display
143
+ */
144
+ export function formatValue(value) {
145
+ if (typeof value === 'string') {
146
+ return `"${value}"`;
147
+ }
148
+ if (typeof value === 'number' || typeof value === 'boolean') {
149
+ return String(value);
150
+ }
151
+ if (value === null || value === undefined) {
152
+ return 'null';
153
+ }
154
+ if (typeof value === 'object') {
155
+ return JSON.stringify(value);
156
+ }
157
+ return String(value);
158
+ }
159
+ /**
160
+ * Capitalize first letter of a string
161
+ */
162
+ export function capitalize(str) {
163
+ return str.charAt(0).toUpperCase() + str.slice(1);
164
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createValidateCommand(): Command;
3
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/commands/workflow/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4BpC,wBAAgB,qBAAqB,IAAI,OAAO,CAkY/C"}
@@ -0,0 +1,344 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { resolve, dirname } from 'node:path';
4
+ import { readFileSync, readdirSync, existsSync } from 'node:fs';
5
+ import { readFile } from 'node:fs/promises';
6
+ // Node.js-specific imports (validator)
7
+ import { WorkflowValidator, CanvasDiscovery, EventRegistry, getScopeNames, } from '@principal-ai/subsystems-core/node';
8
+ import { FilesystemService, NodeFileSystemAdapter } from '@principal-ai/codebase-composition/node';
9
+ import { NodeFileSystemAdapter as RepoFsAdapter } from '@principal-ai/repository-abstraction/node';
10
+ import yaml from 'js-yaml';
11
+ // Browser-safe imports
12
+ import { computeAggregates } from '@principal-ai/subsystems-core';
13
+ import { loadWorkflow, resolvePath, loadExecution, executionToEvents } from './utils.js';
14
+ export function createValidateCommand() {
15
+ const command = new Command('validate');
16
+ command
17
+ .description('Validate workflow template syntax, schema, and references')
18
+ .argument('<file>', 'Path to .workflow.json file')
19
+ .option('--canvas <path>', 'Override canvas file path for validation')
20
+ .option('--execution <path>', 'Execution file (.otel.json) for validating attribute references')
21
+ .option('--json', 'Output violations as JSON')
22
+ .option('-q, --quiet', 'Only show errors, suppress warnings')
23
+ .option('-d, --dir <path>', 'Project directory (default: cwd)')
24
+ .action(async (file, options) => {
25
+ try {
26
+ const baseDir = options.dir || process.cwd();
27
+ const workflowPath = resolvePath(file, baseDir);
28
+ // Load workflow
29
+ const workflow = await loadWorkflow(workflowPath);
30
+ // Resolve canvas path
31
+ // Canvas paths are always relative to repository root
32
+ let canvasPath;
33
+ let canvas;
34
+ if (options.canvas) {
35
+ canvasPath = resolvePath(options.canvas, baseDir);
36
+ }
37
+ else if (workflow.canvas) {
38
+ canvasPath = resolve(baseDir, workflow.canvas);
39
+ }
40
+ // Load canvas if path exists
41
+ if (canvasPath) {
42
+ try {
43
+ const canvasContent = readFileSync(canvasPath, 'utf-8');
44
+ canvas = JSON.parse(canvasContent);
45
+ }
46
+ catch (error) {
47
+ // Canvas not found or invalid - will be flagged by validator
48
+ canvas = undefined;
49
+ }
50
+ }
51
+ // Load execution data if provided
52
+ let executionData;
53
+ if (options.execution) {
54
+ try {
55
+ const executionPath = resolvePath(options.execution, baseDir);
56
+ const execution = await loadExecution(executionPath);
57
+ const events = executionToEvents(execution);
58
+ const aggregates = computeAggregates(events);
59
+ // Build event-specific attribute map
60
+ const eventAttributes = new Map();
61
+ for (const event of events) {
62
+ if (!eventAttributes.has(event.name)) {
63
+ eventAttributes.set(event.name, {});
64
+ }
65
+ const attrs = eventAttributes.get(event.name);
66
+ // Merge attributes from this event occurrence
67
+ if (event.attributes) {
68
+ Object.assign(attrs, event.attributes);
69
+ }
70
+ }
71
+ executionData = { aggregates, eventAttributes };
72
+ }
73
+ catch (error) {
74
+ console.error(chalk.yellow('Warning:'), `Failed to load execution file: ${error.message}`);
75
+ console.error(chalk.gray(' Attribute validation will be skipped'));
76
+ }
77
+ }
78
+ // Discover co-located execution files for template completeness validation
79
+ const workflowDir = dirname(workflowPath);
80
+ const executionFiles = [];
81
+ try {
82
+ const filesInDir = readdirSync(workflowDir);
83
+ for (const file of filesInDir) {
84
+ if (file.endsWith('.otel.json')) {
85
+ executionFiles.push(resolve(workflowDir, file));
86
+ }
87
+ }
88
+ }
89
+ catch (error) {
90
+ // Directory not readable, skip co-located file discovery
91
+ }
92
+ // Build EventRegistry and discover workflows for cross-canvas event lookup
93
+ let eventRegistry;
94
+ let allWorkflowEvents;
95
+ try {
96
+ // Load library
97
+ let library;
98
+ const libraryPath = resolve(baseDir, '.principal-views/library.yaml');
99
+ if (existsSync(libraryPath)) {
100
+ try {
101
+ const libraryContent = readFileSync(libraryPath, 'utf-8');
102
+ library = yaml.load(libraryContent);
103
+ }
104
+ catch {
105
+ // Library failed to parse, continue without it
106
+ }
107
+ }
108
+ // Discover all canvases and workflows in the project
109
+ const service = new FilesystemService(new NodeFileSystemAdapter());
110
+ const fileTree = await service.buildFileSystemTreeFromPath(baseDir);
111
+ const fileReader = async (path) => readFile(resolve(baseDir, path), 'utf-8');
112
+ const discovery = new CanvasDiscovery();
113
+ const discoveryResult = await discovery.discover(fileTree, {
114
+ fileReader,
115
+ includeContent: true,
116
+ });
117
+ // Parse all canvases for EventRegistry
118
+ const parsedCanvases = new Map();
119
+ for (const discoveredCanvas of discoveryResult.canvases) {
120
+ if (discoveredCanvas.type === 'otel') {
121
+ try {
122
+ const canvasFullPath = resolve(baseDir, discoveredCanvas.path);
123
+ if (existsSync(canvasFullPath)) {
124
+ const content = readFileSync(canvasFullPath, 'utf-8');
125
+ const parsed = JSON.parse(content);
126
+ parsedCanvases.set(discoveredCanvas.path, parsed);
127
+ }
128
+ }
129
+ catch {
130
+ // Skip canvases that fail to parse
131
+ }
132
+ }
133
+ }
134
+ // Build the registry
135
+ eventRegistry = EventRegistry.build(library, parsedCanvases, library ? libraryPath : undefined);
136
+ // Find all workflows for the same canvas (multi-workflow pattern support)
137
+ if (workflow.canvas) {
138
+ // Find the storyboard(s) that use this canvas
139
+ const storyboardsForCanvas = discoveryResult.storyboards.filter(sb => sb.canvas.path === workflow.canvas);
140
+ // If multiple workflows reference this canvas, collect all their events
141
+ const allWorkflows = storyboardsForCanvas.flatMap(sb => sb.workflows);
142
+ if (allWorkflows.length > 1) {
143
+ allWorkflowEvents = new Set();
144
+ for (const discoveredWorkflow of allWorkflows) {
145
+ // Read workflow content
146
+ try {
147
+ const workflowFullPath = resolve(baseDir, discoveredWorkflow.path);
148
+ const workflowContent = readFileSync(workflowFullPath, 'utf-8');
149
+ const wf = JSON.parse(workflowContent);
150
+ if (wf.scenarios) {
151
+ for (const scenario of wf.scenarios) {
152
+ // From template.events
153
+ if (scenario.template?.events) {
154
+ for (const eventName of Object.keys(scenario.template.events)) {
155
+ if (!eventName.includes('*')) {
156
+ allWorkflowEvents.add(eventName);
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ catch {
164
+ // Skip workflows that fail to parse
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+ catch (error) {
171
+ // Discovery failed - continue without it
172
+ // Validation will still work, just without cross-canvas suggestions
173
+ }
174
+ // Extract owned-scopes from library resources
175
+ let ownedScopes;
176
+ if (eventRegistry) {
177
+ // Get library from the registry's internal state
178
+ // The registry was built with the library, so we can extract scopes from resources
179
+ const libraryPath = resolve(baseDir, '.principal-views/library.yaml');
180
+ if (existsSync(libraryPath)) {
181
+ try {
182
+ const libraryContent = readFileSync(libraryPath, 'utf-8');
183
+ const lib = yaml.load(libraryContent);
184
+ if (lib?.resources) {
185
+ ownedScopes = [];
186
+ for (const [_resourceKey, attrs] of Object.entries(lib.resources)) {
187
+ // Handle both legacy (string[]) and new (Record<string, ScopeDefinition>) formats
188
+ const scopes = attrs['owned-scopes'];
189
+ const scopeNames = getScopeNames(scopes);
190
+ ownedScopes.push(...scopeNames);
191
+ // Also add the service name itself as a valid scope
192
+ const serviceName = attrs['service.name'];
193
+ if (typeof serviceName === 'string') {
194
+ ownedScopes.push(serviceName);
195
+ }
196
+ }
197
+ // Remove duplicates
198
+ ownedScopes = [...new Set(ownedScopes)];
199
+ }
200
+ }
201
+ catch {
202
+ // Failed to extract scopes, continue without
203
+ }
204
+ }
205
+ }
206
+ // Create validator
207
+ const validator = new WorkflowValidator(new RepoFsAdapter());
208
+ // Validate
209
+ const context = {
210
+ workflow,
211
+ workflowPath,
212
+ canvasPath,
213
+ canvas,
214
+ basePath: baseDir,
215
+ executionData,
216
+ executionFiles,
217
+ eventRegistry,
218
+ allWorkflowEvents,
219
+ ownedScopes,
220
+ };
221
+ const result = await validator.validate(context);
222
+ // Filter violations if quiet mode
223
+ const violations = options.quiet
224
+ ? result.violations.filter((v) => v.severity === 'error')
225
+ : result.violations;
226
+ const errors = violations.filter((v) => v.severity === 'error');
227
+ const warnings = violations.filter((v) => v.severity === 'warn');
228
+ // Output
229
+ if (options.json) {
230
+ const output = {
231
+ file: file,
232
+ valid: errors.length === 0,
233
+ violations: violations.map((v) => ({
234
+ severity: v.severity,
235
+ ruleId: v.ruleId,
236
+ file: v.file,
237
+ path: v.path,
238
+ message: v.message,
239
+ impact: v.impact,
240
+ suggestion: v.suggestion,
241
+ fixable: v.fixable,
242
+ })),
243
+ summary: {
244
+ errors: errors.length,
245
+ warnings: warnings.length,
246
+ scenarioCount: workflow.scenarios.length,
247
+ scope: workflow.scope || null,
248
+ status: workflow.status || 'draft',
249
+ attributeValidation: executionData ? 'enabled' : 'skipped',
250
+ },
251
+ };
252
+ console.log(JSON.stringify(output, null, 2));
253
+ }
254
+ else {
255
+ // Text output
256
+ console.log(chalk.bold(`\nValidating: ${file}\n`));
257
+ if (errors.length === 0 && warnings.length === 0) {
258
+ console.log(chalk.green('✓'), 'Schema validation passed');
259
+ console.log(chalk.green('✓'), `${workflow.scenarios.length} scenarios found`);
260
+ const priorities = workflow.scenarios.map((s) => s.priority);
261
+ const allUnique = new Set(priorities).size === priorities.length;
262
+ console.log(chalk.green('✓'), allUnique ? 'All priorities unique' : 'Duplicate priorities found');
263
+ if (canvasPath) {
264
+ console.log(chalk.green('✓'), `Canvas: ${workflow.canvas || canvasPath} ✓`);
265
+ }
266
+ }
267
+ else {
268
+ // Show violations
269
+ for (const violation of violations) {
270
+ const label = violation.severity === 'error'
271
+ ? chalk.red('error')
272
+ : chalk.yellow('warning');
273
+ console.log(`\n${label}: ${violation.message}`);
274
+ if (violation.path) {
275
+ console.log(chalk.gray(` Location: ${violation.path}`));
276
+ }
277
+ if (violation.impact) {
278
+ console.log(chalk.gray(` Impact: ${violation.impact}`));
279
+ }
280
+ if (violation.suggestion) {
281
+ console.log(chalk.cyan(` Suggestion: ${violation.suggestion}`));
282
+ }
283
+ }
284
+ }
285
+ // Summary
286
+ console.log(chalk.bold('\nSummary:'));
287
+ if (errors.length > 0) {
288
+ console.log(chalk.red(` • ${errors.length} error(s)`));
289
+ }
290
+ else {
291
+ console.log(chalk.green(' • 0 errors'));
292
+ }
293
+ if (warnings.length > 0) {
294
+ console.log(chalk.yellow(` • ${warnings.length} warning(s)`));
295
+ }
296
+ else if (!options.quiet) {
297
+ console.log(chalk.green(' • 0 warnings'));
298
+ }
299
+ console.log(chalk.gray(` • ${workflow.scenarios.length} scenario(s)`));
300
+ if (canvasPath) {
301
+ console.log(chalk.gray(` • Canvas: ${workflow.canvas || canvasPath}`));
302
+ }
303
+ // Show scope information
304
+ if (workflow.scope) {
305
+ console.log(chalk.gray(` • Scope: ${workflow.scope}`));
306
+ }
307
+ else {
308
+ const status = workflow.status || 'draft';
309
+ if (status === 'approved' || status === 'implemented') {
310
+ console.log(chalk.yellow(` • Scope: not specified (required for ${status} status)`));
311
+ }
312
+ else {
313
+ console.log(chalk.gray(' • Scope: not specified'));
314
+ }
315
+ }
316
+ // Show status
317
+ const status = workflow.status || 'draft';
318
+ const statusColor = status === 'implemented' ? chalk.green :
319
+ status === 'approved' ? chalk.cyan :
320
+ chalk.gray;
321
+ console.log(chalk.gray(' • Status:'), statusColor(status));
322
+ if (executionData) {
323
+ console.log(chalk.gray(' • Attribute validation:'), chalk.green('enabled'));
324
+ }
325
+ else {
326
+ console.log(chalk.gray(' • Attribute validation:'), chalk.gray('skipped (use --execution to enable)'));
327
+ }
328
+ if (executionFiles.length > 0) {
329
+ console.log(chalk.gray(` • Co-located executions: ${executionFiles.length} file(s) checked`));
330
+ }
331
+ console.log();
332
+ }
333
+ // Exit with error code if validation failed
334
+ if (errors.length > 0) {
335
+ process.exit(1);
336
+ }
337
+ }
338
+ catch (error) {
339
+ console.error(chalk.red('Error:'), error.message);
340
+ process.exit(1);
341
+ }
342
+ });
343
+ return command;
344
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shared file utilities for CLI commands
3
+ */
4
+ /**
5
+ * File types recognized by the CLI
6
+ */
7
+ export type FileType = 'canvas' | 'workflow' | 'testTrace' | 'library' | 'dashboard' | 'unknown';
8
+ /**
9
+ * Determine file type based on naming convention
10
+ *
11
+ * This function is shared between validate and lint commands to ensure
12
+ * consistent file type detection across the CLI.
13
+ */
14
+ export declare function determineFileType(filePath: string): FileType;
15
+ //# sourceMappingURL=file-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../src/file-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;AAEjG;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAmB5D"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared file utilities for CLI commands
3
+ */
4
+ import { basename } from 'node:path';
5
+ /**
6
+ * Determine file type based on naming convention
7
+ *
8
+ * This function is shared between validate and lint commands to ensure
9
+ * consistent file type detection across the CLI.
10
+ */
11
+ export function determineFileType(filePath) {
12
+ const name = basename(filePath).toLowerCase();
13
+ if (name.startsWith('library.')) {
14
+ return 'library';
15
+ }
16
+ if (name.endsWith('.workflow.json')) {
17
+ return 'workflow';
18
+ }
19
+ if (name.endsWith('.dashboard.json')) {
20
+ return 'dashboard';
21
+ }
22
+ if (name.endsWith('.otel.json')) {
23
+ return 'testTrace';
24
+ }
25
+ if (name.endsWith('.canvas') || name.endsWith('.otel.canvas')) {
26
+ return 'canvas';
27
+ }
28
+ return 'unknown';
29
+ }