eventmodeler 0.2.7 → 0.2.8

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/index.js CHANGED
@@ -160,7 +160,10 @@ async function main() {
160
160
  : getDefaultFormat();
161
161
  const command = filteredArgs[0];
162
162
  const subcommand = filteredArgs[1];
163
- const target = filteredArgs[2];
163
+ // Join remaining args as target (allows "show chapter Register Products" without quotes)
164
+ // For commands with named args (--flag), those are already extracted by getNamedArg
165
+ const remainingArgs = filteredArgs.slice(2);
166
+ const target = remainingArgs.length > 0 ? remainingArgs.join(' ') : undefined;
164
167
  if (!command) {
165
168
  openApp();
166
169
  return;
@@ -271,14 +274,20 @@ async function main() {
271
274
  }
272
275
  search(model, subcommand, format);
273
276
  break;
274
- case 'mark':
275
- if (!subcommand || !target) {
277
+ case 'mark': {
278
+ // mark <slice-name> <status> - status is last arg, slice name is everything before
279
+ const validStatuses = ['created', 'in-progress', 'blocked', 'done'];
280
+ const lastArg = remainingArgs[remainingArgs.length - 1];
281
+ if (remainingArgs.length < 2 || !validStatuses.includes(lastArg)) {
276
282
  console.error('Usage: eventmodeler mark <slice-name> <status>');
277
283
  console.error('Valid statuses: created, in-progress, blocked, done');
278
284
  process.exit(1);
279
285
  }
280
- markSliceStatus(model, filePath, subcommand, target);
286
+ const sliceName = remainingArgs.slice(0, -1).join(' ');
287
+ const status = lastArg;
288
+ markSliceStatus(model, filePath, sliceName, status);
281
289
  break;
290
+ }
282
291
  case 'summary':
283
292
  showModelSummary(model, format);
284
293
  break;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Element lookup utilities for CLI commands.
3
- * Provides exact-match lookup with UUID disambiguation for duplicate names.
3
+ * Provides fuzzy lookup with UUID disambiguation for ambiguous matches.
4
4
  */
5
5
  export type LookupResult<T> = {
6
6
  success: true;
@@ -11,10 +11,12 @@ export type LookupResult<T> = {
11
11
  matches: T[];
12
12
  };
13
13
  /**
14
- * Find an element by exact name (case-insensitive) or UUID.
14
+ * Find an element by name (fuzzy) or UUID.
15
15
  * - If search starts with "id:", treats rest as UUID/UUID prefix
16
- * - Otherwise, performs case-insensitive exact name match
17
- * - Returns error if multiple elements have the same name
16
+ * - Otherwise, performs fuzzy name matching:
17
+ * 1. Exact match (case-insensitive, normalized spaces)
18
+ * 2. Partial match (search is contained in name)
19
+ * - Returns error if multiple elements match
18
20
  */
19
21
  export declare function findElement<T extends {
20
22
  id: string;
@@ -1,12 +1,23 @@
1
1
  /**
2
2
  * Element lookup utilities for CLI commands.
3
- * Provides exact-match lookup with UUID disambiguation for duplicate names.
3
+ * Provides fuzzy lookup with UUID disambiguation for ambiguous matches.
4
4
  */
5
5
  /**
6
- * Find an element by exact name (case-insensitive) or UUID.
6
+ * Normalize a string for fuzzy matching:
7
+ * - lowercase
8
+ * - collapse multiple spaces to single space
9
+ * - trim whitespace
10
+ */
11
+ function normalize(str) {
12
+ return str.toLowerCase().replace(/\s+/g, ' ').trim();
13
+ }
14
+ /**
15
+ * Find an element by name (fuzzy) or UUID.
7
16
  * - If search starts with "id:", treats rest as UUID/UUID prefix
8
- * - Otherwise, performs case-insensitive exact name match
9
- * - Returns error if multiple elements have the same name
17
+ * - Otherwise, performs fuzzy name matching:
18
+ * 1. Exact match (case-insensitive, normalized spaces)
19
+ * 2. Partial match (search is contained in name)
20
+ * - Returns error if multiple elements match
10
21
  */
11
22
  export function findElement(elements, search) {
12
23
  const elementArray = Array.isArray(elements) ? elements : [...elements.values()];
@@ -28,14 +39,34 @@ export function findElement(elements, search) {
28
39
  }
29
40
  return { success: false, error: 'not_found', matches: [] };
30
41
  }
31
- // Case-insensitive exact name match
32
- const searchLower = search.toLowerCase();
33
- const matches = elementArray.filter(e => e.name.toLowerCase() === searchLower);
34
- if (matches.length === 1) {
35
- return { success: true, element: matches[0] };
42
+ const searchNorm = normalize(search);
43
+ // 1. Try exact match first (case-insensitive, normalized)
44
+ const exactMatches = elementArray.filter(e => normalize(e.name) === searchNorm);
45
+ if (exactMatches.length === 1) {
46
+ return { success: true, element: exactMatches[0] };
47
+ }
48
+ if (exactMatches.length > 1) {
49
+ return { success: false, error: 'ambiguous', matches: exactMatches };
50
+ }
51
+ // 2. Try partial match (search contained in name)
52
+ const partialMatches = elementArray.filter(e => normalize(e.name).includes(searchNorm));
53
+ if (partialMatches.length === 1) {
54
+ return { success: true, element: partialMatches[0] };
55
+ }
56
+ if (partialMatches.length > 1) {
57
+ return { success: false, error: 'ambiguous', matches: partialMatches };
58
+ }
59
+ // 3. Try word-based match (all search words appear in name)
60
+ const searchWords = searchNorm.split(' ').filter(w => w.length > 0);
61
+ const wordMatches = elementArray.filter(e => {
62
+ const nameNorm = normalize(e.name);
63
+ return searchWords.every(word => nameNorm.includes(word));
64
+ });
65
+ if (wordMatches.length === 1) {
66
+ return { success: true, element: wordMatches[0] };
36
67
  }
37
- if (matches.length > 1) {
38
- return { success: false, error: 'ambiguous', matches };
68
+ if (wordMatches.length > 1) {
69
+ return { success: false, error: 'ambiguous', matches: wordMatches };
39
70
  }
40
71
  return { success: false, error: 'not_found', matches: [] };
41
72
  }
@@ -112,13 +112,22 @@ function getSourceFields(model, sourceId) {
112
112
  return proc.fields;
113
113
  return [];
114
114
  }
115
- function flattenFields(fields, prefix = '') {
115
+ /**
116
+ * Flattens nested Custom fields into a flat list with dot-notation paths.
117
+ * When skipCustomParents is true, Custom fields with subfields are excluded.
118
+ */
119
+ function flattenFields(fields, prefix = '', skipCustomParents = false) {
116
120
  const result = [];
117
121
  for (const field of fields) {
118
122
  const path = prefix ? `${prefix}.${field.name}` : field.name;
119
- result.push({ id: field.id, path, field });
123
+ const isCustomParent = field.fieldType === 'Custom' &&
124
+ field.subfields &&
125
+ field.subfields.length > 0;
126
+ if (!skipCustomParents || !isCustomParent) {
127
+ result.push({ id: field.id, path, field });
128
+ }
120
129
  if (field.fieldType === 'Custom' && field.subfields) {
121
- result.push(...flattenFields(field.subfields, path));
130
+ result.push(...flattenFields(field.subfields, path, skipCustomParents));
122
131
  }
123
132
  }
124
133
  return result;
@@ -158,7 +167,8 @@ function calculateReadModelUnionCompleteness(model, readModelId, readModelFields
158
167
  }
159
168
  }
160
169
  // Check each target field against the union of all sources
161
- const flatTarget = flattenFields(readModelFields);
170
+ // Skip custom parents - only check leaf fields
171
+ const flatTarget = flattenFields(readModelFields, '', true);
162
172
  const untraceableFields = [];
163
173
  const optionalMissingFields = [];
164
174
  const fieldStatuses = [];
@@ -214,7 +224,8 @@ function calculateReadModelUnionCompleteness(model, readModelId, readModelFields
214
224
  }
215
225
  function calculateFlowCompleteness(model, flow, targetFields, sourceFields) {
216
226
  const flatSource = flattenFields(sourceFields);
217
- const flatTarget = flattenFields(targetFields);
227
+ // Skip custom parents for target fields - only check leaf fields
228
+ const flatTarget = flattenFields(targetFields, '', true);
218
229
  const manualMappings = flow.fieldMappings ?? [];
219
230
  // Get source entity name
220
231
  let sourceName = 'Unknown';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eventmodeler",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "CLI tool for interacting with Event Model files - query, update, and export event models from the terminal",
5
5
  "type": "module",
6
6
  "bin": {