@sun-asterisk/sungen 2.7.0-beta.1 β†’ 3.0.0-beta.71

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 (245) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/commands/add.js +3 -3
  3. package/dist/cli/commands/add.js.map +1 -1
  4. package/dist/cli/commands/audit.d.ts +3 -0
  5. package/dist/cli/commands/audit.d.ts.map +1 -0
  6. package/dist/cli/commands/audit.js +134 -0
  7. package/dist/cli/commands/audit.js.map +1 -0
  8. package/dist/cli/commands/blindspot.d.ts +3 -0
  9. package/dist/cli/commands/blindspot.d.ts.map +1 -0
  10. package/dist/cli/commands/blindspot.js +58 -0
  11. package/dist/cli/commands/blindspot.js.map +1 -0
  12. package/dist/cli/commands/challenge.d.ts +3 -0
  13. package/dist/cli/commands/challenge.d.ts.map +1 -0
  14. package/dist/cli/commands/challenge.js +102 -0
  15. package/dist/cli/commands/challenge.js.map +1 -0
  16. package/dist/cli/commands/feedback.d.ts +3 -0
  17. package/dist/cli/commands/feedback.d.ts.map +1 -0
  18. package/dist/cli/commands/feedback.js +72 -0
  19. package/dist/cli/commands/feedback.js.map +1 -0
  20. package/dist/cli/commands/generate.d.ts.map +1 -1
  21. package/dist/cli/commands/generate.js +22 -0
  22. package/dist/cli/commands/generate.js.map +1 -1
  23. package/dist/cli/commands/ledger.d.ts +3 -0
  24. package/dist/cli/commands/ledger.d.ts.map +1 -0
  25. package/dist/cli/commands/ledger.js +71 -0
  26. package/dist/cli/commands/ledger.js.map +1 -0
  27. package/dist/cli/commands/manifest.d.ts +3 -0
  28. package/dist/cli/commands/manifest.d.ts.map +1 -0
  29. package/dist/cli/commands/manifest.js +101 -0
  30. package/dist/cli/commands/manifest.js.map +1 -0
  31. package/dist/cli/commands/script-check.d.ts +3 -0
  32. package/dist/cli/commands/script-check.d.ts.map +1 -0
  33. package/dist/cli/commands/script-check.js +97 -0
  34. package/dist/cli/commands/script-check.js.map +1 -0
  35. package/dist/cli/commands/trace.d.ts +3 -0
  36. package/dist/cli/commands/trace.d.ts.map +1 -0
  37. package/dist/cli/commands/trace.js +110 -0
  38. package/dist/cli/commands/trace.js.map +1 -0
  39. package/dist/cli/commands/update.d.ts.map +1 -1
  40. package/dist/cli/commands/update.js +22 -9
  41. package/dist/cli/commands/update.js.map +1 -1
  42. package/dist/cli/index.js +16 -0
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +1 -0
  45. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +7 -0
  46. package/dist/generators/test-generator/patterns/capture-patterns.d.ts +16 -0
  47. package/dist/generators/test-generator/patterns/capture-patterns.d.ts.map +1 -0
  48. package/dist/generators/test-generator/patterns/capture-patterns.js +54 -0
  49. package/dist/generators/test-generator/patterns/capture-patterns.js.map +1 -0
  50. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  51. package/dist/generators/test-generator/patterns/index.js +2 -0
  52. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  53. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  54. package/dist/generators/test-generator/step-mapper.js +1 -0
  55. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  56. package/dist/generators/test-generator/utils/data-resolver.d.ts +5 -0
  57. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  58. package/dist/generators/test-generator/utils/data-resolver.js +17 -0
  59. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  60. package/dist/harness/audit.d.ts +24 -0
  61. package/dist/harness/audit.d.ts.map +1 -0
  62. package/dist/harness/audit.js +115 -0
  63. package/dist/harness/audit.js.map +1 -0
  64. package/dist/harness/blindspot.d.ts +15 -0
  65. package/dist/harness/blindspot.d.ts.map +1 -0
  66. package/dist/harness/blindspot.js +85 -0
  67. package/dist/harness/blindspot.js.map +1 -0
  68. package/dist/harness/catalog/universal-viewpoints.yaml +114 -0
  69. package/dist/harness/challenge.d.ts +21 -0
  70. package/dist/harness/challenge.d.ts.map +1 -0
  71. package/dist/harness/challenge.js +151 -0
  72. package/dist/harness/challenge.js.map +1 -0
  73. package/dist/harness/feedback.d.ts +29 -0
  74. package/dist/harness/feedback.d.ts.map +1 -0
  75. package/dist/harness/feedback.js +106 -0
  76. package/dist/harness/feedback.js.map +1 -0
  77. package/dist/harness/intent.d.ts +11 -0
  78. package/dist/harness/intent.d.ts.map +1 -0
  79. package/dist/harness/intent.js +86 -0
  80. package/dist/harness/intent.js.map +1 -0
  81. package/dist/harness/ledger.d.ts +42 -0
  82. package/dist/harness/ledger.d.ts.map +1 -0
  83. package/dist/harness/ledger.js +171 -0
  84. package/dist/harness/ledger.js.map +1 -0
  85. package/dist/harness/manifest.d.ts +42 -0
  86. package/dist/harness/manifest.d.ts.map +1 -0
  87. package/dist/harness/manifest.js +209 -0
  88. package/dist/harness/manifest.js.map +1 -0
  89. package/dist/harness/parse.d.ts +22 -0
  90. package/dist/harness/parse.d.ts.map +1 -0
  91. package/dist/harness/parse.js +163 -0
  92. package/dist/harness/parse.js.map +1 -0
  93. package/dist/harness/script-check.d.ts +16 -0
  94. package/dist/harness/script-check.d.ts.map +1 -0
  95. package/dist/harness/script-check.js +169 -0
  96. package/dist/harness/script-check.js.map +1 -0
  97. package/dist/harness/secret-scan.d.ts +8 -0
  98. package/dist/harness/secret-scan.d.ts.map +1 -0
  99. package/dist/harness/secret-scan.js +88 -0
  100. package/dist/harness/secret-scan.js.map +1 -0
  101. package/dist/harness/sensors.d.ts +88 -0
  102. package/dist/harness/sensors.d.ts.map +1 -0
  103. package/dist/harness/sensors.js +232 -0
  104. package/dist/harness/sensors.js.map +1 -0
  105. package/dist/harness/trace.d.ts +31 -0
  106. package/dist/harness/trace.d.ts.map +1 -0
  107. package/dist/harness/trace.js +173 -0
  108. package/dist/harness/trace.js.map +1 -0
  109. package/dist/orchestrator/ai-rules-updater.d.ts +1 -0
  110. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  111. package/dist/orchestrator/ai-rules-updater.js +55 -11
  112. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  113. package/dist/orchestrator/figma/spec-figma-renderer.d.ts +2 -2
  114. package/dist/orchestrator/figma/spec-figma-renderer.js +2 -2
  115. package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts +1 -1
  116. package/dist/orchestrator/figma/spec-figma-section-renderers.js +1 -1
  117. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  118. package/dist/orchestrator/project-initializer.js +10 -6
  119. package/dist/orchestrator/project-initializer.js.map +1 -1
  120. package/dist/orchestrator/templates/ai-instructions/claude-agent-challenge.md +46 -0
  121. package/dist/orchestrator/templates/ai-instructions/claude-agent-discovery.md +32 -0
  122. package/dist/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +37 -0
  123. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +3 -3
  124. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
  125. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +36 -12
  126. package/dist/orchestrator/templates/ai-instructions/claude-cmd-design.md +12 -0
  127. package/dist/orchestrator/templates/ai-instructions/claude-cmd-feedback.md +36 -0
  128. package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +27 -30
  129. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +4 -1
  130. package/dist/orchestrator/templates/ai-instructions/claude-config.md +1 -4
  131. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-mcp.md +82 -0
  132. package/dist/orchestrator/templates/ai-instructions/{github-skill-sungen-figma-source.md β†’ claude-skill-capture-mode-figma-pat.md} +14 -48
  133. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-live.md +60 -0
  134. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-local.md +38 -0
  135. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture.md +35 -0
  136. package/dist/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +84 -0
  137. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +40 -1
  138. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +3 -3
  139. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
  140. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +18 -10
  141. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-design.md +13 -0
  142. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +24 -0
  143. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +20 -30
  144. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +2 -1
  145. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +1 -4
  146. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md +82 -0
  147. package/{src/orchestrator/templates/ai-instructions/claude-skill-figma-source.md β†’ dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-pat.md} +14 -48
  148. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md +60 -0
  149. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md +38 -0
  150. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md +35 -0
  151. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +84 -0
  152. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +1 -1
  153. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -1
  154. package/dist/orchestrator/templates/specs-test-data.ts +9 -0
  155. package/dist/tools/figma/figma-auth.d.ts +5 -2
  156. package/dist/tools/figma/figma-auth.d.ts.map +1 -1
  157. package/dist/tools/figma/figma-auth.js +19 -9
  158. package/dist/tools/figma/figma-auth.js.map +1 -1
  159. package/docs/orchestration-spec.md +267 -0
  160. package/package.json +10 -6
  161. package/src/cli/commands/add.ts +3 -3
  162. package/src/cli/commands/audit.ts +92 -0
  163. package/src/cli/commands/blindspot.ts +48 -0
  164. package/src/cli/commands/challenge.ts +55 -0
  165. package/src/cli/commands/feedback.ts +65 -0
  166. package/src/cli/commands/generate.ts +19 -0
  167. package/src/cli/commands/ledger.ts +61 -0
  168. package/src/cli/commands/manifest.ts +55 -0
  169. package/src/cli/commands/script-check.ts +50 -0
  170. package/src/cli/commands/trace.ts +60 -0
  171. package/src/cli/commands/update.ts +30 -10
  172. package/src/cli/index.ts +16 -0
  173. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +1 -0
  174. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +7 -0
  175. package/src/generators/test-generator/patterns/capture-patterns.ts +59 -0
  176. package/src/generators/test-generator/patterns/index.ts +2 -0
  177. package/src/generators/test-generator/step-mapper.ts +1 -0
  178. package/src/generators/test-generator/utils/data-resolver.ts +20 -0
  179. package/src/harness/audit.ts +112 -0
  180. package/src/harness/blindspot.ts +51 -0
  181. package/src/harness/catalog/universal-viewpoints.yaml +114 -0
  182. package/src/harness/challenge.ts +131 -0
  183. package/src/harness/feedback.ts +84 -0
  184. package/src/harness/intent.ts +58 -0
  185. package/src/harness/ledger.ts +155 -0
  186. package/src/harness/manifest.ts +173 -0
  187. package/src/harness/parse.ts +145 -0
  188. package/src/harness/script-check.ts +149 -0
  189. package/src/harness/secret-scan.ts +51 -0
  190. package/src/harness/sensors.ts +279 -0
  191. package/src/harness/trace.ts +138 -0
  192. package/src/orchestrator/ai-rules-updater.ts +57 -10
  193. package/src/orchestrator/figma/spec-figma-renderer.ts +2 -2
  194. package/src/orchestrator/figma/spec-figma-section-renderers.ts +1 -1
  195. package/src/orchestrator/project-initializer.ts +10 -7
  196. package/src/orchestrator/templates/ai-instructions/claude-agent-challenge.md +46 -0
  197. package/src/orchestrator/templates/ai-instructions/claude-agent-discovery.md +32 -0
  198. package/src/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +37 -0
  199. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +3 -3
  200. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
  201. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +36 -12
  202. package/src/orchestrator/templates/ai-instructions/claude-cmd-design.md +12 -0
  203. package/src/orchestrator/templates/ai-instructions/claude-cmd-feedback.md +36 -0
  204. package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +27 -30
  205. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +4 -1
  206. package/src/orchestrator/templates/ai-instructions/claude-config.md +1 -4
  207. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-mcp.md +82 -0
  208. package/{dist/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md β†’ src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-pat.md} +14 -48
  209. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-live.md +60 -0
  210. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-local.md +38 -0
  211. package/src/orchestrator/templates/ai-instructions/claude-skill-capture.md +35 -0
  212. package/src/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +84 -0
  213. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +40 -1
  214. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +3 -3
  215. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
  216. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +18 -10
  217. package/src/orchestrator/templates/ai-instructions/copilot-cmd-design.md +13 -0
  218. package/src/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +24 -0
  219. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +20 -30
  220. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +2 -1
  221. package/src/orchestrator/templates/ai-instructions/copilot-config.md +1 -4
  222. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md +82 -0
  223. package/{dist/orchestrator/templates/ai-instructions/claude-skill-figma-source.md β†’ src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-pat.md} +14 -48
  224. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md +60 -0
  225. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md +38 -0
  226. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md +35 -0
  227. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +84 -0
  228. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +1 -1
  229. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -1
  230. package/src/orchestrator/templates/specs-test-data.ts +9 -0
  231. package/src/tools/figma/figma-auth.ts +20 -9
  232. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +0 -142
  233. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +0 -112
  234. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +0 -73
  235. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +0 -142
  236. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +0 -112
  237. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +0 -73
  238. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +0 -142
  239. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +0 -112
  240. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +0 -73
  241. package/src/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +0 -151
  242. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +0 -142
  243. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +0 -112
  244. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +0 -73
  245. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +0 -151
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Harness Sensors β€” deterministic quality measurement over test-design artifacts.
3
+ * Each sensor returns a structured finding the Orchestrator/Repair loop can act on.
4
+ *
5
+ * NOTE (honesty): the Viewpoint Gate and Duplicate sensors involve semantics, so
6
+ * they are HEURISTIC (keyword / skeleton based), not provably exhaustive. They use
7
+ * a curated seed catalog as the matching reference. See docs/orchestration-spec.md Β§5.2.
8
+ */
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import { parse as parseYaml } from 'yaml';
12
+ import { ScenarioInfo, ViewpointEntry } from './parse';
13
+
14
+ // Business-critical category codes (project VP-<CAT> prefixes). Configurable later.
15
+ const BUSINESS_CRITICAL_CATS = ['LIST', 'CART', 'PRODUCT', 'FILTER', 'CHECKOUT', 'ORDER'];
16
+
17
+ // Buckets for coverage-balance.
18
+ const BUCKETS: Record<string, string[]> = {
19
+ 'business-core': BUSINESS_CRITICAL_CATS,
20
+ 'presentation': ['UI'],
21
+ 'validation-security': ['VAL', 'SEC', 'SUB'],
22
+ 'behavior': ['LOGIC'],
23
+ 'navigation': ['NAV'],
24
+ };
25
+
26
+ export interface ThemeDepth {
27
+ requires: string; // 'data-assertion' β†’ scenarios on this theme must assert DATA
28
+ cross_screen?: boolean; // genuine depth needs another screen β†’ flow / @manual-deferred
29
+ keywords?: string[]; // precise data-noun keywords for depth matching
30
+ template?: string; // the deep step the generator should emit by default
31
+ }
32
+ export interface CatalogTheme { theme: string; keywords: string[]; depth?: ThemeDepth }
33
+ export interface Catalog {
34
+ page_types: Record<string, { detect_keywords: string[]; must_cover: CatalogTheme[] }>;
35
+ universal: CatalogTheme[];
36
+ }
37
+
38
+ export function loadCatalog(): Catalog {
39
+ const p = path.join(__dirname, 'catalog', 'universal-viewpoints.yaml');
40
+ return parseYaml(fs.readFileSync(p, 'utf-8')) as Catalog;
41
+ }
42
+
43
+ const has = (haystacks: string[], kw: string) => {
44
+ const k = kw.toLowerCase();
45
+ return haystacks.some((h) => h.includes(k));
46
+ };
47
+
48
+ // ---------- Sensor 1: Viewpoint Gate (catalog-driven) ----------
49
+
50
+ export interface GateResult {
51
+ pageType: string | null;
52
+ themesTotal: number;
53
+ themesCovered: number; // deeply covered (has a data assertion)
54
+ coverageRatio: number;
55
+ gaps: { theme: string; keywords: string[]; status: 'missing' | 'shallow' }[];
56
+ universalGaps: string[];
57
+ }
58
+
59
+ export function viewpointGate(scenarios: ScenarioInfo[], viewpoints: ViewpointEntry[], catalog: Catalog): GateResult {
60
+ const haystacks = [
61
+ ...scenarios.map((s) => s.haystack),
62
+ ...viewpoints.map((v) => `${v.id} ${v.reason}`.toLowerCase()),
63
+ ];
64
+
65
+ // Detect page-type by detect_keywords hit count
66
+ let pageType: string | null = null;
67
+ let best = 0;
68
+ for (const [pt, def] of Object.entries(catalog.page_types)) {
69
+ const hits = def.detect_keywords.filter((k) => has(haystacks, k)).length;
70
+ if (hits > best) { best = hits; pageType = pt; }
71
+ }
72
+
73
+ const gaps: GateResult['gaps'] = [];
74
+ let total = 0, covered = 0;
75
+ if (pageType) {
76
+ for (const t of catalog.page_types[pageType].must_cover) {
77
+ total++;
78
+ // Scenarios that cover this theme by keyword (include @manual β€” a manual
79
+ // scenario with a real assertion still covers the viewpoint for design).
80
+ const coverers = scenarios.filter((s) => t.keywords.some((k) => s.haystack.includes(k.toLowerCase())));
81
+ const deep = coverers.some((s) => s.hasDataAssertion);
82
+ if (deep) covered++;
83
+ else if (coverers.length > 0) gaps.push({ theme: t.theme, keywords: t.keywords, status: 'shallow' });
84
+ else gaps.push({ theme: t.theme, keywords: t.keywords, status: 'missing' });
85
+ }
86
+ }
87
+
88
+ const universalGaps = catalog.universal
89
+ .filter((t) => !t.keywords.some((k) => has(scenarios.map((s) => s.haystack), k)))
90
+ .map((t) => t.theme);
91
+
92
+ return {
93
+ pageType,
94
+ themesTotal: total,
95
+ themesCovered: covered,
96
+ coverageRatio: total ? covered / total : 1,
97
+ gaps,
98
+ universalGaps,
99
+ };
100
+ }
101
+
102
+ // ---------- Sensor 2: Assertion depth ----------
103
+
104
+ export type DepthVerdict = 'pass' | 'warn' | 'fail';
105
+
106
+ export interface DepthResult {
107
+ total: number;
108
+ shallowTotal: number;
109
+ shallowRatio: number;
110
+ businessCriticalTotal: number; // = depth-required scenarios (catalog data-themes)
111
+ businessCriticalShallow: number; // = depth-required scenarios that are shallow
112
+ bcDepthRatio: number; // fraction of depth-required scenarios with a real data assertion
113
+ shallowBusinessCritical: { name: string; category?: string }[];
114
+ // Depth-as-Gate (harness-roadmap P1)
115
+ focus: string; // intent focus driving the threshold
116
+ threshold: number; // required bcDepthRatio for this focus
117
+ verdict: DepthVerdict; // pass | warn | fail
118
+ }
119
+
120
+ // Intent β†’ required depth ratio + whether a miss WARNs (smoke) or FAILs the gate.
121
+ // P3 will read `focus` from qa/context.md; P1 uses 'functional' by default.
122
+ const DEPTH_THRESHOLDS: Record<string, number> = {
123
+ functional: 0.7, 'e-commerce': 0.7, security: 0.8, smoke: 0.4,
124
+ };
125
+ const WARN_ONLY_FOCUS = new Set(['smoke']);
126
+
127
+ /**
128
+ * Depth = do DATA-correctness scenarios actually assert DATA (not just visibility)?
129
+ * "Depth-required" is CATALOG-DRIVEN: only scenarios matching a theme whose
130
+ * `depth.requires === 'data-assertion'` are measured. Navigation viewpoints
131
+ * (category NAV) are excluded β€” landing on a page IS their correct assertion.
132
+ * This avoids the old keyword-based false-positives (e.g. "API list page").
133
+ */
134
+ export function assertionDepth(
135
+ scenarios: ScenarioInfo[],
136
+ dataThemes: CatalogTheme[] = [],
137
+ focus = 'functional',
138
+ ): DepthResult {
139
+ const nonManual = scenarios.filter((s) => !s.manual);
140
+ const shallow = nonManual.filter((s) => s.shallow);
141
+
142
+ // Precise depth keywords come from each data-theme's depth.keywords (fallback: theme keywords).
143
+ const depthKeywords = dataThemes.flatMap((t) => (t.depth?.keywords?.length ? t.depth.keywords : t.keywords));
144
+ // Dismiss/close behaviors ("Continue Shopping closes the modal", "Escape dismisses the
145
+ // dialog") assert a HIDDEN state β€” that IS the correct, data-light assertion for the
146
+ // behavior. They only match a data keyword incidentally (e.g. "added"), so exclude them.
147
+ const isDismissBehavior = (s: ScenarioInfo) =>
148
+ /\bis hidden\b|\bare hidden\b|\bdismiss|\bdisappear|\bno longer (visible|shown|present|displayed)\b|\bcloses? the (modal|dialog|popup|overlay|panel|menu)\b/.test(s.haystack);
149
+ const isDepthRequired = (s: ScenarioInfo) =>
150
+ s.category !== 'NAV' && !isDismissBehavior(s) && depthKeywords.some((k) => s.haystack.includes(k.toLowerCase()));
151
+
152
+ const required = nonManual.filter(isDepthRequired);
153
+ const reqShallow = required.filter((s) => s.shallow);
154
+ // No data-theme scenarios on this screen β†’ depth is not the binding constraint
155
+ // (the viewpoint gate already flags missing data themes). Don't double-penalize.
156
+ const ratio = required.length ? 1 - reqShallow.length / required.length : 1;
157
+
158
+ const threshold = DEPTH_THRESHOLDS[focus] ?? DEPTH_THRESHOLDS.functional;
159
+ let verdict: DepthVerdict = 'pass';
160
+ if (ratio < threshold) verdict = WARN_ONLY_FOCUS.has(focus) ? 'warn' : 'fail';
161
+
162
+ return {
163
+ total: nonManual.length,
164
+ shallowTotal: shallow.length,
165
+ shallowRatio: nonManual.length ? shallow.length / nonManual.length : 0,
166
+ businessCriticalTotal: required.length,
167
+ businessCriticalShallow: reqShallow.length,
168
+ bcDepthRatio: ratio,
169
+ shallowBusinessCritical: reqShallow.map((s) => ({ name: s.name, category: s.category })),
170
+ focus,
171
+ threshold,
172
+ verdict,
173
+ };
174
+ }
175
+
176
+ /** Collect data-correctness themes (depth.requires) for a page-type + universal. */
177
+ export function dataThemesFor(catalog: Catalog, pageType: string | null): CatalogTheme[] {
178
+ const themes: CatalogTheme[] = [];
179
+ if (pageType && catalog.page_types[pageType]) themes.push(...catalog.page_types[pageType].must_cover);
180
+ themes.push(...catalog.universal);
181
+ return themes.filter((t) => t.depth?.requires === 'data-assertion');
182
+ }
183
+
184
+ // ---------- Sensor 3: Coverage balance ----------
185
+
186
+ export interface BalanceResult {
187
+ byBucket: Record<string, number>;
188
+ byCategory: Record<string, number>;
189
+ coreCount: number;
190
+ secondaryCount: number;
191
+ imbalanced: boolean;
192
+ note: string;
193
+ }
194
+
195
+ export function coverageBalance(scenarios: ScenarioInfo[]): BalanceResult {
196
+ const byCategory: Record<string, number> = {};
197
+ const byBucket: Record<string, number> = {};
198
+ for (const b of Object.keys(BUCKETS)) byBucket[b] = 0;
199
+ byBucket['other'] = 0;
200
+
201
+ for (const s of scenarios) {
202
+ const cat = s.category || 'NONE';
203
+ byCategory[cat] = (byCategory[cat] || 0) + 1;
204
+ const bucket = Object.entries(BUCKETS).find(([, cats]) => cats.includes(cat))?.[0] || 'other';
205
+ byBucket[bucket]++;
206
+ }
207
+
208
+ const core = byBucket['business-core'];
209
+ const secondary = byBucket['presentation'] + byBucket['validation-security'];
210
+ const imbalanced = secondary > core * 1.5 && core > 0;
211
+ return {
212
+ byBucket,
213
+ byCategory,
214
+ coreCount: core,
215
+ secondaryCount: secondary,
216
+ imbalanced,
217
+ note: imbalanced
218
+ ? `Secondary viewpoints (presentation+validation/security = ${secondary}) outweigh business-core (${core}) by >1.5x.`
219
+ : 'Balanced.',
220
+ };
221
+ }
222
+
223
+ // ---------- Sensor 4: Duplicate clusters ----------
224
+
225
+ export interface DuplicateResult {
226
+ clusters: { skeleton: string; scenarios: string[]; sameDataLikely: boolean }[];
227
+ exactDuplicateCount: number;
228
+ sameShapeCount: number;
229
+ }
230
+
231
+ export function duplicateClusters(scenarios: ScenarioInfo[]): DuplicateResult {
232
+ const map = new Map<string, ScenarioInfo[]>();
233
+ for (const s of scenarios) {
234
+ const arr = map.get(s.stepSkeleton) || [];
235
+ arr.push(s);
236
+ map.set(s.stepSkeleton, arr);
237
+ }
238
+ const clusters = [...map.entries()]
239
+ .filter(([, arr]) => arr.length > 1)
240
+ .map(([skeleton, arr]) => ({
241
+ skeleton: skeleton.length > 120 ? skeleton.slice(0, 117) + '...' : skeleton,
242
+ scenarios: arr.map((s) => s.name),
243
+ // Same skeleton with data placeholders β†’ likely an EP/data family (intentional), not a true dup.
244
+ sameDataLikely: !skeleton.includes('{}'),
245
+ }));
246
+ return {
247
+ clusters,
248
+ exactDuplicateCount: clusters.filter((c) => c.sameDataLikely).reduce((n, c) => n + (c.scenarios.length - 1), 0),
249
+ sameShapeCount: clusters.reduce((n, c) => n + c.scenarios.length, 0),
250
+ };
251
+ }
252
+
253
+ // ---------- Sensor 5: Traceability ----------
254
+
255
+ export interface TraceResult {
256
+ total: number;
257
+ withVpCode: number;
258
+ mappedToOverview: number;
259
+ withVpCodeRatio: number;
260
+ mappedRatio: number;
261
+ note: string;
262
+ }
263
+
264
+ export function traceability(scenarios: ScenarioInfo[], viewpoints: ViewpointEntry[]): TraceResult {
265
+ const overviewIds = new Set(viewpoints.map((v) => v.id.toUpperCase()));
266
+ const withCode = scenarios.filter((s) => s.vpCode);
267
+ // A scenario maps to overview if its full VP code OR its category-derived id exists in overview.
268
+ const mapped = withCode.filter((s) => overviewIds.has(s.vpCode!) || [...overviewIds].some((id) => id.includes(s.category || '###')));
269
+ return {
270
+ total: scenarios.length,
271
+ withVpCode: withCode.length,
272
+ mappedToOverview: mapped.length,
273
+ withVpCodeRatio: scenarios.length ? withCode.length / scenarios.length : 0,
274
+ mappedRatio: scenarios.length ? mapped.length / scenarios.length : 0,
275
+ note: mapped.length < withCode.length * 0.5
276
+ ? 'Scenarios use ad-hoc VP-<CAT>-NNN codes not linked to viewpoint-overview ids (weak traceability β€” see review Gate 4).'
277
+ : 'Traceable.',
278
+ };
279
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Trace β€” visualise the whole executed test-design process and tell the end-user
3
+ * where to focus human-in-the-loop review.
4
+ *
5
+ * Aggregates the artifacts the harness already produces:
6
+ * - .sungen/ledger/<name>.jsonl β†’ what ran, in what order, how many repair loops, time
7
+ * - .sungen/reports/<name>-audit.json β†’ gate result, sub-scores, weak sensor (bottleneck)
8
+ * - .sungen/reports/<name>-script-check.json β†’ Gherkin↔script drift
9
+ * - features/<name>.feature β†’ @manual scenarios (where humans MUST verify)
10
+ *
11
+ * Outputs: a text process map + a Mermaid flowchart (paste into any viewer) +
12
+ * bottleneck analysis + a ranked "human-loop focus" list.
13
+ */
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+ import { segmentRuns, latestRunEvents, LedgerEvent } from './ledger';
17
+
18
+ interface ManualItem { scenario: string; reason: string }
19
+
20
+ function readJson(p: string): any | null {
21
+ try { return fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, 'utf-8')) : null; } catch { return null; }
22
+ }
23
+
24
+ function readLedger(screen: string): any[] {
25
+ const p = path.join(process.cwd(), '.sungen', 'ledger', `${screen}.jsonl`);
26
+ if (!fs.existsSync(p)) return [];
27
+ return fs.readFileSync(p, 'utf-8').split('\n').filter(Boolean).map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
28
+ }
29
+
30
+ /** Parse @manual scenarios + the explanatory comment line above each. */
31
+ function parseManual(featurePath: string): ManualItem[] {
32
+ if (!fs.existsSync(featurePath)) return [];
33
+ const lines = fs.readFileSync(featurePath, 'utf-8').split('\n');
34
+ const out: ManualItem[] = [];
35
+ for (let i = 0; i < lines.length; i++) {
36
+ const m = lines[i].match(/^\s*Scenario:\s*(.+)$/);
37
+ if (!m) continue;
38
+ // look back for tag line + a reason comment
39
+ let manual = false; let reason = '';
40
+ for (let j = i - 1; j >= 0 && j >= i - 4; j--) {
41
+ const l = lines[j].trim();
42
+ if (/^@/.test(l) && /@manual/.test(l)) manual = true;
43
+ else if (/^#/.test(l) && !reason) reason = l.replace(/^#+\s*/, '');
44
+ else if (l === '' || /^Scenario:/.test(l)) break;
45
+ }
46
+ if (manual) out.push({ scenario: m[1].trim(), reason: reason || '(no reason noted)' });
47
+ }
48
+ return out;
49
+ }
50
+
51
+ export interface TraceReport {
52
+ screen: string;
53
+ runs: number; // total runs on file (process map shows the latest)
54
+ ledger: { step: string; ms: number }[];
55
+ repairRounds: number;
56
+ totalMs: number;
57
+ recordedSteps: string[];
58
+ missingSteps: string[]; // expected phases not recorded in ledger
59
+ audit: { score?: number; gate?: string; weakest?: string; findings?: number; gaps?: string[] } | null;
60
+ drift: string | null;
61
+ manual: ManualItem[];
62
+ bottlenecks: string[];
63
+ humanFocus: string[];
64
+ mermaid: string;
65
+ }
66
+
67
+ const EXPECTED_PHASES = ['discovery', 'viewpoint', 'gherkin', 'audit', 'repair'];
68
+
69
+ export function buildTrace(screenDir: string, screenName: string): TraceReport {
70
+ const allEvents = readLedger(screenName) as LedgerEvent[];
71
+ const runs = segmentRuns(allEvents).length;
72
+ const events = latestRunEvents(allEvents); // scope to the most recent run (P2)
73
+ const ledger = events.map((e) => ({ step: e.step as string, ms: (e.ms as number) || 0 }));
74
+ const repairRounds = new Set(events.filter((e) => /^repair/i.test(e.step)).map((e) => e.step)).size;
75
+ const totalMs = ledger.reduce((n, e) => n + e.ms, 0);
76
+ const recordedSteps = [...new Set(ledger.map((e) => e.step.replace(/:\d+$/, '')))];
77
+ const missingSteps = EXPECTED_PHASES.filter((p) => !recordedSteps.includes(p));
78
+
79
+ const auditRaw = readJson(path.join(process.cwd(), '.sungen', 'reports', `${screenName}-audit.json`));
80
+ let audit: TraceReport['audit'] = null;
81
+ if (auditRaw) {
82
+ const subs: Record<string, number> = {
83
+ coverage: auditRaw.score?.coverage, businessDepth: auditRaw.score?.businessDepth,
84
+ balance: auditRaw.score?.balance, traceability: auditRaw.score?.traceability,
85
+ };
86
+ const weakest = Object.entries(subs).filter(([, v]) => typeof v === 'number').sort((a, b) => a[1] - b[1])[0]?.[0];
87
+ audit = {
88
+ score: auditRaw.score?.overall, gate: auditRaw.gateStatus, weakest,
89
+ findings: (auditRaw.findings || []).length,
90
+ gaps: (auditRaw.gate?.gaps || []).map((g: any) => `${g.theme} (${g.status})`),
91
+ };
92
+ }
93
+
94
+ const scRaw = readJson(path.join(process.cwd(), '.sungen', 'reports', `${screenName}-script-check.json`));
95
+ const drift = scRaw ? scRaw.drift : null;
96
+
97
+ const manual = parseManual(path.join(screenDir, 'features', `${screenName}.feature`));
98
+
99
+ // Bottlenecks
100
+ const bottlenecks: string[] = [];
101
+ if (repairRounds >= 3) bottlenecks.push(`Repair loop hit ${repairRounds} rounds (near/over budget) β†’ first-pass generation (Guide) is the bottleneck.`);
102
+ if (audit?.weakest) bottlenecks.push(`Lowest audit dimension: ${audit.weakest} β†’ focus engine improvement there.`);
103
+ if (audit?.gaps?.length) bottlenecks.push(`Gate gaps: ${audit.gaps.join(', ')}.`);
104
+ if (drift === 'drift') bottlenecks.push('Spec DRIFT vs Gherkin β†’ regenerate (never hand-edit specs).');
105
+ if (missingSteps.length) bottlenecks.push(`Ledger missing phases: ${missingSteps.join(', ')} β†’ instrument \`sungen ledger record\` at each phase for full observability.`);
106
+ if (bottlenecks.length === 0) bottlenecks.push('No bottleneck detected from available signals.');
107
+
108
+ // Human-loop focus (where the user must look)
109
+ const humanFocus: string[] = [];
110
+ if (manual.length) humanFocus.push(`${manual.length} @manual scenario(s) need HUMAN verification (not automated):`);
111
+ for (const m of manual.slice(0, 12)) humanFocus.push(` β€’ ${m.scenario} β€” ${m.reason}`);
112
+ if (audit?.gaps?.length) humanFocus.push(`Critical gaps deferred/uncovered: ${audit.gaps.join(', ')} β€” decide flow vs accept.`);
113
+ if (humanFocus.length === 0) humanFocus.push('No manual/uncovered items β€” automation covers the critical viewpoints.');
114
+
115
+ // Mermaid flowchart of the executed process
116
+ const phaseSeq = ledger.length
117
+ ? ledger.map((e) => e.step)
118
+ : ['(no ledger β€” process not instrumented)'];
119
+ const nodes = phaseSeq.map((s, i) => ` S${i}["${s}${ledger[i] ? ` (${ledger[i].ms}ms)` : ''}"]`).join('\n');
120
+ const edges = phaseSeq.map((_, i) => (i < phaseSeq.length - 1 ? ` S${i} --> S${i + 1}` : '')).filter(Boolean).join('\n');
121
+ const mermaid = [
122
+ 'flowchart TD',
123
+ ' IN["spec / figma / ui / live"] --> DISC[discovery]',
124
+ ' DISC --> VP[viewpoint overview]',
125
+ ' VP --> GEN[generate Gherkin]',
126
+ ' GEN --> AUD{sungen audit gate}',
127
+ repairRounds ? ` AUD -- FAIL --> REP["repair Γ—${repairRounds}"]` : '',
128
+ repairRounds ? ' REP --> AUD' : '',
129
+ ` AUD -- ${audit?.gate === 'PASS' ? 'PASS' : 'still failing'} --> DONE[converge]`,
130
+ ' DONE --> SC{script-check 1:1}',
131
+ ` SC -- ${drift === 'drift' ? 'DRIFT β†’ regenerate' : 'in-sync'} --> OUT[generate β†’ run-test β†’ delivery]`,
132
+ ].filter(Boolean).join('\n');
133
+
134
+ return {
135
+ screen: screenName, runs, ledger, repairRounds, totalMs, recordedSteps, missingSteps,
136
+ audit, drift, manual, bottlenecks, humanFocus, mermaid,
137
+ };
138
+ }
@@ -18,21 +18,25 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
18
18
  ['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
19
19
  ['claude-cmd-add-flow.md', '.claude/commands/sungen/add-flow.md'],
20
20
  ['claude-cmd-create-test.md', '.claude/commands/sungen/create-test.md'],
21
+ ['claude-cmd-design.md', '.claude/commands/sungen/design.md'],
21
22
  ['claude-cmd-run-test.md', '.claude/commands/sungen/run-test.md'],
22
23
  ['claude-cmd-review.md', '.claude/commands/sungen/review.md'],
23
24
  ['claude-cmd-delivery.md', '.claude/commands/sungen/delivery.md'],
24
25
  ['claude-cmd-dashboard.md', '.claude/commands/sungen/dashboard.md'],
25
26
  ['claude-cmd-locale.md', '.claude/commands/sungen/locale.md'],
27
+ ['claude-cmd-feedback.md', '.claude/commands/sungen/feedback.md'],
26
28
 
27
29
  // Commands β€” GitHub Copilot
28
30
  ['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
29
31
  ['copilot-cmd-add-flow.md', '.github/prompts/sungen-add-flow.prompt.md'],
30
32
  ['copilot-cmd-create-test.md', '.github/prompts/sungen-create-test.prompt.md'],
33
+ ['copilot-cmd-design.md', '.github/prompts/sungen-design.prompt.md'],
31
34
  ['copilot-cmd-run-test.md', '.github/prompts/sungen-run-test.prompt.md'],
32
35
  ['copilot-cmd-review.md', '.github/prompts/sungen-review.prompt.md'],
33
36
  ['copilot-cmd-delivery.md', '.github/prompts/sungen-delivery.prompt.md'],
34
37
  ['copilot-cmd-dashboard.md', '.github/prompts/sungen-dashboard.prompt.md'],
35
38
  ['copilot-cmd-locale.md', '.github/prompts/sungen-locale.prompt.md'],
39
+ ['copilot-cmd-feedback.md', '.github/prompts/sungen-feedback.prompt.md'],
36
40
 
37
41
  // Skills β€” Claude Code
38
42
  ['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
@@ -42,6 +46,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
42
46
  ['claude-skill-test-design-techniques.md', '.claude/skills/sungen-test-design-techniques/SKILL.md'],
43
47
  ['claude-skill-selector-fix.md', '.claude/skills/sungen-selector-fix/SKILL.md'],
44
48
  ['claude-skill-tc-review.md', '.claude/skills/sungen-tc-review/SKILL.md'],
49
+ ['claude-skill-harness-audit.md', '.claude/skills/sungen-harness-audit/SKILL.md'],
45
50
  ['claude-skill-viewpoint.md', '.claude/skills/sungen-viewpoint/SKILL.md'],
46
51
  ['claude-skill-viewpoint-group-a-data-entry.md', '.claude/skills/sungen-viewpoint/group-a-data-entry.md'],
47
52
  ['claude-skill-viewpoint-group-b-data-ops.md', '.claude/skills/sungen-viewpoint/group-b-data-ops.md'],
@@ -50,14 +55,19 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
50
55
  ['claude-skill-viewpoint-group-e-identity.md', '.claude/skills/sungen-viewpoint/group-e-identity.md'],
51
56
  ['claude-skill-delivery.md', '.claude/skills/sungen-delivery/SKILL.md'],
52
57
  ['claude-skill-dashboard.md', '.claude/skills/sungen-dashboard/SKILL.md'],
53
- ['claude-skill-capture-figma.md', '.claude/skills/sungen-capture-figma/SKILL.md'],
54
- ['claude-skill-capture-local.md', '.claude/skills/sungen-capture-local/SKILL.md'],
55
- ['claude-skill-capture-live.md', '.claude/skills/sungen-capture-live/SKILL.md'],
56
- ['claude-skill-figma-source.md', '.claude/skills/sungen-figma-source/SKILL.md'],
58
+ // sungen-capture β€” router skill (1 SKILL.md + 4 mode files). Replaces the former
59
+ // capture-figma / capture-live / capture-local / figma-source standalone skills.
60
+ ['claude-skill-capture.md', '.claude/skills/sungen-capture/SKILL.md'],
61
+ ['claude-skill-capture-mode-figma-mcp.md', '.claude/skills/sungen-capture/mode-figma-mcp.md'],
62
+ ['claude-skill-capture-mode-figma-pat.md', '.claude/skills/sungen-capture/mode-figma-pat.md'],
63
+ ['claude-skill-capture-mode-live.md', '.claude/skills/sungen-capture/mode-live.md'],
64
+ ['claude-skill-capture-mode-local.md', '.claude/skills/sungen-capture/mode-local.md'],
57
65
  ['claude-skill-locale.md', '.claude/skills/sungen-locale/SKILL.md'],
58
66
 
59
- // Skills β€” Copilot (prompt-based)
60
- ['copilot-skill-figma-source.md', '.github/prompts/sungen-figma-source.prompt.md'],
67
+ // Agents β€” Claude Code sub-agents (isolated context). Copilot runs these inline.
68
+ ['claude-agent-reviewer.md', '.claude/agents/sungen-reviewer.md'],
69
+ ['claude-agent-discovery.md', '.claude/agents/sungen-discovery.md'],
70
+ ['claude-agent-challenge.md', '.claude/agents/sungen-challenge.md'],
61
71
 
62
72
  // Skills β€” GitHub Copilot
63
73
  ['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
@@ -67,6 +77,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
67
77
  ['github-skill-sungen-test-design-techniques.md', '.github/skills/sungen-test-design-techniques/SKILL.md'],
68
78
  ['github-skill-sungen-selector-fix.md', '.github/skills/sungen-selector-fix/SKILL.md'],
69
79
  ['github-skill-sungen-tc-review.md', '.github/skills/sungen-tc-review/SKILL.md'],
80
+ ['github-skill-sungen-harness-audit.md', '.github/skills/sungen-harness-audit/SKILL.md'],
70
81
  ['github-skill-sungen-viewpoint.md', '.github/skills/sungen-viewpoint/SKILL.md'],
71
82
  ['github-skill-sungen-viewpoint-group-a-data-entry.md', '.github/skills/sungen-viewpoint/group-a-data-entry.md'],
72
83
  ['github-skill-sungen-viewpoint-group-b-data-ops.md', '.github/skills/sungen-viewpoint/group-b-data-ops.md'],
@@ -75,13 +86,29 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
75
86
  ['github-skill-sungen-viewpoint-group-e-identity.md', '.github/skills/sungen-viewpoint/group-e-identity.md'],
76
87
  ['github-skill-sungen-delivery.md', '.github/skills/sungen-delivery/SKILL.md'],
77
88
  ['github-skill-sungen-dashboard.md', '.github/skills/sungen-dashboard/SKILL.md'],
78
- ['github-skill-sungen-capture-figma.md', '.github/skills/sungen-capture-figma/SKILL.md'],
79
- ['github-skill-sungen-capture-local.md', '.github/skills/sungen-capture-local/SKILL.md'],
80
- ['github-skill-sungen-capture-live.md', '.github/skills/sungen-capture-live/SKILL.md'],
81
- ['github-skill-sungen-figma-source.md', '.github/skills/sungen-figma-source/SKILL.md'],
89
+ ['github-skill-sungen-capture.md', '.github/skills/sungen-capture/SKILL.md'],
90
+ ['github-skill-sungen-capture-mode-figma-mcp.md', '.github/skills/sungen-capture/mode-figma-mcp.md'],
91
+ ['github-skill-sungen-capture-mode-figma-pat.md', '.github/skills/sungen-capture/mode-figma-pat.md'],
92
+ ['github-skill-sungen-capture-mode-live.md', '.github/skills/sungen-capture/mode-live.md'],
93
+ ['github-skill-sungen-capture-mode-local.md', '.github/skills/sungen-capture/mode-local.md'],
82
94
  ['github-skill-sungen-locale.md', '.github/skills/sungen-locale/SKILL.md'],
83
95
  ];
84
96
 
97
+ // Skill/asset directories retired in a previous refactor. `sungen update` removes
98
+ // these so already-initialized projects don't keep loading stale skill descriptions.
99
+ // (v3.0: the four capture/figma skills were merged into the `sungen-capture` router.)
100
+ export const STALE_AI_RULES_PATHS: string[] = [
101
+ '.claude/skills/sungen-capture-figma',
102
+ '.claude/skills/sungen-capture-live',
103
+ '.claude/skills/sungen-capture-local',
104
+ '.claude/skills/sungen-figma-source',
105
+ '.github/skills/sungen-capture-figma',
106
+ '.github/skills/sungen-capture-live',
107
+ '.github/skills/sungen-capture-local',
108
+ '.github/skills/sungen-figma-source',
109
+ '.github/prompts/sungen-figma-source.prompt.md',
110
+ ];
111
+
85
112
  export class AIRulesUpdater {
86
113
  private cwd: string;
87
114
  private aiTemplateDir: string;
@@ -133,11 +160,31 @@ export class AIRulesUpdater {
133
160
  }
134
161
  }
135
162
 
163
+ // Remove retired skills/assets so stale descriptions stop loading.
164
+ const removed: string[] = [];
165
+ for (const staleRelPath of STALE_AI_RULES_PATHS) {
166
+ const stalePath = path.join(this.cwd, staleRelPath);
167
+ if (fs.existsSync(stalePath)) {
168
+ if (!dryRun) {
169
+ fs.rmSync(stalePath, { recursive: true, force: true });
170
+ }
171
+ removed.push(staleRelPath);
172
+ }
173
+ }
174
+
136
175
  // Print results
137
176
  if (dryRun) {
138
177
  console.log('πŸ“‹ Dry run β€” no files changed\n');
139
178
  }
140
179
 
180
+ if (removed.length > 0) {
181
+ console.log(`πŸ—‘οΈ Removed retired assets (${removed.length}):`);
182
+ for (const f of removed) {
183
+ console.log(` ${f}`);
184
+ }
185
+ console.log();
186
+ }
187
+
141
188
  if (updated.length > 0) {
142
189
  console.log(`✏️ Updated (${updated.length}):`);
143
190
  for (const f of updated) {
@@ -5,7 +5,7 @@
5
5
  * auto-gen banner, Frame metadata, Screenshots, and a SYNTHESIS marker.
6
6
  * Narrative sections (Purpose, ASCII Layout, Regions, Actions, Form Fields,
7
7
  * Data Columns, Navigation) are appended BELOW the marker by the
8
- * sungen-figma-source skill, which reads the raw cached node JSON.
8
+ * sungen-capture skill (mode figma-pat), which reads the raw cached node JSON.
9
9
  *
10
10
  * Section renderers live in spec-figma-section-renderers.ts.
11
11
  *
@@ -55,7 +55,7 @@ export interface RenderSpecFigmaInput {
55
55
 
56
56
  /**
57
57
  * Render the deterministic envelope for spec_figma.md.
58
- * The LLM synthesis step (sungen-figma-source skill) appends the narrative
58
+ * The LLM synthesis step (sungen-capture skill (mode figma-pat)) appends the narrative
59
59
  * sections AFTER the SYNTHESIS_MARKER comment. Re-synthesis replaces
60
60
  * everything from the marker to EOF.
61
61
  *
@@ -5,7 +5,7 @@
5
5
  * needs: frontmatter, Frame metadata, Screenshots, and the SYNTHESIS marker.
6
6
  * All prose sections (Purpose, ASCII Layout, Regions, Actions, Form Fields,
7
7
  * Data Columns, Navigation) are appended below the marker by the
8
- * sungen-figma-source skill.
8
+ * sungen-capture skill (mode figma-pat).
9
9
  *
10
10
  * Pure β€” no I/O.
11
11
  */
@@ -150,10 +150,12 @@ export class ProjectInitializer {
150
150
  const gitignorePath = path.join(this.cwd, '.gitignore');
151
151
  const sungenEntries = [
152
152
  '# Sungen generated files',
153
- 'specs/.auth/',
153
+ 'specs/.auth/', // session storage state = cookies/tokens (sensitive)
154
154
  'test-results/',
155
155
  'playwright-report/',
156
- '.playwright-mcp/'
156
+ '.playwright-mcp/',
157
+ '.sungen/figma-cache/', // raw Figma node JSON (design IP)
158
+ '.sungen/feedback/', // local QA feedback (may contain product-sensitive notes)
157
159
  ];
158
160
 
159
161
  if (!fs.existsSync(gitignorePath)) {
@@ -162,12 +164,13 @@ export class ProjectInitializer {
162
164
  fs.writeFileSync(gitignorePath, content, 'utf-8');
163
165
  this.createdItems.push('.gitignore');
164
166
  } else {
165
- // Append Sungen entries if not already present
167
+ // Append any Sungen entries that are missing (idempotent β€” covers projects
168
+ // created before new sensitive paths were added).
166
169
  let content = fs.readFileSync(gitignorePath, 'utf-8');
167
- const needsUpdate = !content.includes('specs/.auth/');
168
-
169
- if (needsUpdate) {
170
- content += '\n' + sungenEntries.join('\n') + '\n';
170
+ const missing = sungenEntries.filter((e) => e.startsWith('#') ? false : !content.includes(e));
171
+ if (missing.length) {
172
+ const block = content.includes('# Sungen generated files') ? missing : ['# Sungen generated files', ...missing];
173
+ content += '\n' + block.join('\n') + '\n';
171
174
  fs.writeFileSync(gitignorePath, content, 'utf-8');
172
175
  this.createdItems.push('.gitignore (updated)');
173
176
  } else {
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: sungen-challenge
3
+ description: Exploration/Challenge critic (Loop 2). Does NOT regenerate the suite β€” it ATTACKS the existing one to surface blind spots and propose a few high-value novelty candidates. Advisory only; never edits files, never auto-merges. Run after create-test converges, or on demand via `sungen challenge`.
4
+ tools: Read, Grep, Glob, Bash
5
+ ---
6
+
7
+ You are an **exploration critic** β€” the antidote to "the harness always outputs the same thing". Production mode (create-test β†’ audit gate β†’ repair) is deterministic by design; your job is to find what that determinism reliably **misses**. You do **not** rewrite the suite β€” you challenge it and hand back candidates for the QA to accept or reject.
8
+
9
+ ## First, run the deterministic spine
10
+ Run `sungen challenge --screen <name>` (Bash) and read its report (`.sungen/reports/<name>-challenge.md`). It already gives you: title↔assertion collection gaps, over-covered/shallow areas, and novelty prompts. Build on it β€” don't repeat it.
11
+
12
+ ## Inputs (read)
13
+ - `qa/<screens|flows>/<name>/features/<name>.feature` β€” the suite under attack.
14
+ - `requirements/spec.md` + `test-viewpoint.md` β€” source of truth + intended viewpoints.
15
+ - `.sungen/reports/<name>-audit.json` β€” what the gate already measured.
16
+ - Blind-spot patterns β€” run `sungen blindspot list --prompt` (Bash) and check the suite against each known pattern.
17
+
18
+ ## Three critics
19
+
20
+ 1. **Coverage critic** β€” viewpoints that are missing or covered only shallowly; areas over-covered with low value (e.g. many subscription edge cases while cart correctness is thin). Recommend rebalancing, not just adding.
21
+ 2. **Business-Depth critic** β€” scenarios whose **title claims more than the steps prove** (a set/collection asserted by one element; "correct X" asserted by mere visibility). For each, give the exact deep step to add. Confirm or dismiss the deterministic flags from `sungen challenge`.
22
+ 3. **Novelty critic** β€” 3–5 **non-obvious, valuable** scenarios outside the existing pattern, via risk lenses (double-submit, partial-load, boundary/unusual data, concurrency/back-button, historical incidents). Each must map to a risk or viewpoint and explain why it isn't a duplicate.
23
+
24
+ ## Guardrails (hard)
25
+ - **Read-only.** Never edit the feature or any file. You return findings; the QA/orchestrator decides.
26
+ - **No auto-merge.** Novelty candidates are proposals, capped at **≀ 20%** of the official scenario count.
27
+ - Each candidate is classified **Required / Recommended / Optional** and must not reduce traceability of the official suite.
28
+
29
+ ## Output (Challenge Report β€” Markdown, to the caller)
30
+ ```
31
+ # Challenge Report β€” <name>
32
+ ## Summary
33
+ - Official score: <from audit> Β· depth gaps confirmed: <n> Β· novelty candidates: <n>
34
+ ## Weak / missing viewpoints
35
+ | Viewpoint | Issue | Severity | Recommendation |
36
+ ## Shallow assertions (title > steps)
37
+ | Scenario | Claim in title | Current assertion | Suggested deep step |
38
+ ## Over-covered (low value)
39
+ | Area | Why | Suggested action |
40
+ ## Novelty candidates (≀20%, no auto-merge)
41
+ | Candidate | Related risk/viewpoint | Why valuable | Why not a duplicate | Required/Recommended/Optional |
42
+ ## Blind-spot patterns worth storing
43
+ | Pattern | General rule | Example from this screen |
44
+ ```
45
+
46
+ Keep it tight and actionable. End by reminding the QA: these are **advisory** β€” adopt selectively; promoting a recurring miss to `.sungen/blindspots/` (via `sungen blindspot add`) stops it recurring.