@sun-asterisk/sungen 2.3.1 → 2.4.0

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 (90) hide show
  1. package/README.md +10 -11
  2. package/dist/cli/index.js +1 -1
  3. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +1 -1
  4. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +5 -1
  5. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +7 -1
  6. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +5 -1
  7. package/dist/generators/test-generator/patterns/table-patterns.d.ts +9 -0
  8. package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +1 -1
  9. package/dist/generators/test-generator/patterns/table-patterns.js +94 -98
  10. package/dist/generators/test-generator/patterns/table-patterns.js.map +1 -1
  11. package/dist/generators/test-generator/patterns/types.d.ts +2 -0
  12. package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
  13. package/dist/generators/test-generator/step-mapper.d.ts +13 -0
  14. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  15. package/dist/generators/test-generator/step-mapper.js +80 -0
  16. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  17. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  18. package/dist/orchestrator/ai-rules-updater.js +8 -6
  19. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  20. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  21. package/dist/orchestrator/project-initializer.js +33 -2
  22. package/dist/orchestrator/project-initializer.js.map +1 -1
  23. package/dist/orchestrator/screen-manager.js +1 -1
  24. package/dist/orchestrator/screen-manager.js.map +1 -1
  25. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
  26. package/{src/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md → dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md} +7 -7
  27. package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +21 -0
  28. package/{src/orchestrator/templates/ai-instructions/claude-cmd-make-test.md → dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md} +2 -2
  29. package/dist/orchestrator/templates/ai-instructions/claude-config.md +65 -12
  30. package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +128 -52
  31. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +9 -40
  32. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +67 -239
  33. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +40 -208
  34. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +104 -0
  35. package/dist/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +186 -0
  36. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
  37. package/dist/orchestrator/templates/ai-instructions/{copilot-cmd-make-tc.md → copilot-cmd-create-test.md} +8 -8
  38. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +24 -0
  39. package/{src/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md → dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md} +3 -3
  40. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +66 -13
  41. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +128 -52
  42. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +9 -40
  43. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +67 -214
  44. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -208
  45. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +104 -0
  46. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +186 -0
  47. package/dist/orchestrator/templates/readme.md +4 -4
  48. package/package.json +1 -1
  49. package/src/cli/index.ts +1 -1
  50. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +1 -1
  51. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +5 -1
  52. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +7 -1
  53. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +5 -1
  54. package/src/generators/test-generator/patterns/table-patterns.ts +98 -111
  55. package/src/generators/test-generator/patterns/types.ts +2 -0
  56. package/src/generators/test-generator/step-mapper.ts +87 -1
  57. package/src/orchestrator/ai-rules-updater.ts +8 -6
  58. package/src/orchestrator/project-initializer.ts +38 -2
  59. package/src/orchestrator/screen-manager.ts +1 -1
  60. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
  61. package/{dist/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md → src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md} +7 -7
  62. package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +21 -0
  63. package/{dist/orchestrator/templates/ai-instructions/claude-cmd-make-test.md → src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md} +2 -2
  64. package/src/orchestrator/templates/ai-instructions/claude-config.md +65 -12
  65. package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +128 -52
  66. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +9 -40
  67. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +67 -239
  68. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +40 -208
  69. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +104 -0
  70. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +186 -0
  71. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
  72. package/src/orchestrator/templates/ai-instructions/{copilot-cmd-make-tc.md → copilot-cmd-create-test.md} +8 -8
  73. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +24 -0
  74. package/{dist/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md → src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md} +3 -3
  75. package/src/orchestrator/templates/ai-instructions/copilot-config.md +66 -13
  76. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +128 -52
  77. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +9 -40
  78. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +67 -214
  79. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -208
  80. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +104 -0
  81. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +186 -0
  82. package/src/orchestrator/templates/readme.md +4 -4
  83. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +0 -2
  84. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +0 -2
  85. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-review.md +0 -228
  86. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-review.md +0 -228
  87. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +0 -2
  88. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +0 -2
  89. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-review.md +0 -228
  90. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-review.md +0 -228
@@ -1 +1,5 @@
1
- await expect({{> locator}}.getByRole('columnheader', { name: '{{escapeQuotes columnName}}' })).toBeVisible();
1
+ {{~#if isGiven}}
2
+ await {{> locator}}.getByRole('columnheader', { name: '{{escapeQuotes columnName}}' }).waitFor();
3
+ {{~else}}
4
+ await expect({{> locator}}.getByRole('columnheader', { name: '{{escapeQuotes columnName}}' })).toBeVisible();
5
+ {{~/if}}
@@ -1 +1,7 @@
1
- await expect({{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' })).toBeVisible();
1
+ {{~#if isGiven}}
2
+ const tableRow = {{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' });
3
+ await tableRow.waitFor();
4
+ {{~else}}
5
+ const tableRow = {{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' });
6
+ await expect(tableRow).toBeVisible();
7
+ {{~/if}}
@@ -1 +1,5 @@
1
- await expect({{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' })).toHaveCount(0);
1
+ {{~#if isGiven}}
2
+ await {{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' }).waitFor({ state: 'hidden' });
3
+ {{~else}}
4
+ await expect({{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' })).toHaveCount(0);
5
+ {{~/if}}
@@ -1,19 +1,46 @@
1
1
  /**
2
2
  * Table Patterns
3
3
  * Handles: table row assertions, table cell lookups, actions in table rows
4
+ *
5
+ * Syntax (v2.3 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
4
13
  */
5
14
 
6
- import { StepPattern } from './types';
15
+ import { StepPattern, PatternContext } from './types';
16
+
17
+ /** Helper: check if current step is in a Given context */
18
+ function isGivenContext(context: PatternContext): boolean {
19
+ return context.effectiveKeyword === 'Given';
20
+ }
21
+
22
+ /** Map Gherkin element type to Playwright ARIA role */
23
+ const typeToRole: Record<string, string> = {
24
+ button: 'button',
25
+ link: 'link',
26
+ checkbox: 'checkbox',
27
+ icon: 'img',
28
+ image: 'img',
29
+ radio: 'radio',
30
+ switch: 'switch',
31
+ };
7
32
 
8
33
  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)
34
+ // "User see [Table] table with {{count}}" — row count (no "rows" keyword needed)
11
35
  {
12
36
  name: 'table-row-count',
13
37
  matcher: (step) => {
14
- return /\btable\s+(?:has|with)\b/i.test(step.text) &&
15
- /\}\}\s*rows?\b/i.test(step.text) &&
16
- !!step.dataRef;
38
+ return step.elementType === 'table' &&
39
+ /\btable\s+with\b/i.test(step.text) &&
40
+ !!step.dataRef &&
41
+ !/\brow\b/i.test(step.text) &&
42
+ !/\bcolumn\b/i.test(step.text) &&
43
+ !/\bempty\b/i.test(step.text);
17
44
  },
18
45
  resolver: (step, context) => {
19
46
  const resolved = context.selectorResolver.resolveSelector(
@@ -21,44 +48,47 @@ export const tablePatterns: StepPattern[] = [
21
48
  );
22
49
  const count = context.dataResolver.resolveData(step.dataRef!, context.featureName);
23
50
 
51
+ const isGiven = isGivenContext(context);
24
52
  return {
25
53
  templateName: 'table-row-count',
26
- data: { ...resolved, expectedCount: count },
27
- comment: `Assert ${step.selectorRef} table has ${count} rows`,
54
+ data: { ...resolved, expectedCount: count, isGiven },
55
+ comment: `${isGiven ? 'Wait' : 'Assert'} ${step.selectorRef} table has ${count} rows`,
28
56
  };
29
57
  },
30
58
  priority: 16,
31
59
  },
32
60
 
33
- // "User see [Users] table has [Email] column" (backward compat)
34
- // Preferred: "User see [Email] column in [Users] table" (parent scoping)
61
+ // "User see [Col] column in [Table] table"
35
62
  {
36
63
  name: 'table-column-exists',
37
64
  matcher: (step) => {
38
- return /\btable\s+has\b/i.test(step.text) &&
39
- /\bcolumn\b/i.test(step.text);
65
+ return /\bcolumn\s+in\b/i.test(step.text) &&
66
+ /\btable\b/i.test(step.text) &&
67
+ !step.dataRef;
40
68
  },
41
69
  resolver: (step, context) => {
42
- // Extract column name from second [bracket] reference
70
+ // brackets[0] = Col, brackets[1] = Table
43
71
  const brackets = step.text.match(/\[([^\]]+)\]/g) || [];
44
- const columnName = brackets.length >= 2
72
+ const columnName = brackets[0]?.replace(/[\[\]]/g, '') || 'Unknown';
73
+ const tableName = brackets.length >= 2
45
74
  ? brackets[1].replace(/[\[\]]/g, '')
46
- : 'Unknown';
75
+ : brackets[0]?.replace(/[\[\]]/g, '') || '';
47
76
 
48
77
  const resolved = context.selectorResolver.resolveSelector(
49
- step.selectorRef!, context.featureName, 'table', step.nth
78
+ tableName, context.featureName, 'table', step.nth
50
79
  );
51
80
 
81
+ const isGiven = isGivenContext(context);
52
82
  return {
53
83
  templateName: 'table-column-exists',
54
- data: { ...resolved, columnName },
55
- comment: `Assert ${step.selectorRef} table has ${columnName} column`,
84
+ data: { ...resolved, columnName, isGiven },
85
+ comment: `${isGiven ? 'Wait' : 'Assert'} ${columnName} column in ${tableName} table`,
56
86
  };
57
87
  },
58
88
  priority: 16,
59
89
  },
60
90
 
61
- // "User see [Users] table is empty"
91
+ // "User see [Table] table is empty"
62
92
  {
63
93
  name: 'table-is-empty',
64
94
  matcher: (step) => {
@@ -69,165 +99,122 @@ export const tablePatterns: StepPattern[] = [
69
99
  step.selectorRef!, context.featureName, 'table', step.nth
70
100
  );
71
101
 
102
+ const isGiven = isGivenContext(context);
72
103
  return {
73
104
  templateName: 'table-empty',
74
- data: { ...resolved },
75
- comment: `Assert ${step.selectorRef} table is empty`,
105
+ data: { ...resolved, isGiven },
106
+ comment: `${isGiven ? 'Wait' : 'Assert'} ${step.selectorRef} table is empty`,
76
107
  };
77
108
  },
78
109
  priority: 16,
79
110
  },
80
111
 
81
- // "User see [Users] table row with {{name}} has [Status] with {{value}}"
112
+ // "User see [Ref] row in [Table] table with {{value}}" — row exists (enters row scope)
82
113
  {
83
- name: 'table-cell-by-filter',
114
+ name: 'table-row-exists',
84
115
  matcher: (step) => {
85
- return /\btable\s+row\s+with\b/i.test(step.text) &&
86
- /\bhas\b/i.test(step.text);
116
+ return step.elementType === 'row' &&
117
+ /\bin\b.*\btable\b/i.test(step.text) &&
118
+ /\bwith\b/i.test(step.text) &&
119
+ !!step.dataRef &&
120
+ !/\bis\s+hidden\b/i.test(step.text);
87
121
  },
88
122
  resolver: (step, context) => {
89
- // Extract: [Table] table row with {{filter}} has [Col] with {{value}}
123
+ // brackets[0] = Ref (row label), brackets[1] = Table
90
124
  const brackets = step.text.match(/\[([^\]]+)\]/g) || [];
91
- const tableName = brackets[0]?.replace(/[\[\]]/g, '') || '';
92
- const columnName = brackets.length >= 2
125
+ const rowLabel = brackets[0]?.replace(/[\[\]]/g, '') || '';
126
+ const tableName = brackets.length >= 2
93
127
  ? brackets[1].replace(/[\[\]]/g, '')
94
128
  : '';
95
129
 
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
130
  const resolved = context.selectorResolver.resolveSelector(
111
131
  tableName, context.featureName, 'table', step.nth
112
132
  );
133
+ const filterValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
113
134
 
135
+ const isGiven = isGivenContext(context);
114
136
  return {
115
- templateName: 'table-cell-by-filter',
116
- data: { ...resolved, filterValue, columnName, cellValue },
117
- comment: `Assert ${tableName} table row with ${filterRef} has ${columnName} = ${valueRef}`,
137
+ templateName: 'table-row-exists',
138
+ data: { ...resolved, filterValue, isGiven },
139
+ comment: `${isGiven ? 'Wait' : 'Assert'} ${rowLabel} row in ${tableName} table with ${step.dataRef}`,
118
140
  };
119
141
  },
120
142
  priority: 17,
121
143
  },
122
144
 
123
- // "User see [Users] table row 1 [Name] cell with {{value}}"
145
+ // "User see [Ref] row in [Table] table with {{value}} is hidden"
124
146
  {
125
- name: 'table-cell-by-index',
147
+ name: 'table-row-hidden',
126
148
  matcher: (step) => {
127
- return /\btable\s+row\s+\d+\b/i.test(step.text) &&
128
- /\bcell\b/i.test(step.text);
149
+ return step.elementType === 'row' &&
150
+ /\bin\b.*\btable\b/i.test(step.text) &&
151
+ /\bwith\b/i.test(step.text) &&
152
+ !!step.dataRef &&
153
+ /\bis\s+hidden\b/i.test(step.text);
129
154
  },
130
155
  resolver: (step, context) => {
156
+ // brackets[0] = Ref (row label), brackets[1] = Table
131
157
  const brackets = step.text.match(/\[([^\]]+)\]/g) || [];
132
- const tableName = brackets[0]?.replace(/[\[\]]/g, '') || '';
133
- const columnName = brackets.length >= 2
158
+ const rowLabel = brackets[0]?.replace(/[\[\]]/g, '') || '';
159
+ const tableName = brackets.length >= 2
134
160
  ? brackets[1].replace(/[\[\]]/g, '')
135
161
  : '';
136
162
 
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
163
  const resolved = context.selectorResolver.resolveSelector(
145
164
  tableName, context.featureName, 'table', step.nth
146
165
  );
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
166
  const filterValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
190
167
 
168
+ const isGiven = isGivenContext(context);
191
169
  return {
192
170
  templateName: 'table-row-not-exists',
193
- data: { ...resolved, filterValue },
194
- comment: `Assert ${step.selectorRef} table has no row with ${step.dataRef}`,
171
+ data: { ...resolved, filterValue, isGiven },
172
+ comment: `${isGiven ? 'Wait' : 'Assert'} ${rowLabel} row in ${tableName} table is hidden`,
195
173
  };
196
174
  },
197
175
  priority: 19,
198
176
  },
199
177
 
200
- // "User click [Edit] in [Users] table row with {{name}}"
178
+ // "User click [Act] button in [Table] table with {{filter}}" — action in row
201
179
  {
202
180
  name: 'table-action-in-row',
203
181
  matcher: (step) => {
204
- return /\bin\b.*\btable\s+row\s+with\b/i.test(step.text);
182
+ return /\bin\b.*\btable\b/i.test(step.text) &&
183
+ /\bwith\b/i.test(step.text) &&
184
+ !!step.dataRef &&
185
+ step.elementType !== 'column' &&
186
+ step.elementType !== 'row' &&
187
+ step.elementType !== 'table';
205
188
  },
206
189
  resolver: (step, context) => {
190
+ // brackets[0] = Act (element name), brackets[1] = Table
207
191
  const brackets = step.text.match(/\[([^\]]+)\]/g) || [];
208
192
  const elementName = brackets[0]?.replace(/[\[\]]/g, '') || '';
209
193
  const tableName = brackets.length >= 2
210
194
  ? brackets[1].replace(/[\[\]]/g, '')
211
195
  : '';
212
196
 
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)
197
+ const filterValue = step.dataRef
198
+ ? context.dataResolver.resolveData(step.dataRef, context.featureName)
217
199
  : '';
218
200
 
219
201
  const tableResolved = context.selectorResolver.resolveSelector(
220
202
  tableName, context.featureName, 'table', 0
221
203
  );
222
204
 
223
- // Determine action
205
+ // Determine action from step text
224
206
  const actionMatch = step.text.match(/\b(click|check|uncheck)\b/i);
225
207
  const action = actionMatch ? actionMatch[1].toLowerCase() : 'click';
226
208
 
209
+ // Extract element type (word after [Act]) and map to ARIA role
210
+ const elementTypeMatch = step.text.match(/\]\s+([\w-]+)\s+in\b/i);
211
+ const elementType = elementTypeMatch ? elementTypeMatch[1].toLowerCase() : 'button';
212
+ const elementRole = typeToRole[elementType] || elementType;
213
+
227
214
  return {
228
215
  templateName: 'table-action-in-row',
229
- data: { ...tableResolved, elementName, filterValue, action },
230
- comment: `${action} ${elementName} in ${tableName} table row with ${filterRef}`,
216
+ data: { ...tableResolved, elementName, filterValue, action, elementRole },
217
+ comment: `${action} ${elementName} in ${tableName} table row with ${step.dataRef}`,
231
218
  };
232
219
  },
233
220
  priority: 17,
@@ -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