@sun-asterisk/sungen 2.3.1 → 2.4.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.
Files changed (105) hide show
  1. package/README.md +10 -11
  2. package/dist/cli/index.js +1 -1
  3. package/dist/generators/gherkin-parser/index.d.ts +8 -0
  4. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  5. package/dist/generators/gherkin-parser/index.js +12 -0
  6. package/dist/generators/gherkin-parser/index.js.map +1 -1
  7. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +1 -1
  8. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +5 -1
  9. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +15 -0
  10. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +7 -1
  11. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +5 -1
  12. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  13. package/dist/generators/test-generator/patterns/index.js +2 -1
  14. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  15. package/dist/generators/test-generator/patterns/table-patterns.d.ts +12 -0
  16. package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +1 -1
  17. package/dist/generators/test-generator/patterns/table-patterns.js +142 -98
  18. package/dist/generators/test-generator/patterns/table-patterns.js.map +1 -1
  19. package/dist/generators/test-generator/patterns/types.d.ts +2 -0
  20. package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
  21. package/dist/generators/test-generator/step-mapper.d.ts +13 -0
  22. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  23. package/dist/generators/test-generator/step-mapper.js +80 -0
  24. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  25. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  26. package/dist/orchestrator/ai-rules-updater.js +8 -6
  27. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  28. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  29. package/dist/orchestrator/project-initializer.js +33 -2
  30. package/dist/orchestrator/project-initializer.js.map +1 -1
  31. package/dist/orchestrator/screen-manager.js +1 -1
  32. package/dist/orchestrator/screen-manager.js.map +1 -1
  33. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
  34. package/{src/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md → dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md} +7 -7
  35. package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +21 -0
  36. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +32 -0
  37. package/dist/orchestrator/templates/ai-instructions/claude-config.md +65 -12
  38. package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +128 -52
  39. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +15 -39
  40. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +72 -259
  41. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +57 -205
  42. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +104 -0
  43. package/dist/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +188 -0
  44. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
  45. package/dist/orchestrator/templates/ai-instructions/{copilot-cmd-make-tc.md → copilot-cmd-create-test.md} +8 -8
  46. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +24 -0
  47. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +35 -0
  48. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +66 -13
  49. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +128 -52
  50. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +15 -39
  51. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +72 -234
  52. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +57 -205
  53. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +104 -0
  54. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +188 -0
  55. package/dist/orchestrator/templates/readme.md +85 -22
  56. package/package.json +1 -1
  57. package/src/cli/index.ts +1 -1
  58. package/src/generators/gherkin-parser/index.ts +23 -0
  59. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +1 -1
  60. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +5 -1
  61. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +15 -0
  62. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +7 -1
  63. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +5 -1
  64. package/src/generators/test-generator/patterns/index.ts +2 -1
  65. package/src/generators/test-generator/patterns/table-patterns.ts +155 -111
  66. package/src/generators/test-generator/patterns/types.ts +2 -0
  67. package/src/generators/test-generator/step-mapper.ts +87 -1
  68. package/src/orchestrator/ai-rules-updater.ts +8 -6
  69. package/src/orchestrator/project-initializer.ts +38 -2
  70. package/src/orchestrator/screen-manager.ts +1 -1
  71. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
  72. package/{dist/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md → src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md} +7 -7
  73. package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +21 -0
  74. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +32 -0
  75. package/src/orchestrator/templates/ai-instructions/claude-config.md +65 -12
  76. package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +128 -52
  77. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +15 -39
  78. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +72 -259
  79. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +57 -205
  80. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +104 -0
  81. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +188 -0
  82. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
  83. package/src/orchestrator/templates/ai-instructions/{copilot-cmd-make-tc.md → copilot-cmd-create-test.md} +8 -8
  84. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +24 -0
  85. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +35 -0
  86. package/src/orchestrator/templates/ai-instructions/copilot-config.md +66 -13
  87. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +128 -52
  88. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +15 -39
  89. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +72 -234
  90. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +57 -205
  91. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +104 -0
  92. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +188 -0
  93. package/src/orchestrator/templates/readme.md +85 -22
  94. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +0 -2
  95. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +0 -2
  96. package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +0 -29
  97. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-review.md +0 -228
  98. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +0 -32
  99. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-review.md +0 -228
  100. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +0 -2
  101. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +0 -2
  102. package/src/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +0 -29
  103. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-review.md +0 -228
  104. package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +0 -32
  105. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-review.md +0 -228
@@ -1,19 +1,49 @@
1
1
  /**
2
2
  * Table Patterns
3
3
  * Handles: table row assertions, table cell lookups, actions in table rows
4
+ *
5
+ * Syntax (v2.4 final):
6
+ * - User see [Col] column in [Table] table # column exists
7
+ * - User see [Ref] row in [Table] table with {{value}} # row exists (enters row scope)
8
+ * - User see [Ref] row in [Table] table with {{value}} is hidden # row hidden
9
+ * - User see [Table] table with {{count}} # row count
10
+ * - User see [Table] table is empty # empty table
11
+ * - User see [Col] column with {{value}} # cell value (row scoped — handled in step-mapper)
12
+ * - User click [Act] button in [Table] table with {{filter}} # action in row
13
+ * - User see [Table] table match data: # exact table match with inline DataTable
14
+ * | Header1 | Header2 |
15
+ * | {{value1}} | {{value2}} |
4
16
  */
5
17
 
6
- import { StepPattern } from './types';
18
+ import { StepPattern, PatternContext } from './types';
19
+
20
+ /** Helper: check if current step is in a Given context */
21
+ function isGivenContext(context: PatternContext): boolean {
22
+ return context.effectiveKeyword === 'Given';
23
+ }
24
+
25
+ /** Map Gherkin element type to Playwright ARIA role */
26
+ const typeToRole: Record<string, string> = {
27
+ button: 'button',
28
+ link: 'link',
29
+ checkbox: 'checkbox',
30
+ icon: 'img',
31
+ image: 'img',
32
+ radio: 'radio',
33
+ switch: 'switch',
34
+ };
7
35
 
8
36
  export const tablePatterns: StepPattern[] = [
9
- // "User see [Users] table with {{count}} rows" (preferred)
10
- // Also accepts: "User see [Users] table has {{count}} rows" (backward compat)
37
+ // "User see [Table] table with {{count}}" — row count (no "rows" keyword needed)
11
38
  {
12
39
  name: 'table-row-count',
13
40
  matcher: (step) => {
14
- return /\btable\s+(?:has|with)\b/i.test(step.text) &&
15
- /\}\}\s*rows?\b/i.test(step.text) &&
16
- !!step.dataRef;
41
+ return step.elementType === 'table' &&
42
+ /\btable\s+with\b/i.test(step.text) &&
43
+ !!step.dataRef &&
44
+ !/\brow\b/i.test(step.text) &&
45
+ !/\bcolumn\b/i.test(step.text) &&
46
+ !/\bempty\b/i.test(step.text);
17
47
  },
18
48
  resolver: (step, context) => {
19
49
  const resolved = context.selectorResolver.resolveSelector(
@@ -21,44 +51,47 @@ export const tablePatterns: StepPattern[] = [
21
51
  );
22
52
  const count = context.dataResolver.resolveData(step.dataRef!, context.featureName);
23
53
 
54
+ const isGiven = isGivenContext(context);
24
55
  return {
25
56
  templateName: 'table-row-count',
26
- data: { ...resolved, expectedCount: count },
27
- comment: `Assert ${step.selectorRef} table has ${count} rows`,
57
+ data: { ...resolved, expectedCount: count, isGiven },
58
+ comment: `${isGiven ? 'Wait' : 'Assert'} ${step.selectorRef} table has ${count} rows`,
28
59
  };
29
60
  },
30
61
  priority: 16,
31
62
  },
32
63
 
33
- // "User see [Users] table has [Email] column" (backward compat)
34
- // Preferred: "User see [Email] column in [Users] table" (parent scoping)
64
+ // "User see [Col] column in [Table] table"
35
65
  {
36
66
  name: 'table-column-exists',
37
67
  matcher: (step) => {
38
- return /\btable\s+has\b/i.test(step.text) &&
39
- /\bcolumn\b/i.test(step.text);
68
+ return /\bcolumn\s+in\b/i.test(step.text) &&
69
+ /\btable\b/i.test(step.text) &&
70
+ !step.dataRef;
40
71
  },
41
72
  resolver: (step, context) => {
42
- // Extract column name from second [bracket] reference
73
+ // brackets[0] = Col, brackets[1] = Table
43
74
  const brackets = step.text.match(/\[([^\]]+)\]/g) || [];
44
- const columnName = brackets.length >= 2
75
+ const columnName = brackets[0]?.replace(/[\[\]]/g, '') || 'Unknown';
76
+ const tableName = brackets.length >= 2
45
77
  ? brackets[1].replace(/[\[\]]/g, '')
46
- : 'Unknown';
78
+ : brackets[0]?.replace(/[\[\]]/g, '') || '';
47
79
 
48
80
  const resolved = context.selectorResolver.resolveSelector(
49
- step.selectorRef!, context.featureName, 'table', step.nth
81
+ tableName, context.featureName, 'table', step.nth
50
82
  );
51
83
 
84
+ const isGiven = isGivenContext(context);
52
85
  return {
53
86
  templateName: 'table-column-exists',
54
- data: { ...resolved, columnName },
55
- comment: `Assert ${step.selectorRef} table has ${columnName} column`,
87
+ data: { ...resolved, columnName, isGiven },
88
+ comment: `${isGiven ? 'Wait' : 'Assert'} ${columnName} column in ${tableName} table`,
56
89
  };
57
90
  },
58
91
  priority: 16,
59
92
  },
60
93
 
61
- // "User see [Users] table is empty"
94
+ // "User see [Table] table is empty"
62
95
  {
63
96
  name: 'table-is-empty',
64
97
  matcher: (step) => {
@@ -69,167 +102,178 @@ export const tablePatterns: StepPattern[] = [
69
102
  step.selectorRef!, context.featureName, 'table', step.nth
70
103
  );
71
104
 
105
+ const isGiven = isGivenContext(context);
72
106
  return {
73
107
  templateName: 'table-empty',
74
- data: { ...resolved },
75
- comment: `Assert ${step.selectorRef} table is empty`,
108
+ data: { ...resolved, isGiven },
109
+ comment: `${isGiven ? 'Wait' : 'Assert'} ${step.selectorRef} table is empty`,
76
110
  };
77
111
  },
78
112
  priority: 16,
79
113
  },
80
114
 
81
- // "User see [Users] table row with {{name}} has [Status] with {{value}}"
115
+ // "User see [Ref] row in [Table] table with {{value}}" — row exists (enters row scope)
82
116
  {
83
- name: 'table-cell-by-filter',
117
+ name: 'table-row-exists',
84
118
  matcher: (step) => {
85
- return /\btable\s+row\s+with\b/i.test(step.text) &&
86
- /\bhas\b/i.test(step.text);
119
+ return step.elementType === 'row' &&
120
+ /\bin\b.*\btable\b/i.test(step.text) &&
121
+ /\bwith\b/i.test(step.text) &&
122
+ !!step.dataRef &&
123
+ !/\bis\s+hidden\b/i.test(step.text);
87
124
  },
88
125
  resolver: (step, context) => {
89
- // Extract: [Table] table row with {{filter}} has [Col] with {{value}}
126
+ // brackets[0] = Ref (row label), brackets[1] = Table
90
127
  const brackets = step.text.match(/\[([^\]]+)\]/g) || [];
91
- const tableName = brackets[0]?.replace(/[\[\]]/g, '') || '';
92
- const columnName = brackets.length >= 2
128
+ const rowLabel = brackets[0]?.replace(/[\[\]]/g, '') || '';
129
+ const tableName = brackets.length >= 2
93
130
  ? brackets[1].replace(/[\[\]]/g, '')
94
131
  : '';
95
132
 
96
- // Extract data refs: first is filter, second is cell value
97
- const dataRefs = step.text.match(/\{\{([^}]+)\}\}/g) || [];
98
- const filterRef = dataRefs[0]?.replace(/\{\{|\}\}/g, '') || '';
99
- const valueRef = dataRefs.length >= 2
100
- ? dataRefs[1].replace(/\{\{|\}\}/g, '')
101
- : '';
102
-
103
- const filterValue = filterRef
104
- ? context.dataResolver.resolveData(filterRef, context.featureName)
105
- : '';
106
- const cellValue = valueRef
107
- ? context.dataResolver.resolveData(valueRef, context.featureName)
108
- : '';
109
-
110
133
  const resolved = context.selectorResolver.resolveSelector(
111
134
  tableName, context.featureName, 'table', step.nth
112
135
  );
136
+ const filterValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
113
137
 
138
+ const isGiven = isGivenContext(context);
114
139
  return {
115
- templateName: 'table-cell-by-filter',
116
- data: { ...resolved, filterValue, columnName, cellValue },
117
- comment: `Assert ${tableName} table row with ${filterRef} has ${columnName} = ${valueRef}`,
140
+ templateName: 'table-row-exists',
141
+ data: { ...resolved, filterValue, isGiven },
142
+ comment: `${isGiven ? 'Wait' : 'Assert'} ${rowLabel} row in ${tableName} table with ${step.dataRef}`,
118
143
  };
119
144
  },
120
145
  priority: 17,
121
146
  },
122
147
 
123
- // "User see [Users] table row 1 [Name] cell with {{value}}"
148
+ // "User see [Ref] row in [Table] table with {{value}} is hidden"
124
149
  {
125
- name: 'table-cell-by-index',
150
+ name: 'table-row-hidden',
126
151
  matcher: (step) => {
127
- return /\btable\s+row\s+\d+\b/i.test(step.text) &&
128
- /\bcell\b/i.test(step.text);
152
+ return step.elementType === 'row' &&
153
+ /\bin\b.*\btable\b/i.test(step.text) &&
154
+ /\bwith\b/i.test(step.text) &&
155
+ !!step.dataRef &&
156
+ /\bis\s+hidden\b/i.test(step.text);
129
157
  },
130
158
  resolver: (step, context) => {
159
+ // brackets[0] = Ref (row label), brackets[1] = Table
131
160
  const brackets = step.text.match(/\[([^\]]+)\]/g) || [];
132
- const tableName = brackets[0]?.replace(/[\[\]]/g, '') || '';
133
- const columnName = brackets.length >= 2
161
+ const rowLabel = brackets[0]?.replace(/[\[\]]/g, '') || '';
162
+ const tableName = brackets.length >= 2
134
163
  ? brackets[1].replace(/[\[\]]/g, '')
135
164
  : '';
136
165
 
137
- const rowMatch = step.text.match(/\brow\s+(\d+)\b/i);
138
- const rowIndex = rowMatch ? parseInt(rowMatch[1], 10) : 1;
139
-
140
- const cellValue = step.dataRef
141
- ? context.dataResolver.resolveData(step.dataRef, context.featureName)
142
- : '';
143
-
144
166
  const resolved = context.selectorResolver.resolveSelector(
145
167
  tableName, context.featureName, 'table', step.nth
146
168
  );
147
-
148
- return {
149
- templateName: 'table-cell-by-index',
150
- data: { ...resolved, rowIndex, columnName, cellValue },
151
- comment: `Assert ${tableName} table row ${rowIndex} ${columnName} cell = ${step.dataRef}`,
152
- };
153
- },
154
- priority: 17,
155
- },
156
-
157
- // "User see [Users] table row with {{name}}" (preferred)
158
- // Also accepts: "User see [Users] table has row with {{name}}" (backward compat)
159
- {
160
- name: 'table-row-exists',
161
- matcher: (step) => {
162
- return /\btable\s+(?:has\s+)?row\s+with\b/i.test(step.text) && !!step.dataRef;
163
- },
164
- resolver: (step, context) => {
165
- const resolved = context.selectorResolver.resolveSelector(
166
- step.selectorRef!, context.featureName, 'table', step.nth
167
- );
168
- const filterValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
169
-
170
- return {
171
- templateName: 'table-row-exists',
172
- data: { ...resolved, filterValue },
173
- comment: `Assert ${step.selectorRef} table has row with ${step.dataRef}`,
174
- };
175
- },
176
- priority: 18,
177
- },
178
-
179
- // "User see [Users] table has no row with {{name}}"
180
- {
181
- name: 'table-row-not-exists',
182
- matcher: (step) => {
183
- return /\btable\s+has\s+no\s+row\s+with\b/i.test(step.text) && !!step.dataRef;
184
- },
185
- resolver: (step, context) => {
186
- const resolved = context.selectorResolver.resolveSelector(
187
- step.selectorRef!, context.featureName, 'table', step.nth
188
- );
189
169
  const filterValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
190
170
 
171
+ const isGiven = isGivenContext(context);
191
172
  return {
192
173
  templateName: 'table-row-not-exists',
193
- data: { ...resolved, filterValue },
194
- comment: `Assert ${step.selectorRef} table has no row with ${step.dataRef}`,
174
+ data: { ...resolved, filterValue, isGiven },
175
+ comment: `${isGiven ? 'Wait' : 'Assert'} ${rowLabel} row in ${tableName} table is hidden`,
195
176
  };
196
177
  },
197
178
  priority: 19,
198
179
  },
199
180
 
200
- // "User click [Edit] in [Users] table row with {{name}}"
181
+ // "User click [Act] button in [Table] table with {{filter}}" — action in row
201
182
  {
202
183
  name: 'table-action-in-row',
203
184
  matcher: (step) => {
204
- return /\bin\b.*\btable\s+row\s+with\b/i.test(step.text);
185
+ return /\bin\b.*\btable\b/i.test(step.text) &&
186
+ /\bwith\b/i.test(step.text) &&
187
+ !!step.dataRef &&
188
+ step.elementType !== 'column' &&
189
+ step.elementType !== 'row' &&
190
+ step.elementType !== 'table';
205
191
  },
206
192
  resolver: (step, context) => {
193
+ // brackets[0] = Act (element name), brackets[1] = Table
207
194
  const brackets = step.text.match(/\[([^\]]+)\]/g) || [];
208
195
  const elementName = brackets[0]?.replace(/[\[\]]/g, '') || '';
209
196
  const tableName = brackets.length >= 2
210
197
  ? brackets[1].replace(/[\[\]]/g, '')
211
198
  : '';
212
199
 
213
- const dataRefs = step.text.match(/\{\{([^}]+)\}\}/g) || [];
214
- const filterRef = dataRefs[0]?.replace(/\{\{|\}\}/g, '') || '';
215
- const filterValue = filterRef
216
- ? context.dataResolver.resolveData(filterRef, context.featureName)
200
+ const filterValue = step.dataRef
201
+ ? context.dataResolver.resolveData(step.dataRef, context.featureName)
217
202
  : '';
218
203
 
219
204
  const tableResolved = context.selectorResolver.resolveSelector(
220
205
  tableName, context.featureName, 'table', 0
221
206
  );
222
207
 
223
- // Determine action
208
+ // Determine action from step text
224
209
  const actionMatch = step.text.match(/\b(click|check|uncheck)\b/i);
225
210
  const action = actionMatch ? actionMatch[1].toLowerCase() : 'click';
226
211
 
212
+ // Extract element type (word after [Act]) and map to ARIA role
213
+ const elementTypeMatch = step.text.match(/\]\s+([\w-]+)\s+in\b/i);
214
+ const elementType = elementTypeMatch ? elementTypeMatch[1].toLowerCase() : 'button';
215
+ const elementRole = typeToRole[elementType] || elementType;
216
+
227
217
  return {
228
218
  templateName: 'table-action-in-row',
229
- data: { ...tableResolved, elementName, filterValue, action },
230
- comment: `${action} ${elementName} in ${tableName} table row with ${filterRef}`,
219
+ data: { ...tableResolved, elementName, filterValue, action, elementRole },
220
+ comment: `${action} ${elementName} in ${tableName} table row with ${step.dataRef}`,
231
221
  };
232
222
  },
233
223
  priority: 17,
234
224
  },
225
+
226
+ // "User see [Table] table match data:" — filter-based table match with inline DataTable
227
+ // First row = headers, remaining rows = expected data
228
+ // Uses filter({ hasText }) per row — resilient to data changes, row ordering, extra rows
229
+ {
230
+ name: 'table-match-data',
231
+ matcher: (step) => {
232
+ return step.elementType === 'table' &&
233
+ /\btable\s+match\s+data\b/i.test(step.text) &&
234
+ !!step.dataTable;
235
+ },
236
+ resolver: (step, context) => {
237
+ const resolved = context.selectorResolver.resolveSelector(
238
+ step.selectorRef!, context.featureName, 'table', step.nth
239
+ );
240
+
241
+ const dataTable = step.dataTable!;
242
+
243
+ // Resolve {{variable}} references in cell values
244
+ const resolvedRows = dataTable.rows.map(row => ({
245
+ cells: row.cells.map(cell => {
246
+ const varMatch = cell.match(/^\{\{([a-z0-9\-\.\_]+)\}\}$/i);
247
+ if (varMatch) {
248
+ return context.dataResolver.resolveData(varMatch[1], context.featureName);
249
+ }
250
+ return cell;
251
+ }),
252
+ }));
253
+
254
+ // Pre-compute filter-based assertion lines
255
+ // For each expected row: chain .filter({ hasText }) for all cell values, then assert visible
256
+ const assertions: string[] = [];
257
+ for (const row of resolvedRows) {
258
+ const filters = row.cells
259
+ .map(cell => `.filter({ hasText: '${cell.replace(/'/g, "\\'")}' })`)
260
+ .join('');
261
+ assertions.push(
262
+ `await expect(rows${filters}).toBeVisible();`
263
+ );
264
+ }
265
+
266
+ const isGiven = isGivenContext(context);
267
+ return {
268
+ templateName: 'table-match-data',
269
+ data: {
270
+ ...resolved,
271
+ assertions,
272
+ isGiven,
273
+ },
274
+ comment: `${isGiven ? 'Wait' : 'Assert'} ${step.selectorRef} table contains ${resolvedRows.length} expected row(s)`,
275
+ };
276
+ },
277
+ priority: 20, // Higher than other table patterns to match first
278
+ },
235
279
  ];
@@ -42,6 +42,8 @@ export interface PatternContext {
42
42
  screenName?: string;
43
43
  featurePath?: string;
44
44
  scenarioSteps?: ParsedStep[];
45
+ /** Effective Gherkin keyword (Given/When/Then), resolved from And/But */
46
+ effectiveKeyword?: string;
45
47
  }
46
48
 
47
49
  /**
@@ -29,6 +29,11 @@ export class StepMapper {
29
29
  private currentScenarioSteps?: ParsedStep[];
30
30
  private inDialogScope: boolean = false;
31
31
  private inFrameScope: boolean = false;
32
+ /** Tracks the last primary keyword (Given/When/Then) for resolving And/But */
33
+ private lastPrimaryKeyword: string = 'Given';
34
+ /** Row scope: when active, column assertions are scoped to this row */
35
+ private inRowScope: boolean = false;
36
+ private rowScopeTable: string = '';
32
37
 
33
38
  constructor(options: { verbose?: boolean; baseURL?: string; featureName?: string; screenName?: string; featurePath?: string } = {}) {
34
39
  this.verbose = options.verbose ?? false;
@@ -74,6 +79,9 @@ export class StepMapper {
74
79
  // Reset dialog and frame scope at the start of each new scenario
75
80
  this.inDialogScope = false;
76
81
  this.inFrameScope = false;
82
+ this.inRowScope = false;
83
+ this.rowScopeTable = '';
84
+ this.lastPrimaryKeyword = 'Given';
77
85
  this.templateEngine.resetBaseContext();
78
86
  }
79
87
 
@@ -84,6 +92,12 @@ export class StepMapper {
84
92
  mapStep(step: ParsedStep): MappedStep | Promise<MappedStep> {
85
93
  this.stepCounter++;
86
94
 
95
+ // Track effective keyword: And/But inherit from previous Given/When/Then
96
+ const keyword = step.keyword.trim();
97
+ if (keyword === 'Given' || keyword === 'When' || keyword === 'Then') {
98
+ this.lastPrimaryKeyword = keyword;
99
+ }
100
+
87
101
  // Frame scope directives — intercept before pattern matching
88
102
  if (/\bswitch(?:es)?\s+to\b/i.test(step.text) &&
89
103
  (step.elementType === 'frame' || step.elementType === 'iframe')) {
@@ -154,11 +168,29 @@ export class StepMapper {
154
168
  return { code: '', comment: `Enter dialog scope${step.selectorRef ? ': ' + step.selectorRef : ''}` };
155
169
  }
156
170
 
171
+ // Row-scoped column assertion: "User see [Col] column with {{value}}"
172
+ // Must be checked BEFORE pattern matching (like dialog/frame scope)
173
+ if (this.inRowScope && step.elementType === 'column' &&
174
+ !!step.dataRef && /\bwith\b/i.test(step.text) &&
175
+ !/\btable\b/i.test(step.text)) {
176
+ return this.generateRowScopedColumnAssertion(step);
177
+ }
178
+
157
179
  // Try pattern registry first
158
180
  const context = this.createPatternContext();
159
181
  const mappedStep = this.patternRegistry.generateStep(step, context);
160
-
182
+
161
183
  if (mappedStep) {
184
+ // Enter row scope when a row-in-table step is matched
185
+ if (step.elementType === 'row' && /\bin\b.*\btable\b/i.test(step.text) &&
186
+ /\bwith\b/i.test(step.text) && !/\bis\s+hidden\b/i.test(step.text)) {
187
+ const brackets = step.text.match(/\[([^\]]+)\]/g) || [];
188
+ this.inRowScope = true;
189
+ this.rowScopeTable = brackets.length >= 2
190
+ ? brackets[1].replace(/[\[\]]/g, '')
191
+ : '';
192
+ }
193
+
162
194
  if (this.verbose) {
163
195
  console.log(` ✓ Pattern matched: ${step.text.substring(0, 50)}...`);
164
196
  }
@@ -172,6 +204,59 @@ export class StepMapper {
172
204
  };
173
205
  }
174
206
 
207
+ /**
208
+ * Generate column assertion scoped to the current row.
209
+ * Uses columns config for exact cell targeting (nth), or filter fallback.
210
+ *
211
+ * With columns: await expect(tableRow.getByRole('cell').nth(index)).toHaveText('value');
212
+ * Without: await expect(tableRow.getByRole('cell').filter({ hasText: 'value' })).toBeVisible();
213
+ */
214
+ private generateRowScopedColumnAssertion(step: ParsedStep): MappedStep {
215
+ const columnRef = step.selectorRef || '';
216
+ const cellValue = step.dataRef
217
+ ? this.dataResolver.resolveData(step.dataRef, this.featureName)
218
+ : '';
219
+
220
+ // Try to find column index from table's columns config
221
+ let columnIndex: number | undefined;
222
+ if (this.rowScopeTable) {
223
+ try {
224
+ const tableResolved = this.selectorResolver.resolveSelector(
225
+ this.rowScopeTable, this.featureName, 'table', 0
226
+ );
227
+ const columnKey = columnRef.toLowerCase().normalize('NFC');
228
+ columnIndex = tableResolved.columns?.[columnKey]?.index;
229
+ } catch {
230
+ // Table not found — fallback to filter
231
+ }
232
+ }
233
+
234
+ const isGiven = this.lastPrimaryKeyword === 'Given';
235
+ const escapedValue = cellValue.replace(/'/g, "\\'");
236
+
237
+ let code: string;
238
+ if (columnIndex !== undefined) {
239
+ // Exact cell: nth(index) + toHaveText
240
+ if (isGiven) {
241
+ code = `await expect(tableRow.getByRole('cell').nth(${columnIndex})).toHaveText('${escapedValue}');`;
242
+ } else {
243
+ code = `await expect(tableRow.getByRole('cell').nth(${columnIndex})).toHaveText('${escapedValue}');`;
244
+ }
245
+ } else {
246
+ // Fallback: filter by text
247
+ if (isGiven) {
248
+ code = `await tableRow.getByRole('cell').filter({ hasText: '${escapedValue}' }).waitFor();`;
249
+ } else {
250
+ code = `await expect(tableRow.getByRole('cell').filter({ hasText: '${escapedValue}' })).toBeVisible();`;
251
+ }
252
+ }
253
+
254
+ return {
255
+ code,
256
+ comment: `${isGiven ? 'Wait' : 'Assert'} ${columnRef} column = ${step.dataRef}`,
257
+ };
258
+ }
259
+
175
260
  /**
176
261
  * Create pattern context for generators
177
262
  */
@@ -191,6 +276,7 @@ export class StepMapper {
191
276
  screenName: this.screenName,
192
277
  featurePath: this.featurePath, // Pass feature path to patterns
193
278
  scenarioSteps: this.currentScenarioSteps || [], // Pass scenario steps for path variables
279
+ effectiveKeyword: this.lastPrimaryKeyword,
194
280
  };
195
281
  }
196
282
 
@@ -16,13 +16,13 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
16
16
 
17
17
  // Commands — Claude Code
18
18
  ['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
19
- ['claude-cmd-make-tc.md', '.claude/commands/sungen/make-tc.md'],
20
- ['claude-cmd-make-test.md', '.claude/commands/sungen/make-test.md'],
19
+ ['claude-cmd-create-test.md', '.claude/commands/sungen/create-test.md'],
20
+ ['claude-cmd-run-test.md', '.claude/commands/sungen/run-test.md'],
21
21
 
22
22
  // Commands — GitHub Copilot
23
23
  ['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
24
- ['copilot-cmd-make-tc.md', '.github/prompts/sungen-make-tc.prompt.md'],
25
- ['copilot-cmd-make-test.md', '.github/prompts/sungen-make-test.prompt.md'],
24
+ ['copilot-cmd-create-test.md', '.github/prompts/sungen-create-test.prompt.md'],
25
+ ['copilot-cmd-run-test.md', '.github/prompts/sungen-run-test.prompt.md'],
26
26
 
27
27
  // Skills — Claude Code
28
28
  ['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
@@ -30,7 +30,8 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
30
30
  ['claude-skill-error-mapping.md', '.claude/skills/sungen-error-mapping/SKILL.md'],
31
31
  ['claude-skill-tc-generation.md', '.claude/skills/sungen-tc-generation/SKILL.md'],
32
32
  ['claude-skill-selector-fix.md', '.claude/skills/sungen-selector-fix/SKILL.md'],
33
- ['claude-skill-gherkin-review.md', '.claude/skills/sungen-gherkin-review/SKILL.md'],
33
+ ['claude-skill-tc-review.md', '.claude/skills/sungen-tc-review/SKILL.md'],
34
+ ['claude-skill-viewpoint.md', '.claude/skills/sungen-viewpoint/SKILL.md'],
34
35
 
35
36
  // Skills — GitHub Copilot
36
37
  ['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
@@ -38,7 +39,8 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
38
39
  ['github-skill-sungen-error-mapping.md', '.github/skills/sungen-error-mapping/SKILL.md'],
39
40
  ['github-skill-sungen-tc-generation.md', '.github/skills/sungen-tc-generation/SKILL.md'],
40
41
  ['github-skill-sungen-selector-fix.md', '.github/skills/sungen-selector-fix/SKILL.md'],
41
- ['github-skill-sungen-gherkin-review.md', '.github/skills/sungen-gherkin-review/SKILL.md'],
42
+ ['github-skill-sungen-tc-review.md', '.github/skills/sungen-tc-review/SKILL.md'],
43
+ ['github-skill-sungen-viewpoint.md', '.github/skills/sungen-viewpoint/SKILL.md'],
42
44
  ];
43
45
 
44
46
  export class AIRulesUpdater {
@@ -6,7 +6,6 @@
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
8
  import { execSync } from 'child_process';
9
- import { AI_RULES_FILE_MAPPING } from './ai-rules-updater';
10
9
 
11
10
  export class ProjectInitializer {
12
11
  private baseCwd: string;
@@ -251,7 +250,44 @@ export class ProjectInitializer {
251
250
  private createAIRules(): void {
252
251
  const aiTemplateDir = path.join(__dirname, 'templates', 'ai-instructions');
253
252
 
254
- for (const [templateFile, outputRelPath] of AI_RULES_FILE_MAPPING) {
253
+ // File mapping: [templateFile, outputPath]
254
+ const fileMapping: [string, string][] = [
255
+ // Config
256
+ ['claude-config.md', 'CLAUDE.md'],
257
+ ['copilot-config.md', '.github/copilot-instructions.md'],
258
+
259
+ // Commands — Claude Code
260
+ ['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
261
+ ['claude-cmd-create-test.md', '.claude/commands/sungen/create-test.md'],
262
+ ['claude-cmd-run-test.md', '.claude/commands/sungen/run-test.md'],
263
+ ['claude-cmd-review.md', '.claude/commands/sungen/review.md'],
264
+
265
+ // Commands — GitHub Copilot
266
+ ['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
267
+ ['copilot-cmd-create-test.md', '.github/prompts/sungen-create-test.prompt.md'],
268
+ ['copilot-cmd-run-test.md', '.github/prompts/sungen-run-test.prompt.md'],
269
+ ['copilot-cmd-review.md', '.github/prompts/sungen-review.prompt.md'],
270
+
271
+ // Skills — Claude Code
272
+ ['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
273
+ ['claude-skill-selector-keys.md', '.claude/skills/sungen-selector-keys/SKILL.md'],
274
+ ['claude-skill-error-mapping.md', '.claude/skills/sungen-error-mapping/SKILL.md'],
275
+ ['claude-skill-tc-generation.md', '.claude/skills/sungen-tc-generation/SKILL.md'],
276
+ ['claude-skill-selector-fix.md', '.claude/skills/sungen-selector-fix/SKILL.md'],
277
+ ['claude-skill-tc-review.md', '.claude/skills/sungen-tc-review/SKILL.md'],
278
+ ['claude-skill-viewpoint.md', '.claude/skills/sungen-viewpoint/SKILL.md'],
279
+
280
+ // Skills — GitHub Copilot (separate copies with Copilot-friendly descriptions)
281
+ ['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
282
+ ['github-skill-sungen-selector-keys.md', '.github/skills/sungen-selector-keys/SKILL.md'],
283
+ ['github-skill-sungen-error-mapping.md', '.github/skills/sungen-error-mapping/SKILL.md'],
284
+ ['github-skill-sungen-tc-generation.md', '.github/skills/sungen-tc-generation/SKILL.md'],
285
+ ['github-skill-sungen-selector-fix.md', '.github/skills/sungen-selector-fix/SKILL.md'],
286
+ ['github-skill-sungen-tc-review.md', '.github/skills/sungen-tc-review/SKILL.md'],
287
+ ['github-skill-sungen-viewpoint.md', '.github/skills/sungen-viewpoint/SKILL.md'],
288
+ ];
289
+
290
+ for (const [templateFile, outputRelPath] of fileMapping) {
255
291
  const outputPath = path.join(this.cwd, outputRelPath);
256
292
 
257
293
  if (fs.existsSync(outputPath)) {
@@ -134,7 +134,7 @@ export class ScreenManager {
134
134
  console.log('Next steps:');
135
135
  console.log(` 1. Fill requirements/spec.md with screen spec (fields, validation, business rules)`);
136
136
  console.log(` Optionally add UI designs to requirements/ui/ (screenshots, mockups)`);
137
- console.log(` 2. Generate test cases: /sungen:make-tc ${screenName} (or /sungen-make-tc)`);
137
+ console.log(` 2. Generate test cases: /sungen:create-test ${screenName} (or /sungen-create-test)`);
138
138
  console.log(` 3. Compile: sungen generate --screen ${screenName}`);
139
139
  console.log(` 4. Run: npx playwright test\n`);
140
140
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: add-screen
3
- description: 'Add a new Sungen screen — scaffolds directories and delegates to make-tc for test case creation'
3
+ description: 'Add a new Sungen screen — scaffolds directories and delegates to create-test for test case creation'
4
4
  argument-hint: [screen-name] [url-path]
5
5
  allowed-tools: Read, Grep, Bash, Glob, AskUserQuestion
6
6
  ---
@@ -37,10 +37,10 @@ Ask the user: "Would you like to fill in `requirements/spec.md` now? This helps
37
37
 
38
38
  Ask the user: "Would you like to create test cases now?"
39
39
 
40
- If yes → **you MUST use the Skill tool** to invoke `/sungen:make-tc <screen>`. This is critical because `make-tc` auto-loads the `sungen-gherkin-syntax` and `sungen-tc-generation` skills which contain the full Gherkin syntax rules, pattern shapes, viewpoint checklists, and output format. **Do NOT attempt to generate test cases yourself** — always invoke the Skill tool so these skills are properly loaded.
40
+ If yes → **you MUST use the Skill tool** to invoke `/sungen:create-test <screen>`. This is critical because `create-test` auto-loads the `sungen-gherkin-syntax` and `sungen-tc-generation` skills which contain the full Gherkin syntax rules, pattern shapes, viewpoint checklists, and output format. **Do NOT attempt to generate test cases yourself** — always invoke the Skill tool so these skills are properly loaded.
41
41
 
42
42
  ```
43
- Skill: make-tc
43
+ Skill: create-test
44
44
  Args: <screen>
45
45
  ```
46
46
 
@@ -48,5 +48,5 @@ Args: <screen>
48
48
 
49
49
  If the user declined test case creation, tell them next steps:
50
50
  - Fill `requirements/spec.md` with screen specs (if not done)
51
- - Run `/sungen:make-tc <screen>` to create test cases
52
- - Run `/sungen:make-test <screen>` to generate selectors, compile, and run tests
51
+ - Run `/sungen:create-test <screen>` to create test cases
52
+ - Run `/sungen:run-test <screen>` to generate selectors, compile, and run tests