@principal-ai/principal-view-cli 0.1.30 → 0.1.31

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 (35) hide show
  1. package/dist/commands/lint.d.ts.map +1 -1
  2. package/dist/commands/lint.js +13 -8
  3. package/dist/commands/narrative/eval.d.ts +3 -0
  4. package/dist/commands/narrative/eval.d.ts.map +1 -0
  5. package/dist/commands/narrative/eval.js +76 -0
  6. package/dist/commands/narrative/index.d.ts +3 -0
  7. package/dist/commands/narrative/index.d.ts.map +1 -0
  8. package/dist/commands/narrative/index.js +19 -0
  9. package/dist/commands/narrative/inspect.d.ts +3 -0
  10. package/dist/commands/narrative/inspect.d.ts.map +1 -0
  11. package/dist/commands/narrative/inspect.js +109 -0
  12. package/dist/commands/narrative/list.d.ts +3 -0
  13. package/dist/commands/narrative/list.d.ts.map +1 -0
  14. package/dist/commands/narrative/list.js +101 -0
  15. package/dist/commands/narrative/render.d.ts +3 -0
  16. package/dist/commands/narrative/render.d.ts.map +1 -0
  17. package/dist/commands/narrative/render.js +99 -0
  18. package/dist/commands/narrative/test.d.ts +3 -0
  19. package/dist/commands/narrative/test.d.ts.map +1 -0
  20. package/dist/commands/narrative/test.js +150 -0
  21. package/dist/commands/narrative/utils.d.ts +69 -0
  22. package/dist/commands/narrative/utils.d.ts.map +1 -0
  23. package/dist/commands/narrative/utils.js +158 -0
  24. package/dist/commands/narrative/validate.d.ts +3 -0
  25. package/dist/commands/narrative/validate.d.ts.map +1 -0
  26. package/dist/commands/narrative/validate.js +135 -0
  27. package/dist/commands/validate-execution.d.ts +12 -0
  28. package/dist/commands/validate-execution.d.ts.map +1 -0
  29. package/dist/commands/validate-execution.js +230 -0
  30. package/dist/commands/validate.d.ts.map +1 -1
  31. package/dist/commands/validate.js +0 -4
  32. package/dist/index.js +4 -0
  33. package/package.json +3 -2
  34. package/dist/index.cjs +0 -24870
  35. package/dist/index.cjs.map +0 -7
@@ -1 +1 @@
1
- {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwTpC,wBAAgB,iBAAiB,IAAI,OAAO,CAwQ3C"}
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsUpC,wBAAgB,iBAAiB,IAAI,OAAO,CA+P3C"}
@@ -255,6 +255,19 @@ function formatJsonOutput(results) {
255
255
  };
256
256
  }
257
257
  // ============================================================================
258
+ // Helper Functions
259
+ // ============================================================================
260
+ /**
261
+ * Count violations by rule ID
262
+ */
263
+ function countByRule(violations) {
264
+ const counts = {};
265
+ for (const v of violations) {
266
+ counts[v.ruleId] = (counts[v.ruleId] || 0) + 1;
267
+ }
268
+ return counts;
269
+ }
270
+ // ============================================================================
258
271
  // Command Implementation
259
272
  // ============================================================================
260
273
  export function createLintCommand() {
@@ -364,14 +377,6 @@ export function createLintCommand() {
364
377
  console.log(chalk.yellow(`Warning: Could not load library from ${libraryPath}`));
365
378
  }
366
379
  }
367
- // Helper function to count violations by rule
368
- function countByRule(violations) {
369
- const counts = {};
370
- for (const v of violations) {
371
- counts[v.ruleId] = (counts[v.ruleId] || 0) + 1;
372
- }
373
- return counts;
374
- }
375
380
  // Create validators
376
381
  const engine = createDefaultRulesEngine();
377
382
  const narrativeValidator = createNarrativeValidator();
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createEvalCommand(): Command;
3
+ //# sourceMappingURL=eval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eval.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/eval.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,iBAAiB,IAAI,OAAO,CA2E3C"}
@@ -0,0 +1,76 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { evaluateExpression } from '@principal-ai/principal-view-core';
5
+ import { resolvePath, formatValue } from './utils.js';
6
+ export function createEvalCommand() {
7
+ const command = new Command('eval');
8
+ command
9
+ .description('Evaluate template expression with context')
10
+ .argument('<expression>', 'Template expression to evaluate (e.g., {count > 5 ? \'many\' : \'few\'})')
11
+ .argument('[context]', 'Optional path to JSON context file')
12
+ .option('--context <json>', 'Inline JSON context as string')
13
+ .option('--json', 'Output result as JSON')
14
+ .action(async (expression, contextPath, options) => {
15
+ try {
16
+ let context = {};
17
+ // Load context from file or inline option
18
+ if (contextPath) {
19
+ const content = await readFile(resolvePath(contextPath), 'utf-8');
20
+ context = JSON.parse(content);
21
+ }
22
+ else if (options.context) {
23
+ context = JSON.parse(options.context);
24
+ }
25
+ // Remove curly braces if user included them
26
+ let cleanExpression = expression.trim();
27
+ if (cleanExpression.startsWith('{') && cleanExpression.endsWith('}')) {
28
+ cleanExpression = cleanExpression.slice(1, -1).trim();
29
+ }
30
+ // Evaluate expression
31
+ const result = evaluateExpression(cleanExpression, context);
32
+ if (options.json) {
33
+ const output = {
34
+ expression: cleanExpression,
35
+ context,
36
+ result,
37
+ type: typeof result,
38
+ };
39
+ console.log(JSON.stringify(output, null, 2));
40
+ }
41
+ else {
42
+ console.log(chalk.bold('\nExpression:'), chalk.cyan(`{${cleanExpression}}`));
43
+ if (Object.keys(context).length > 0) {
44
+ console.log(chalk.bold('\nContext:'));
45
+ for (const [key, value] of Object.entries(context)) {
46
+ console.log(chalk.gray(' •'), `${key} = ${formatValue(value)}`);
47
+ }
48
+ }
49
+ else {
50
+ console.log(chalk.yellow('\nNo context provided'));
51
+ }
52
+ console.log(chalk.bold('\nResult:'), formatValue(result));
53
+ console.log(chalk.gray('Type:'), typeof result);
54
+ console.log();
55
+ }
56
+ }
57
+ catch (error) {
58
+ const errorMessage = error.message;
59
+ if (options.json) {
60
+ const output = {
61
+ error: true,
62
+ message: errorMessage,
63
+ expression: expression,
64
+ };
65
+ console.log(JSON.stringify(output, null, 2));
66
+ }
67
+ else {
68
+ console.error(chalk.red('\nEvaluation Error:'), errorMessage);
69
+ console.error(chalk.gray('\nExpression:'), expression);
70
+ console.log();
71
+ }
72
+ process.exit(1);
73
+ }
74
+ });
75
+ return command;
76
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createNarrativeCommand(): Command;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,wBAAgB,sBAAsB,IAAI,OAAO,CAahD"}
@@ -0,0 +1,19 @@
1
+ import { Command } from 'commander';
2
+ import { createValidateCommand } from './validate.js';
3
+ import { createInspectCommand } from './inspect.js';
4
+ import { createRenderCommand } from './render.js';
5
+ import { createTestCommand } from './test.js';
6
+ import { createEvalCommand } from './eval.js';
7
+ import { createListCommand } from './list.js';
8
+ export function createNarrativeCommand() {
9
+ const command = new Command('narrative');
10
+ command
11
+ .description('Validate, test, and debug narrative templates')
12
+ .addCommand(createValidateCommand())
13
+ .addCommand(createInspectCommand())
14
+ .addCommand(createRenderCommand())
15
+ .addCommand(createTestCommand())
16
+ .addCommand(createEvalCommand())
17
+ .addCommand(createListCommand());
18
+ return command;
19
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createInspectCommand(): Command;
3
+ //# sourceMappingURL=inspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/inspect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBpC,wBAAgB,oBAAoB,IAAI,OAAO,CAiI9C"}
@@ -0,0 +1,109 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { computeAggregates } from '@principal-ai/principal-view-core';
4
+ import { loadExecution, executionToEvents, resolvePath, formatTimestamp, formatDuration, groupAttributesByPrefix, filterAttributes, countEventsByType, formatValue, capitalize, } from './utils.js';
5
+ export function createInspectCommand() {
6
+ const command = new Command('inspect');
7
+ command
8
+ .description('Inspect execution file and show available attributes for templates')
9
+ .argument('<execution>', 'Path to .otel.json execution file')
10
+ .option('--events', 'Show all events')
11
+ .option('--aggregates', 'Show computed aggregates (default)', true)
12
+ .option('--json', 'Output as JSON')
13
+ .option('--filter <pattern>', 'Filter attributes by pattern (e.g., auth.*)')
14
+ .action(async (execution, options) => {
15
+ try {
16
+ const executionPath = resolvePath(execution);
17
+ // Load execution
18
+ const executionData = await loadExecution(executionPath);
19
+ const events = executionToEvents(executionData);
20
+ // Compute aggregates
21
+ const aggregates = computeAggregates(events);
22
+ // Apply filter if provided
23
+ const filteredAggregates = options.filter
24
+ ? filterAttributes(aggregates, options.filter)
25
+ : aggregates;
26
+ // Count events by type
27
+ const eventCounts = countEventsByType(events);
28
+ // Calculate time range
29
+ const timestamps = events.map((e) => typeof e.timestamp === 'string' ? parseInt(e.timestamp, 10) : e.timestamp);
30
+ const minTime = Math.min(...timestamps);
31
+ const maxTime = Math.max(...timestamps);
32
+ const duration = maxTime - minTime;
33
+ if (options.json) {
34
+ const output = {
35
+ file: execution,
36
+ summary: {
37
+ eventCount: events.length,
38
+ spanCount: executionData.spans.length,
39
+ timeRange: {
40
+ start: minTime,
41
+ end: maxTime,
42
+ duration: duration,
43
+ },
44
+ status: executionData.metadata?.status || 'UNKNOWN',
45
+ },
46
+ eventTypes: Array.from(eventCounts.entries()).map(([name, count]) => ({
47
+ name,
48
+ count,
49
+ })),
50
+ attributes: filteredAggregates,
51
+ };
52
+ if (options.events) {
53
+ output.events = events.map((e) => ({
54
+ name: e.name,
55
+ timestamp: e.timestamp,
56
+ attributes: e.attributes,
57
+ }));
58
+ }
59
+ console.log(JSON.stringify(output, null, 2));
60
+ }
61
+ else {
62
+ // Text output
63
+ console.log(chalk.bold(`\nInspecting: ${execution}\n`));
64
+ // Execution Summary
65
+ console.log(chalk.bold('Execution Summary:'));
66
+ console.log('━'.repeat(60));
67
+ console.log(chalk.gray(' • Total Events:'), events.length);
68
+ console.log(chalk.gray(' • Total Spans:'), executionData.spans.length);
69
+ console.log(chalk.gray(' • Time Range:'), `${formatTimestamp(minTime)} → ${formatTimestamp(maxTime)} (${formatDuration(duration)})`);
70
+ const status = executionData.metadata?.status || 'UNKNOWN';
71
+ const statusColor = status === 'OK' ? chalk.green : chalk.red;
72
+ console.log(chalk.gray(' • Status:'), statusColor(status));
73
+ // Event Types
74
+ console.log(chalk.bold('\nEvent Types:'));
75
+ console.log('━'.repeat(60));
76
+ for (const [name, count] of eventCounts) {
77
+ console.log(chalk.gray(' •'), `${name} (${count})`);
78
+ }
79
+ // Available Attributes
80
+ console.log(chalk.bold('\nAvailable Attributes (for templates):'));
81
+ console.log('━'.repeat(60));
82
+ const grouped = groupAttributesByPrefix(filteredAggregates);
83
+ for (const [prefix, attrs] of Object.entries(grouped)) {
84
+ console.log(chalk.bold(`\n${capitalize(prefix)}:`));
85
+ for (const [key, value] of Object.entries(attrs)) {
86
+ console.log(chalk.gray(' •'), `${key}: ${formatValue(value)}`);
87
+ }
88
+ }
89
+ // Show events if requested
90
+ if (options.events) {
91
+ console.log(chalk.bold('\nEvents:'));
92
+ console.log('━'.repeat(60));
93
+ for (const event of events) {
94
+ console.log(chalk.cyan(`\n[${formatTimestamp(typeof event.timestamp === 'string' ? parseInt(event.timestamp, 10) : event.timestamp)}]`), chalk.bold(event.name));
95
+ for (const [key, value] of Object.entries(event.attributes || {})) {
96
+ console.log(chalk.gray(' •'), `${key}: ${formatValue(value)}`);
97
+ }
98
+ }
99
+ }
100
+ console.log();
101
+ }
102
+ }
103
+ catch (error) {
104
+ console.error(chalk.red('Error:'), error.message);
105
+ process.exit(1);
106
+ }
107
+ });
108
+ return command;
109
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createListCommand(): Command;
3
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,wBAAgB,iBAAiB,IAAI,OAAO,CA6G3C"}
@@ -0,0 +1,101 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { access } from 'node:fs/promises';
4
+ import { resolve, dirname, join } from 'node:path';
5
+ import { globby } from 'globby';
6
+ import { loadNarrative } from './utils.js';
7
+ export function createListCommand() {
8
+ const command = new Command('list');
9
+ command
10
+ .description('List all narrative files in project')
11
+ .argument('[dir]', 'Directory to search (default: .principal-views/)')
12
+ .option('--json', 'Output as JSON')
13
+ .option('--show-canvas', 'Show linked canvas files')
14
+ .action(async (dir, options) => {
15
+ try {
16
+ const searchDir = dir || '.principal-views';
17
+ const searchPath = resolve(process.cwd(), searchDir);
18
+ // Find all .narrative.json files
19
+ const files = await globby('**/*.narrative.json', {
20
+ cwd: searchPath,
21
+ ignore: ['node_modules/**', '.git/**', '__executions__/**'],
22
+ });
23
+ // Load each narrative and check canvas existence
24
+ const narratives = await Promise.all(files.map(async (file) => {
25
+ const fullPath = join(searchPath, file);
26
+ const narrative = await loadNarrative(fullPath);
27
+ let canvasExists;
28
+ if (options.showCanvas && narrative.canvas) {
29
+ const narrativeDir = dirname(fullPath);
30
+ const canvasPath = resolve(narrativeDir, narrative.canvas);
31
+ try {
32
+ await access(canvasPath);
33
+ canvasExists = true;
34
+ }
35
+ catch {
36
+ canvasExists = false;
37
+ }
38
+ }
39
+ const defaultCount = narrative.scenarios.filter((s) => s.condition.default).length;
40
+ return {
41
+ file: join(searchDir, file),
42
+ canvas: narrative.canvas,
43
+ canvasExists,
44
+ scenarioCount: narrative.scenarios.length,
45
+ defaultCount,
46
+ mode: narrative.mode,
47
+ name: narrative.name,
48
+ };
49
+ }));
50
+ if (options.json) {
51
+ const output = {
52
+ searchDir,
53
+ count: narratives.length,
54
+ narratives: narratives.map((n) => ({
55
+ file: n.file,
56
+ name: n.name,
57
+ canvas: n.canvas,
58
+ canvasExists: n.canvasExists,
59
+ scenarios: n.scenarioCount,
60
+ defaultScenarios: n.defaultCount,
61
+ mode: n.mode,
62
+ })),
63
+ };
64
+ console.log(JSON.stringify(output, null, 2));
65
+ }
66
+ else {
67
+ console.log(chalk.bold('\nNarrative Templates:'));
68
+ console.log('━'.repeat(60));
69
+ if (narratives.length === 0) {
70
+ console.log(chalk.yellow(`\nNo narrative templates found in ${searchDir}`));
71
+ console.log();
72
+ return;
73
+ }
74
+ for (const narrative of narratives) {
75
+ console.log(chalk.bold(`\n${narrative.file}`));
76
+ if (narrative.name) {
77
+ console.log(chalk.gray(` Name: ${narrative.name}`));
78
+ }
79
+ if (options.showCanvas && narrative.canvas) {
80
+ const status = narrative.canvasExists
81
+ ? chalk.green('✓')
82
+ : chalk.red('✗');
83
+ console.log(chalk.gray(` Canvas: ${narrative.canvas} ${status}`));
84
+ }
85
+ else if (narrative.canvas) {
86
+ console.log(chalk.gray(` Canvas: ${narrative.canvas}`));
87
+ }
88
+ console.log(chalk.gray(` Scenarios: ${narrative.scenarioCount} (${narrative.defaultCount} default)`));
89
+ console.log(chalk.gray(` Mode: ${narrative.mode}`));
90
+ }
91
+ console.log(chalk.bold(`\nFound ${narratives.length} narrative template(s)`));
92
+ console.log();
93
+ }
94
+ }
95
+ catch (error) {
96
+ console.error(chalk.red('Error:'), error.message);
97
+ process.exit(1);
98
+ }
99
+ });
100
+ return command;
101
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createRenderCommand(): Command;
3
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBpC,wBAAgB,mBAAmB,IAAI,OAAO,CAmH7C"}
@@ -0,0 +1,99 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { renderNarrative } from '@principal-ai/principal-view-core';
4
+ import { loadNarrative, loadExecution, executionToEvents, resolvePath, } from './utils.js';
5
+ export function createRenderCommand() {
6
+ const command = new Command('render');
7
+ command
8
+ .description('Render narrative template using execution data')
9
+ .argument('<narrative>', 'Path to .narrative.json file')
10
+ .argument('<execution>', 'Path to .otel.json execution file')
11
+ .option('--mode <mode>', 'Override rendering mode: span-tree, timeline, summary-only')
12
+ .option('--scenario <id>', 'Force specific scenario (skip auto-selection)')
13
+ .option('--json', 'Output structured result as JSON')
14
+ .option('--format <format>', 'Output format: text (default), markdown, json', 'text')
15
+ .option('--show-metadata', 'Include rendering metadata in output')
16
+ .action(async (narrativePath, executionPath, options) => {
17
+ try {
18
+ const narrative = await loadNarrative(resolvePath(narrativePath));
19
+ const executionData = await loadExecution(resolvePath(executionPath));
20
+ const events = executionToEvents(executionData);
21
+ // Override mode if specified
22
+ if (options.mode) {
23
+ const validModes = ['span-tree', 'timeline', 'summary-only'];
24
+ if (!validModes.includes(options.mode)) {
25
+ throw new Error(`Invalid mode: ${options.mode}. Must be one of: ${validModes.join(', ')}`);
26
+ }
27
+ narrative.mode = options.mode;
28
+ }
29
+ // Render narrative
30
+ const result = renderNarrative(narrative, events);
31
+ // Get the selected scenario from the result
32
+ const selectedScenario = narrative.scenarios.find((s) => s.id === result.scenarioId);
33
+ // Force scenario if specified
34
+ if (options.scenario) {
35
+ const scenario = narrative.scenarios.find((s) => s.id === options.scenario);
36
+ if (!scenario) {
37
+ throw new Error(`Scenario not found: ${options.scenario}`);
38
+ }
39
+ // Note: This would require a way to force scenario in renderNarrative API
40
+ // For now, we'll just validate the scenario exists
41
+ }
42
+ if (options.json) {
43
+ const output = {
44
+ narrative: narrativePath,
45
+ execution: executionPath,
46
+ mode: narrative.mode,
47
+ scenario: {
48
+ id: selectedScenario?.id,
49
+ priority: selectedScenario?.priority,
50
+ matched: true,
51
+ },
52
+ text: result.text,
53
+ };
54
+ if (options.showMetadata) {
55
+ output.metadata = result.metadata;
56
+ }
57
+ console.log(JSON.stringify(output, null, 2));
58
+ }
59
+ else {
60
+ // Text output
61
+ if (!options.format || options.format === 'text') {
62
+ console.log(chalk.gray(`Rendering: ${narrativePath}`));
63
+ console.log(chalk.gray(`Execution: ${executionPath}`));
64
+ console.log(chalk.gray(`Mode: ${narrative.mode}`));
65
+ if (selectedScenario) {
66
+ console.log(chalk.gray(`Scenario: ${selectedScenario.id} (priority: ${selectedScenario.priority})`));
67
+ }
68
+ console.log();
69
+ console.log('━'.repeat(60));
70
+ console.log();
71
+ console.log(result.text);
72
+ console.log();
73
+ console.log('━'.repeat(60));
74
+ if (options.showMetadata && result.metadata) {
75
+ console.log();
76
+ console.log(chalk.bold('Metadata:'));
77
+ console.log(chalk.gray(' • Event Count:'), result.metadata.eventCount);
78
+ console.log(chalk.gray(' • Span Count:'), result.metadata.spanCount);
79
+ if (result.metadata.timeRange) {
80
+ console.log(chalk.gray(' • Time Range:'), `${result.metadata.timeRange.start} → ${result.metadata.timeRange.end}`);
81
+ }
82
+ }
83
+ console.log();
84
+ }
85
+ else if (options.format === 'markdown') {
86
+ console.log(result.text);
87
+ }
88
+ else if (options.format === 'json') {
89
+ console.log(JSON.stringify({ text: result.text }, null, 2));
90
+ }
91
+ }
92
+ }
93
+ catch (error) {
94
+ console.error(chalk.red('Error:'), error.message);
95
+ process.exit(1);
96
+ }
97
+ });
98
+ return command;
99
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createTestCommand(): Command;
3
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsBpC,wBAAgB,iBAAiB,IAAI,OAAO,CAuL3C"}
@@ -0,0 +1,150 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { selectScenario, matchesCondition, computeAggregates, hasEventMatching, } from '@principal-ai/principal-view-core';
4
+ import { loadNarrative, loadExecution, executionToEvents, resolvePath, formatValue, } from './utils.js';
5
+ export function createTestCommand() {
6
+ const command = new Command('test');
7
+ command
8
+ .description('Test scenario matching and show why scenarios match or don\'t match')
9
+ .argument('<narrative>', 'Path to .narrative.json file')
10
+ .argument('<execution>', 'Path to .otel.json execution file')
11
+ .option('--show-all', 'Show all scenarios (not just matches)')
12
+ .option('--show-aggregates', 'Display computed aggregates')
13
+ .option('--json', 'Output as JSON')
14
+ .action(async (narrativePath, executionPath, options) => {
15
+ try {
16
+ const narrative = await loadNarrative(resolvePath(narrativePath));
17
+ const executionData = await loadExecution(resolvePath(executionPath));
18
+ const events = executionToEvents(executionData);
19
+ // Compute aggregates
20
+ const aggregates = computeAggregates(events);
21
+ // Test each scenario
22
+ const scenarioResults = narrative.scenarios.map((scenario) => {
23
+ const condition = scenario.condition;
24
+ const matched = matchesCondition(condition, events, aggregates);
25
+ let reason;
26
+ const requiresResults = [];
27
+ const excludesResults = [];
28
+ // Check requires
29
+ if (condition.requires) {
30
+ for (const pattern of condition.requires) {
31
+ const hasMatch = hasEventMatching(events, pattern);
32
+ const count = events.filter((e) => hasEventMatching([e], pattern)).length;
33
+ requiresResults.push({ pattern, matched: hasMatch, count });
34
+ if (!hasMatch && !reason) {
35
+ reason = `Missing required event '${pattern}'`;
36
+ }
37
+ }
38
+ }
39
+ // Check excludes
40
+ if (condition.excludes) {
41
+ for (const pattern of condition.excludes) {
42
+ const hasMatch = hasEventMatching(events, pattern);
43
+ const count = events.filter((e) => hasEventMatching([e], pattern)).length;
44
+ excludesResults.push({ pattern, matched: hasMatch, count });
45
+ if (hasMatch && !reason) {
46
+ reason = `Found excluded event '${pattern}'`;
47
+ }
48
+ }
49
+ }
50
+ // Default scenario
51
+ if (condition.default) {
52
+ reason = 'Default scenario (always matches)';
53
+ }
54
+ return {
55
+ scenario,
56
+ matched,
57
+ reason,
58
+ requiresResults,
59
+ excludesResults,
60
+ };
61
+ });
62
+ // Select the winning scenario
63
+ const matchResult = selectScenario(narrative, events, aggregates);
64
+ const selectedScenario = matchResult.scenario;
65
+ if (options.json) {
66
+ const output = {
67
+ narrative: narrativePath,
68
+ execution: executionPath,
69
+ scenarios: scenarioResults.map((r) => ({
70
+ id: r.scenario.id,
71
+ priority: r.scenario.priority,
72
+ matched: r.matched,
73
+ reason: r.reason,
74
+ requires: r.requiresResults,
75
+ excludes: r.excludesResults,
76
+ })),
77
+ selectedScenario: selectedScenario?.id,
78
+ aggregates: options.showAggregates ? aggregates : undefined,
79
+ };
80
+ console.log(JSON.stringify(output, null, 2));
81
+ }
82
+ else {
83
+ // Text output
84
+ console.log(chalk.bold(`\nTesting: ${narrativePath}`));
85
+ console.log(chalk.gray(`Execution: ${executionPath}\n`));
86
+ console.log(chalk.bold('Scenario Matching Results:'));
87
+ console.log('━'.repeat(60));
88
+ // Sort by priority (lower = higher priority)
89
+ const sorted = [...scenarioResults].sort((a, b) => a.scenario.priority - b.scenario.priority);
90
+ for (const result of sorted) {
91
+ if (!options.showAll && !result.matched) {
92
+ continue;
93
+ }
94
+ const icon = result.matched ? chalk.green('✓') : chalk.red('✗');
95
+ const status = result.matched
96
+ ? chalk.green('MATCHED')
97
+ : chalk.gray('NOT MATCHED');
98
+ console.log(`\n${icon} ${chalk.bold(result.scenario.id)} (priority: ${result.scenario.priority}) - ${status}`);
99
+ // Show requires
100
+ if (result.requiresResults.length > 0) {
101
+ console.log(chalk.gray(' Requires:'));
102
+ for (const req of result.requiresResults) {
103
+ const reqIcon = req.matched ? chalk.green('✓') : chalk.red('✗');
104
+ const countMsg = req.matched ? `Found ${req.count} event(s)` : 'No matching events';
105
+ console.log(` ${reqIcon} ${req.pattern} - ${countMsg}`);
106
+ }
107
+ }
108
+ // Show excludes
109
+ if (result.excludesResults.length > 0) {
110
+ console.log(chalk.gray(' Excludes:'));
111
+ for (const exc of result.excludesResults) {
112
+ const excIcon = exc.matched ? chalk.red('✗') : chalk.green('✓');
113
+ const countMsg = exc.matched ? `Found ${exc.count} event(s)` : 'No matching events';
114
+ console.log(` ${excIcon} ${exc.pattern} - ${countMsg}`);
115
+ }
116
+ }
117
+ if (result.reason && !result.matched) {
118
+ console.log(chalk.gray(` Reason: ${result.reason}`));
119
+ }
120
+ if (result.scenario.condition.default) {
121
+ console.log(chalk.gray(' Default scenario (always matches)'));
122
+ }
123
+ }
124
+ console.log('\n' + '━'.repeat(60));
125
+ if (selectedScenario) {
126
+ console.log(chalk.bold('\nSelected Scenario:'), chalk.cyan(`${selectedScenario.id} (priority: ${selectedScenario.priority})`));
127
+ }
128
+ else {
129
+ console.log(chalk.yellow('\nNo scenario selected'));
130
+ }
131
+ if (options.showAggregates) {
132
+ console.log(chalk.bold('\nComputed Aggregates:'));
133
+ for (const [key, value] of Object.entries(aggregates)) {
134
+ console.log(chalk.gray(' •'), `${key}: ${formatValue(value)}`);
135
+ }
136
+ }
137
+ console.log();
138
+ }
139
+ // Exit with error if no scenario matched
140
+ if (!selectedScenario) {
141
+ process.exit(1);
142
+ }
143
+ }
144
+ catch (error) {
145
+ console.error(chalk.red('Error:'), error.message);
146
+ process.exit(1);
147
+ }
148
+ });
149
+ return command;
150
+ }