@sun-asterisk/sungen 2.7.0-beta.1 → 3.0.0-beta.72

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 (309) 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/capability.d.ts +3 -0
  13. package/dist/cli/commands/capability.d.ts.map +1 -0
  14. package/dist/cli/commands/capability.js +196 -0
  15. package/dist/cli/commands/capability.js.map +1 -0
  16. package/dist/cli/commands/challenge.d.ts +3 -0
  17. package/dist/cli/commands/challenge.d.ts.map +1 -0
  18. package/dist/cli/commands/challenge.js +102 -0
  19. package/dist/cli/commands/challenge.js.map +1 -0
  20. package/dist/cli/commands/feedback.d.ts +3 -0
  21. package/dist/cli/commands/feedback.d.ts.map +1 -0
  22. package/dist/cli/commands/feedback.js +72 -0
  23. package/dist/cli/commands/feedback.js.map +1 -0
  24. package/dist/cli/commands/flow-check.d.ts +3 -0
  25. package/dist/cli/commands/flow-check.d.ts.map +1 -0
  26. package/dist/cli/commands/flow-check.js +136 -0
  27. package/dist/cli/commands/flow-check.js.map +1 -0
  28. package/dist/cli/commands/generate.d.ts.map +1 -1
  29. package/dist/cli/commands/generate.js +50 -2
  30. package/dist/cli/commands/generate.js.map +1 -1
  31. package/dist/cli/commands/ledger.d.ts +3 -0
  32. package/dist/cli/commands/ledger.d.ts.map +1 -0
  33. package/dist/cli/commands/ledger.js +71 -0
  34. package/dist/cli/commands/ledger.js.map +1 -0
  35. package/dist/cli/commands/manifest.d.ts +3 -0
  36. package/dist/cli/commands/manifest.d.ts.map +1 -0
  37. package/dist/cli/commands/manifest.js +101 -0
  38. package/dist/cli/commands/manifest.js.map +1 -0
  39. package/dist/cli/commands/script-check.d.ts +3 -0
  40. package/dist/cli/commands/script-check.d.ts.map +1 -0
  41. package/dist/cli/commands/script-check.js +97 -0
  42. package/dist/cli/commands/script-check.js.map +1 -0
  43. package/dist/cli/commands/trace.d.ts +3 -0
  44. package/dist/cli/commands/trace.d.ts.map +1 -0
  45. package/dist/cli/commands/trace.js +110 -0
  46. package/dist/cli/commands/trace.js.map +1 -0
  47. package/dist/cli/commands/update.d.ts.map +1 -1
  48. package/dist/cli/commands/update.js +22 -9
  49. package/dist/cli/commands/update.js.map +1 -1
  50. package/dist/cli/index.js +20 -0
  51. package/dist/cli/index.js.map +1 -1
  52. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +1 -0
  53. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  54. package/dist/generators/test-generator/adapters/adapter-registry.d.ts +13 -0
  55. package/dist/generators/test-generator/adapters/adapter-registry.d.ts.map +1 -1
  56. package/dist/generators/test-generator/adapters/adapter-registry.js +73 -1
  57. package/dist/generators/test-generator/adapters/adapter-registry.js.map +1 -1
  58. package/dist/generators/test-generator/adapters/index.d.ts +1 -1
  59. package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
  60. package/dist/generators/test-generator/adapters/index.js +5 -1
  61. package/dist/generators/test-generator/adapters/index.js.map +1 -1
  62. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +1 -0
  63. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +7 -0
  64. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +6 -0
  65. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  66. package/dist/generators/test-generator/code-generator.js +6 -2
  67. package/dist/generators/test-generator/code-generator.js.map +1 -1
  68. package/dist/generators/test-generator/patterns/capture-patterns.d.ts +16 -0
  69. package/dist/generators/test-generator/patterns/capture-patterns.d.ts.map +1 -0
  70. package/dist/generators/test-generator/patterns/capture-patterns.js +54 -0
  71. package/dist/generators/test-generator/patterns/capture-patterns.js.map +1 -0
  72. package/dist/generators/test-generator/patterns/form-patterns.d.ts.map +1 -1
  73. package/dist/generators/test-generator/patterns/form-patterns.js +3 -1
  74. package/dist/generators/test-generator/patterns/form-patterns.js.map +1 -1
  75. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  76. package/dist/generators/test-generator/patterns/index.js +2 -0
  77. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  78. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  79. package/dist/generators/test-generator/step-mapper.js +1 -0
  80. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  81. package/dist/generators/test-generator/utils/data-resolver.d.ts +5 -0
  82. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  83. package/dist/generators/test-generator/utils/data-resolver.js +17 -0
  84. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  85. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -1
  86. package/dist/generators/test-generator/utils/runtime-data-transformer.js +4 -0
  87. package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -1
  88. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  89. package/dist/generators/test-generator/utils/selector-resolver.js +12 -6
  90. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  91. package/dist/harness/audit.d.ts +24 -0
  92. package/dist/harness/audit.d.ts.map +1 -0
  93. package/dist/harness/audit.js +115 -0
  94. package/dist/harness/audit.js.map +1 -0
  95. package/dist/harness/blindspot.d.ts +15 -0
  96. package/dist/harness/blindspot.d.ts.map +1 -0
  97. package/dist/harness/blindspot.js +85 -0
  98. package/dist/harness/blindspot.js.map +1 -0
  99. package/dist/harness/capability-plan.d.ts +49 -0
  100. package/dist/harness/capability-plan.d.ts.map +1 -0
  101. package/dist/harness/capability-plan.js +215 -0
  102. package/dist/harness/capability-plan.js.map +1 -0
  103. package/dist/harness/capability.d.ts +23 -0
  104. package/dist/harness/capability.d.ts.map +1 -0
  105. package/dist/harness/capability.js +98 -0
  106. package/dist/harness/capability.js.map +1 -0
  107. package/dist/harness/catalog/drivers.yaml +57 -0
  108. package/dist/harness/catalog/universal-viewpoints.yaml +114 -0
  109. package/dist/harness/challenge.d.ts +21 -0
  110. package/dist/harness/challenge.d.ts.map +1 -0
  111. package/dist/harness/challenge.js +151 -0
  112. package/dist/harness/challenge.js.map +1 -0
  113. package/dist/harness/feedback.d.ts +29 -0
  114. package/dist/harness/feedback.d.ts.map +1 -0
  115. package/dist/harness/feedback.js +106 -0
  116. package/dist/harness/feedback.js.map +1 -0
  117. package/dist/harness/flow-check.d.ts +23 -0
  118. package/dist/harness/flow-check.d.ts.map +1 -0
  119. package/dist/harness/flow-check.js +132 -0
  120. package/dist/harness/flow-check.js.map +1 -0
  121. package/dist/harness/flow-plan.d.ts +23 -0
  122. package/dist/harness/flow-plan.d.ts.map +1 -0
  123. package/dist/harness/flow-plan.js +166 -0
  124. package/dist/harness/flow-plan.js.map +1 -0
  125. package/dist/harness/intent.d.ts +11 -0
  126. package/dist/harness/intent.d.ts.map +1 -0
  127. package/dist/harness/intent.js +86 -0
  128. package/dist/harness/intent.js.map +1 -0
  129. package/dist/harness/ledger.d.ts +42 -0
  130. package/dist/harness/ledger.d.ts.map +1 -0
  131. package/dist/harness/ledger.js +171 -0
  132. package/dist/harness/ledger.js.map +1 -0
  133. package/dist/harness/manifest.d.ts +42 -0
  134. package/dist/harness/manifest.d.ts.map +1 -0
  135. package/dist/harness/manifest.js +209 -0
  136. package/dist/harness/manifest.js.map +1 -0
  137. package/dist/harness/parse.d.ts +22 -0
  138. package/dist/harness/parse.d.ts.map +1 -0
  139. package/dist/harness/parse.js +163 -0
  140. package/dist/harness/parse.js.map +1 -0
  141. package/dist/harness/script-check.d.ts +39 -0
  142. package/dist/harness/script-check.d.ts.map +1 -0
  143. package/dist/harness/script-check.js +251 -0
  144. package/dist/harness/script-check.js.map +1 -0
  145. package/dist/harness/secret-scan.d.ts +8 -0
  146. package/dist/harness/secret-scan.d.ts.map +1 -0
  147. package/dist/harness/secret-scan.js +88 -0
  148. package/dist/harness/secret-scan.js.map +1 -0
  149. package/dist/harness/sensors.d.ts +88 -0
  150. package/dist/harness/sensors.d.ts.map +1 -0
  151. package/dist/harness/sensors.js +232 -0
  152. package/dist/harness/sensors.js.map +1 -0
  153. package/dist/harness/trace.d.ts +31 -0
  154. package/dist/harness/trace.d.ts.map +1 -0
  155. package/dist/harness/trace.js +173 -0
  156. package/dist/harness/trace.js.map +1 -0
  157. package/dist/orchestrator/ai-rules-updater.d.ts +1 -0
  158. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  159. package/dist/orchestrator/ai-rules-updater.js +55 -11
  160. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  161. package/dist/orchestrator/figma/spec-figma-renderer.d.ts +2 -2
  162. package/dist/orchestrator/figma/spec-figma-renderer.js +2 -2
  163. package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts +1 -1
  164. package/dist/orchestrator/figma/spec-figma-section-renderers.js +1 -1
  165. package/dist/orchestrator/project-initializer.d.ts +5 -0
  166. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  167. package/dist/orchestrator/project-initializer.js +30 -6
  168. package/dist/orchestrator/project-initializer.js.map +1 -1
  169. package/dist/orchestrator/templates/ai-instructions/claude-agent-challenge.md +46 -0
  170. package/dist/orchestrator/templates/ai-instructions/claude-agent-discovery.md +32 -0
  171. package/dist/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +37 -0
  172. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +3 -3
  173. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
  174. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +36 -12
  175. package/dist/orchestrator/templates/ai-instructions/claude-cmd-design.md +12 -0
  176. package/dist/orchestrator/templates/ai-instructions/claude-cmd-feedback.md +36 -0
  177. package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +27 -30
  178. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +4 -1
  179. package/dist/orchestrator/templates/ai-instructions/claude-config.md +1 -4
  180. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-mcp.md +82 -0
  181. package/dist/orchestrator/templates/ai-instructions/{github-skill-sungen-figma-source.md → claude-skill-capture-mode-figma-pat.md} +14 -48
  182. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-live.md +60 -0
  183. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-local.md +38 -0
  184. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture.md +35 -0
  185. package/dist/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +84 -0
  186. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +40 -1
  187. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +3 -3
  188. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
  189. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +18 -10
  190. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-design.md +13 -0
  191. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +24 -0
  192. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +20 -30
  193. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +2 -1
  194. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +1 -4
  195. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md +82 -0
  196. 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
  197. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md +60 -0
  198. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md +38 -0
  199. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md +35 -0
  200. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +84 -0
  201. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +1 -1
  202. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -1
  203. package/dist/orchestrator/templates/specs-test-data.ts +20 -6
  204. package/dist/tools/figma/figma-auth.d.ts +5 -2
  205. package/dist/tools/figma/figma-auth.d.ts.map +1 -1
  206. package/dist/tools/figma/figma-auth.js +19 -9
  207. package/dist/tools/figma/figma-auth.js.map +1 -1
  208. package/docs/orchestration-spec.md +267 -0
  209. package/package.json +12 -7
  210. package/src/cli/commands/add.ts +3 -3
  211. package/src/cli/commands/audit.ts +92 -0
  212. package/src/cli/commands/blindspot.ts +48 -0
  213. package/src/cli/commands/capability.ts +160 -0
  214. package/src/cli/commands/challenge.ts +55 -0
  215. package/src/cli/commands/feedback.ts +65 -0
  216. package/src/cli/commands/flow-check.ts +97 -0
  217. package/src/cli/commands/generate.ts +47 -2
  218. package/src/cli/commands/ledger.ts +61 -0
  219. package/src/cli/commands/manifest.ts +55 -0
  220. package/src/cli/commands/script-check.ts +50 -0
  221. package/src/cli/commands/trace.ts +60 -0
  222. package/src/cli/commands/update.ts +30 -10
  223. package/src/cli/index.ts +20 -0
  224. package/src/generators/test-generator/adapters/adapter-interface.ts +1 -0
  225. package/src/generators/test-generator/adapters/adapter-registry.ts +37 -0
  226. package/src/generators/test-generator/adapters/index.ts +4 -1
  227. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +1 -0
  228. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +7 -0
  229. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +6 -0
  230. package/src/generators/test-generator/code-generator.ts +6 -2
  231. package/src/generators/test-generator/patterns/capture-patterns.ts +59 -0
  232. package/src/generators/test-generator/patterns/form-patterns.ts +3 -1
  233. package/src/generators/test-generator/patterns/index.ts +2 -0
  234. package/src/generators/test-generator/step-mapper.ts +1 -0
  235. package/src/generators/test-generator/utils/data-resolver.ts +20 -0
  236. package/src/generators/test-generator/utils/runtime-data-transformer.ts +8 -0
  237. package/src/generators/test-generator/utils/selector-resolver.ts +13 -6
  238. package/src/harness/audit.ts +112 -0
  239. package/src/harness/blindspot.ts +51 -0
  240. package/src/harness/capability-plan.ts +180 -0
  241. package/src/harness/capability.ts +75 -0
  242. package/src/harness/catalog/drivers.yaml +57 -0
  243. package/src/harness/catalog/universal-viewpoints.yaml +114 -0
  244. package/src/harness/challenge.ts +131 -0
  245. package/src/harness/feedback.ts +84 -0
  246. package/src/harness/flow-check.ts +99 -0
  247. package/src/harness/flow-plan.ts +135 -0
  248. package/src/harness/intent.ts +58 -0
  249. package/src/harness/ledger.ts +155 -0
  250. package/src/harness/manifest.ts +173 -0
  251. package/src/harness/parse.ts +145 -0
  252. package/src/harness/script-check.ts +222 -0
  253. package/src/harness/secret-scan.ts +51 -0
  254. package/src/harness/sensors.ts +279 -0
  255. package/src/harness/trace.ts +138 -0
  256. package/src/orchestrator/ai-rules-updater.ts +57 -10
  257. package/src/orchestrator/figma/spec-figma-renderer.ts +2 -2
  258. package/src/orchestrator/figma/spec-figma-section-renderers.ts +1 -1
  259. package/src/orchestrator/project-initializer.ts +33 -7
  260. package/src/orchestrator/templates/ai-instructions/claude-agent-challenge.md +46 -0
  261. package/src/orchestrator/templates/ai-instructions/claude-agent-discovery.md +32 -0
  262. package/src/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +37 -0
  263. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +3 -3
  264. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
  265. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +36 -12
  266. package/src/orchestrator/templates/ai-instructions/claude-cmd-design.md +12 -0
  267. package/src/orchestrator/templates/ai-instructions/claude-cmd-feedback.md +36 -0
  268. package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +27 -30
  269. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +4 -1
  270. package/src/orchestrator/templates/ai-instructions/claude-config.md +1 -4
  271. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-mcp.md +82 -0
  272. 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
  273. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-live.md +60 -0
  274. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-local.md +38 -0
  275. package/src/orchestrator/templates/ai-instructions/claude-skill-capture.md +35 -0
  276. package/src/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +84 -0
  277. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +40 -1
  278. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +3 -3
  279. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
  280. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +18 -10
  281. package/src/orchestrator/templates/ai-instructions/copilot-cmd-design.md +13 -0
  282. package/src/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +24 -0
  283. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +20 -30
  284. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +2 -1
  285. package/src/orchestrator/templates/ai-instructions/copilot-config.md +1 -4
  286. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md +82 -0
  287. 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
  288. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md +60 -0
  289. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md +38 -0
  290. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md +35 -0
  291. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +84 -0
  292. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +1 -1
  293. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -1
  294. package/src/orchestrator/templates/specs-test-data.ts +20 -6
  295. package/src/tools/figma/figma-auth.ts +20 -9
  296. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +0 -142
  297. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +0 -112
  298. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +0 -73
  299. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +0 -142
  300. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +0 -112
  301. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +0 -73
  302. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +0 -142
  303. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +0 -112
  304. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +0 -73
  305. package/src/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +0 -151
  306. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +0 -142
  307. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +0 -112
  308. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +0 -73
  309. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +0 -151
@@ -0,0 +1,114 @@
1
+ # Seed Universal Viewpoint Catalog (bundled, local — NOT a server).
2
+ # Role: REFERENCE for the Harness Coverage Gate (the "answer key" sensors check
3
+ # against). The AI still GENERATES the viewpoint-overview; this catalog only
4
+ # verifies that critical themes for a page-type were not missed.
5
+ #
6
+ # Each page-type lists must-cover themes. A theme is "covered" when the project's
7
+ # viewpoint-overview (or generated scenarios) contains one of its keywords.
8
+ # See docs/orchestration-spec.md §5.2 and reports/sungen_refactor_spec.md §9.
9
+ #
10
+ # `depth:` (optional, harness-roadmap P1) marks a theme as DATA-correctness:
11
+ # requires: data-assertion → scenarios on this theme must assert DATA (not just
12
+ # visibility) to count as "deep". Drives the depth gate.
13
+ # cross_screen: true → genuine depth needs another screen → use a flow
14
+ # (P5 `remember` / `see all … contain`); on one screen
15
+ # it should be @manual + deferred-to-flow, not shallow.
16
+ # keywords: → PRECISE data-noun keywords for depth matching (kept
17
+ # separate from coverage keywords to avoid matching
18
+ # navigation scenarios like "API list page").
19
+ # template: → the deep step the generator should emit by default.
20
+ # Themes with no `depth:` are visibility/navigation — landing/seeing IS the assertion.
21
+
22
+ page_types:
23
+ ecommerce-list:
24
+ detect_keywords: [cart, product, checkout, catalog, brand, category, "add to cart"]
25
+ must_cover:
26
+ - theme: list-data
27
+ keywords: [list, displayed, card, "product card", grid]
28
+ depth:
29
+ requires: data-assertion
30
+ cross_screen: false
31
+ keywords: ["product price", "product card", "every card", "all product", "product name", "displays a price", "card displays"]
32
+ template: "User see all [Product Price] contain {{currency}} (and name/image per card)"
33
+ - theme: product-detail-consistency
34
+ keywords: [consistent, consistency, match, "same product", "correct product"]
35
+ depth:
36
+ requires: data-assertion
37
+ cross_screen: true
38
+ keywords: ["product detail", "detail page", "same product name", "matching name", "consistency", "same price"]
39
+ template: "User remember [Product Name] text as {{v}} … User see [Detail Product Name] header with {{v}}"
40
+ - theme: cart-correctness
41
+ keywords: ["cart contains", quantity, "price", subtotal, "in cart", "cart content", "cart product"]
42
+ depth:
43
+ requires: data-assertion
44
+ cross_screen: true
45
+ keywords: ["cart product", "cart contains", "in cart", "cart item", "appears in the cart", "cart line", "subtotal", "quantity"]
46
+ template: "User remember [Product Name] text as {{v}} … User see all [Cart Product Name] contain {{v}}"
47
+ - theme: category-filter-correctness
48
+ keywords: ["belong to the selected category", "category result", "matching the selected category", "products belong to", "only products that belong to the selected category"]
49
+ depth:
50
+ requires: data-assertion
51
+ cross_screen: true
52
+ keywords: ["belong to the selected category", "category result", "products that belong", "matching the selected category"]
53
+ template: "User see all [Result Product Name] contain {{selected_category}}"
54
+ - theme: brand-filter-correctness
55
+ keywords: ["belong to the selected brand", "brand result", "only products ... brand", "products that belong to the selected brand", "all displayed products belong to"]
56
+ depth:
57
+ requires: data-assertion
58
+ cross_screen: true
59
+ keywords: ["belong to the selected brand", "brand result", "products that belong to the selected brand"]
60
+ template: "User see all [Result Product Name] contain {{selected_brand}}"
61
+ - theme: add-to-cart
62
+ keywords: ["add to cart", added, "added to cart"]
63
+ depth:
64
+ requires: data-assertion
65
+ cross_screen: false
66
+ keywords: ["add to cart", "added to cart", "added confirmation", "added message"]
67
+ template: "User see [Added Message] text contains {{added_message}} (not just the modal)"
68
+
69
+ form:
70
+ detect_keywords: [form, submit, field, input, validation]
71
+ must_cover:
72
+ - theme: required-validation
73
+ keywords: [required, empty, "must be", validation]
74
+ depth:
75
+ requires: data-assertion
76
+ cross_screen: false
77
+ keywords: ["required", "must be", "error message", "validation message"]
78
+ template: "User see [Field Error] message with {{error_text}}"
79
+ - theme: format-boundary
80
+ keywords: [format, invalid, boundary, length, range]
81
+ depth:
82
+ requires: data-assertion
83
+ cross_screen: false
84
+ keywords: ["invalid format", "boundary", "max length", "min length", "out of range"]
85
+ template: "User see [Field Error] message with {{error_text}}"
86
+ - theme: submit-success
87
+ keywords: [submit, success, saved, created]
88
+ depth:
89
+ requires: data-assertion
90
+ cross_screen: false
91
+ keywords: ["success message", "saved", "created", "confirmation"]
92
+ template: "User see [Success] message with {{success_text}}"
93
+
94
+ auth:
95
+ detect_keywords: [login, logout, password, signin, "sign in", credential]
96
+ must_cover:
97
+ - theme: valid-login
98
+ keywords: ["valid", login, success]
99
+ - theme: invalid-credential
100
+ keywords: ["invalid", "wrong password", error, incorrect]
101
+ depth:
102
+ requires: data-assertion
103
+ cross_screen: false
104
+ keywords: ["wrong password", "incorrect", "invalid credential", "error message"]
105
+ template: "User see [Login Error] message with {{error_text}}"
106
+ - theme: access-control
107
+ keywords: ["unauthorized", "redirect", "not logged in", permission]
108
+
109
+ # Universal themes worth checking on ANY page-type (low-weight reminders).
110
+ universal:
111
+ - theme: error-empty-state
112
+ keywords: ["empty", "no data", "no result", "failed", "error state"]
113
+ - theme: accessibility
114
+ keywords: ["keyboard", "tab order", "accessible", "aria", "focus"]
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Challenge / Exploration Harness (harness-roadmap P4) — Loop 2.
3
+ *
4
+ * Production mode (Loop 1: create-test → audit gate → repair) is deterministic by
5
+ * design: same spec + viewpoint → same official suite. That is the *feature* for
6
+ * delivery/CI, but it can feel like "a machine that always outputs the same thing".
7
+ *
8
+ * The Challenge Harness is the antidote: it does NOT regenerate the suite — it
9
+ * ATTACKS the existing one to surface what production missed. It is advisory
10
+ * (never auto-merges) and read-only.
11
+ *
12
+ * This module is the DETERMINISTIC spine — three structural critics:
13
+ * 1. Coverage — over-covered (low-value) areas + shallow gate themes.
14
+ * 2. Depth — titles that claim a collection but assert a single element.
15
+ * 3. Novelty — risk-based prompts the AI `sungen-challenge` agent expands into
16
+ * concrete novelty candidates (semantic — not deterministic here).
17
+ *
18
+ * The AI agent layer adds the semantic + novelty judgement on top of this spine.
19
+ */
20
+ import * as path from 'path';
21
+ import { loadScenarios, ScenarioInfo } from './parse';
22
+ import { runAudit } from './audit';
23
+
24
+ export interface ChallengeFinding {
25
+ scenario?: string;
26
+ issue: string;
27
+ suggestion: string;
28
+ }
29
+
30
+ export interface ChallengeReport {
31
+ screen: string;
32
+ // Coverage critic
33
+ overCovered: { bucket: string; count: number; note: string }[];
34
+ shallowThemes: string[];
35
+ // Depth critic
36
+ collectionClaimSingular: ChallengeFinding[];
37
+ // Novelty critic (deterministic prompts → AI agent fills candidates)
38
+ noveltyPrompts: string[];
39
+ // Roll-up
40
+ explorationReadiness: string[];
41
+ }
42
+
43
+ // A collection claim = a PLURAL set noun (or an explicit quantifier) used with a
44
+ // DISPLAY verb. Plural-only avoids false-positives on single-item actions like
45
+ // "Adding A product ... shows the dialog" (which correctly asserts one item).
46
+ const PLURAL_NOUN = /\b(cards|items|products|rows|results|prices|entries|records)\b/i;
47
+ const QUANTIFIER = /\b(all|every|each)\b/i;
48
+ const DISPLAY_VERB = /\b(displays?|shows?|lists?|grid|contains?)\b/i;
49
+
50
+ /** Risk lenses the Novelty critic prompts the AI to explore (beyond the catalog). */
51
+ const NOVELTY_LENSES = [
52
+ 'double-submit / rapid repeat of the primary action (duplicate side-effects?)',
53
+ 'state after partial / slow load (assert against a not-yet-ready page)',
54
+ 'boundary & unusual data (very long text, 0 / max quantity, special chars)',
55
+ 'concurrency / back-button / refresh mid-flow',
56
+ 'historical-incident mindset — what has broken on similar screens before?',
57
+ ];
58
+
59
+ export function buildChallenge(screenDir: string, screenName: string): ChallengeReport {
60
+ const featurePath = path.join(screenDir, 'features', `${screenName}.feature`);
61
+ const scenarios: ScenarioInfo[] = loadScenarios(featurePath);
62
+ const audit = runAudit(screenDir, screenName);
63
+
64
+ // 1. Coverage critic — over-covered buckets (secondary >> business-core) + shallow themes.
65
+ const buckets = audit.balance.byBucket;
66
+ const core = buckets['business-core'] || 0;
67
+ const overCovered: ChallengeReport['overCovered'] = [];
68
+ for (const [bucket, count] of Object.entries(buckets)) {
69
+ if (['business-core', 'other'].includes(bucket)) continue;
70
+ if (core > 0 && count > core * 1.5) {
71
+ overCovered.push({ bucket, count, note: `${count} scenarios vs ${core} business-core — likely low-value expansion; trim toward correctness.` });
72
+ }
73
+ }
74
+ const shallowThemes = audit.gate.gaps.filter((g) => g.status === 'shallow').map((g) => g.theme);
75
+
76
+ // 2. Depth critic — title claims a collection but the assertion is singular (no "see all").
77
+ const collectionClaimSingular: ChallengeFinding[] = [];
78
+ for (const s of scenarios) {
79
+ if (s.manual || s.category === 'NAV') continue;
80
+ const claimsCollection = (PLURAL_NOUN.test(s.name) || QUANTIFIER.test(s.name)) && DISPLAY_VERB.test(s.name);
81
+ const assertsAll = /\bsee all\b/.test(s.haystack);
82
+ if (claimsCollection && !assertsAll) {
83
+ collectionClaimSingular.push({
84
+ scenario: s.name,
85
+ issue: 'Title implies a set (cards/items/all) but the assertion targets a single element.',
86
+ suggestion: 'Prove EVERY member: `Then User see all [<Card/Row>] contain {{...}}` instead of a single `see [X]`.',
87
+ });
88
+ }
89
+ }
90
+
91
+ // 3. Novelty critic — deterministic prompts; the AI agent expands these into candidates.
92
+ const noveltyPrompts = NOVELTY_LENSES.map((l) => `Find 1 non-obvious, valuable scenario via: ${l}`);
93
+
94
+ // Roll-up — exploration readiness signals (not a fake score).
95
+ const explorationReadiness: string[] = [];
96
+ if (collectionClaimSingular.length) explorationReadiness.push(`${collectionClaimSingular.length} title↔assertion gap(s) — deterministic depth critic flagged these; an AI Business-Depth critic should confirm + fix.`);
97
+ if (overCovered.length) explorationReadiness.push(`${overCovered.length} possibly over-covered area(s) — rebalance toward correctness.`);
98
+ if (shallowThemes.length) explorationReadiness.push(`Shallow themes: ${shallowThemes.join(', ')}.`);
99
+ explorationReadiness.push('Novelty candidates are NOT generated deterministically — run the `sungen-challenge` agent (Claude) or its inline criteria (Copilot) to propose them, then QA accept/reject (≤20% of official, no auto-merge).');
100
+
101
+ return { screen: screenName, overCovered, shallowThemes, collectionClaimSingular, noveltyPrompts, explorationReadiness };
102
+ }
103
+
104
+ /** Render the Challenge Report as Markdown (advisory — not part of the official suite). */
105
+ export function renderChallengeMarkdown(r: ChallengeReport): string {
106
+ const lines: string[] = [];
107
+ lines.push(`# Challenge Report — ${r.screen}`, '');
108
+ lines.push('> Advisory (Loop 2 / exploration mode). Does NOT change the official suite — it attacks it to surface blind spots. QA decides what to adopt.', '');
109
+
110
+ lines.push('## Depth — title claims a collection but asserts a single element');
111
+ if (r.collectionClaimSingular.length) {
112
+ lines.push('| Scenario | Issue | Suggested |', '|---|---|---|');
113
+ for (const f of r.collectionClaimSingular) lines.push(`| ${f.scenario} | ${f.issue} | ${f.suggestion} |`);
114
+ } else lines.push('_none_');
115
+ lines.push('');
116
+
117
+ lines.push('## Coverage — possibly over-covered / shallow');
118
+ if (r.overCovered.length) for (const o of r.overCovered) lines.push(`- **${o.bucket}** — ${o.note}`);
119
+ if (r.shallowThemes.length) lines.push(`- Shallow themes: ${r.shallowThemes.join(', ')}`);
120
+ if (!r.overCovered.length && !r.shallowThemes.length) lines.push('_balanced_');
121
+ lines.push('');
122
+
123
+ lines.push('## Novelty — prompts for the AI critic (expand into candidates, ≤20% of official, no auto-merge)');
124
+ for (const p of r.noveltyPrompts) lines.push(`- ${p}`);
125
+ lines.push('');
126
+
127
+ lines.push('## Exploration readiness');
128
+ for (const e of r.explorationReadiness) lines.push(`- ${e}`);
129
+ lines.push('');
130
+ return lines.join('\n');
131
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Feedback — local-first capture of QA feedback. The most valuable use of
3
+ * feedback is closing the learning loop WITHIN a project (feed reuse/regenerate
4
+ * + improve the Guide), which needs no server. A future opt-in `feedback sync`
5
+ * can push anonymized metadata to a central API (Nấc 2) — not implemented here.
6
+ *
7
+ * Two purposes are kept distinct:
8
+ * - test-design: a viewpoint/scenario is wrong / missing / duplicate → knowledge
9
+ * - product: Sungen itself did X wrong → telemetry/issue
10
+ *
11
+ * Storage: .sungen/feedback/feedback.jsonl (append-only)
12
+ */
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+
16
+ export type FeedbackType = 'test-design' | 'product' | 'other';
17
+ export type FeedbackDecision = 'accept' | 'reject' | 'edit' | 'add' | 'none';
18
+
19
+ export interface FeedbackEntry {
20
+ ts: string;
21
+ type: FeedbackType;
22
+ screen?: string;
23
+ target?: string; // viewpoint id / scenario / command / artifact the feedback is about
24
+ decision?: FeedbackDecision;
25
+ message: string;
26
+ reason?: string;
27
+ source: string; // who (default: qa)
28
+ auditScore?: number; // snapshot of current audit score if available
29
+ }
30
+
31
+ function feedbackPath(): string {
32
+ return path.join(process.cwd(), '.sungen', 'feedback', 'feedback.jsonl');
33
+ }
34
+
35
+ function currentAuditScore(screen?: string): number | undefined {
36
+ if (!screen) return undefined;
37
+ const p = path.join(process.cwd(), '.sungen', 'reports', `${screen}-audit.json`);
38
+ if (!fs.existsSync(p)) return undefined;
39
+ try { return JSON.parse(fs.readFileSync(p, 'utf-8'))?.score?.overall; } catch { return undefined; }
40
+ }
41
+
42
+ export function recordFeedback(entry: Omit<FeedbackEntry, 'ts' | 'source' | 'auditScore'> & { ts?: string; source?: string }): string {
43
+ const p = feedbackPath();
44
+ fs.mkdirSync(path.dirname(p), { recursive: true });
45
+ const full: FeedbackEntry = {
46
+ ts: entry.ts ?? new Date().toISOString(),
47
+ type: entry.type,
48
+ screen: entry.screen,
49
+ target: entry.target,
50
+ decision: entry.decision ?? 'none',
51
+ message: entry.message,
52
+ reason: entry.reason,
53
+ source: entry.source ?? 'qa',
54
+ auditScore: currentAuditScore(entry.screen),
55
+ };
56
+ fs.appendFileSync(p, JSON.stringify(full) + '\n', 'utf-8');
57
+ return p;
58
+ }
59
+
60
+ export function readFeedback(): FeedbackEntry[] {
61
+ const p = feedbackPath();
62
+ if (!fs.existsSync(p)) return [];
63
+ return fs.readFileSync(p, 'utf-8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
64
+ }
65
+
66
+ export interface FeedbackSummary {
67
+ total: number;
68
+ byType: Record<string, number>;
69
+ byDecision: Record<string, number>;
70
+ entries: FeedbackEntry[];
71
+ }
72
+
73
+ export function summarize(filter?: { screen?: string; type?: FeedbackType }): FeedbackSummary {
74
+ let entries = readFeedback();
75
+ if (filter?.screen) entries = entries.filter((e) => e.screen === filter.screen);
76
+ if (filter?.type) entries = entries.filter((e) => e.type === filter.type);
77
+ const byType: Record<string, number> = {};
78
+ const byDecision: Record<string, number> = {};
79
+ for (const e of entries) {
80
+ byType[e.type] = (byType[e.type] || 0) + 1;
81
+ byDecision[e.decision || 'none'] = (byDecision[e.decision || 'none'] || 0) + 1;
82
+ }
83
+ return { total: entries.length, byType, byDecision, entries };
84
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Flow first-class checks (flow_firstclass_spec).
3
+ *
4
+ * A. Deferral integrity — every screen `@manual` "deferred to a flow (X -> Y)" must
5
+ * be backed by a real flow scenario that covers Y (deeply). Otherwise the
6
+ * cross-screen (XS) manual is a hidden gap, not "flow-covered".
7
+ * B. Run-test contract — surface the fragile cross-screen patterns (same-card
8
+ * identity scope, shared-state isolation under @parallel) as explicit checks.
9
+ *
10
+ * Deterministic + read-only. No test execution, no selector resolution.
11
+ */
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import { loadScenarios } from './parse';
15
+ import { parseScenarios } from './capability-plan';
16
+
17
+ const KNOWN_TARGETS = ['cart', 'category', 'brand', 'detail', 'checkout', 'wishlist', 'order', 'payment', 'login'];
18
+
19
+ export interface Deferral { screen: string; scenario: string; hint: string; targets: string[]; verdict: 'covered' | 'shallow' | 'missing'; via?: string }
20
+ export interface Contract { flow: string; kind: 'SAME-CARD' | 'ISOLATION'; scenario?: string; message: string }
21
+ export interface FlowCheckReport {
22
+ screens: string[]; flows: string[];
23
+ deferrals: Deferral[];
24
+ contracts: Contract[];
25
+ missing: number;
26
+ }
27
+
28
+ function listDirs(p: string): string[] {
29
+ return fs.existsSync(p) ? fs.readdirSync(p, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name) : [];
30
+ }
31
+ function featurePath(base: string, kind: 'screens' | 'flows', name: string): string {
32
+ return path.join(base, 'qa', kind, name, 'features', `${name}.feature`);
33
+ }
34
+ function featureTags(fp: string): string[] {
35
+ if (!fs.existsSync(fp)) return [];
36
+ for (const line of fs.readFileSync(fp, 'utf-8').split('\n')) {
37
+ const t = line.trim();
38
+ if (t.startsWith('Feature:')) break;
39
+ if (t.startsWith('@')) return t.split(/\s+/).filter((x) => x.startsWith('@'));
40
+ }
41
+ return [];
42
+ }
43
+ function targetsFromHint(hint: string): string[] {
44
+ const h = hint.toLowerCase();
45
+ const hits = KNOWN_TARGETS.filter((k) => h.includes(k));
46
+ if (hits.length) return hits;
47
+ // fallback: significant words after "->"
48
+ const after = h.split('->').pop() || h;
49
+ return after.split(/[^a-z]+/).filter((w) => w.length > 3 && !['home', 'page', 'flow', 'products', 'product', 'result'].includes(w));
50
+ }
51
+
52
+ export function buildFlowCheck(cwd: string, onlyFlow?: string): FlowCheckReport {
53
+ const screens = listDirs(path.join(cwd, 'qa', 'screens'));
54
+ const flows = (onlyFlow ? [onlyFlow] : listDirs(path.join(cwd, 'qa', 'flows')));
55
+
56
+ // Index flow scenarios (name + haystack + depth).
57
+ const flowScenarios: { flow: string; name: string; haystack: string; deep: boolean }[] = [];
58
+ for (const f of listDirs(path.join(cwd, 'qa', 'flows'))) {
59
+ for (const s of loadScenarios(featurePath(cwd, 'flows', f))) {
60
+ flowScenarios.push({ flow: f, name: s.name, haystack: s.haystack, deep: s.hasDataAssertion });
61
+ }
62
+ }
63
+
64
+ // A. Deferral integrity (screens).
65
+ const deferrals: Deferral[] = [];
66
+ for (const sc of screens) {
67
+ for (const s of parseScenarios(featurePath(cwd, 'screens', sc))) {
68
+ if (!s.manual || !/deferred to a flow/i.test(s.reason)) continue;
69
+ const targets = targetsFromHint(s.reason);
70
+ const matches = flowScenarios.filter((fs2) => targets.some((t) => fs2.haystack.includes(t)));
71
+ let verdict: Deferral['verdict'] = 'missing';
72
+ let via: string | undefined;
73
+ if (matches.some((m) => m.deep)) { verdict = 'covered'; via = matches.find((m) => m.deep)!.flow; }
74
+ else if (matches.length) { verdict = 'shallow'; via = matches[0].flow; }
75
+ deferrals.push({ screen: sc, scenario: s.name, hint: s.reason, targets, verdict, via });
76
+ }
77
+ }
78
+
79
+ // B. Run-test contract (per flow).
80
+ const contracts: Contract[] = [];
81
+ for (const f of flows) {
82
+ const fp = featurePath(cwd, 'flows', f);
83
+ const parallel = featureTags(fp).some((t) => /^@parallel$/i.test(t));
84
+ const scs = loadScenarios(fp);
85
+ let cartStateCount = 0;
86
+ for (const s of scs) {
87
+ const h = s.haystack;
88
+ if (/\bremember\b/.test(h) && /(add to cart|view product)/.test(h)) {
89
+ contracts.push({ flow: f, kind: 'SAME-CARD', scenario: s.name, message: 'remember + Add To Cart/View Product → scope to ONE card at run-test, else the identity proof is hollow.' });
90
+ }
91
+ if (/(add to cart|remove|\bcart\b|quantity)/.test(h)) cartStateCount++;
92
+ }
93
+ if (parallel && cartStateCount >= 2) {
94
+ contracts.push({ flow: f, kind: 'ISOLATION', message: `@parallel + ${cartStateCount} cart/state scenarios → give each a fresh browser context, else count/quantity asserts are flaky.` });
95
+ }
96
+ }
97
+
98
+ return { screens, flows, deferrals, contracts, missing: deferrals.filter((d) => d.verdict === 'missing').length };
99
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Flow Capability Planner (Phase 2c) — flow-aware, deterministic, recommend-only.
3
+ *
4
+ * Decomposes a flow into per-screen LEGS (from [Screen:Element] refs), reports each
5
+ * leg's SELECTOR READINESS + capability, folds in the manual-reason taxonomy
6
+ * (capability-plan) and the run-test contract (flow-check), and emits a run-test
7
+ * PLAN. Automates the manual diagnosis done while healing cart-and-filter.
8
+ * See reports/sungen_phase2c_spec.md.
9
+ */
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { parse as parseYaml } from 'yaml';
13
+ import { MANUAL_REASONS, inferReasonCode } from './capability-plan';
14
+ import { buildFlowCheck, Contract } from './flow-check';
15
+
16
+ const REF_RE = /\[([A-Za-z][\w ]*?):([\w &.-]+?)\]/g;
17
+
18
+ interface FlowScenario { name: string; manual: boolean; reason: string; tags: string[]; refs: { screen: string; element: string }[] }
19
+
20
+ function parseFlowScenarios(featurePath: string): FlowScenario[] {
21
+ if (!fs.existsSync(featurePath)) return [];
22
+ const lines = fs.readFileSync(featurePath, 'utf-8').split('\n');
23
+ const out: FlowScenario[] = [];
24
+ const idx: number[] = [];
25
+ lines.forEach((l, i) => { if (/^\s*Scenario:/.test(l)) idx.push(i); });
26
+ for (let s = 0; s < idx.length; s++) {
27
+ const i = idx[s];
28
+ const end = s + 1 < idx.length ? idx[s + 1] : lines.length;
29
+ const name = lines[i].replace(/^\s*Scenario:\s*/, '').trim();
30
+ // tags + reason from the lines above + first comment in body
31
+ const tags: string[] = []; let reason = '';
32
+ for (let j = i - 1; j >= 0; j--) {
33
+ const t = lines[j].trim();
34
+ if (t === '') break;
35
+ if (/^@/.test(t)) tags.unshift(...t.split(/\s+/).filter((x) => x.startsWith('@')));
36
+ else if (/^#/.test(t)) { if (!reason) reason = t.replace(/^#+\s*/, ''); }
37
+ else break;
38
+ }
39
+ const body = lines.slice(i, end).join('\n');
40
+ if (!reason) { const m = body.match(/^\s*#\s*(.+)$/m); if (m) reason = m[1].trim(); }
41
+ const refs: FlowScenario['refs'] = [];
42
+ let mm: RegExpExecArray | null;
43
+ REF_RE.lastIndex = 0;
44
+ while ((mm = REF_RE.exec(body))) refs.push({ screen: mm[1].trim(), element: mm[2].trim() });
45
+ out.push({ name, manual: tags.some((t) => /^@manual\b/i.test(t)), reason, tags, refs });
46
+ }
47
+ return out;
48
+ }
49
+
50
+ function selectorKeys(selectorsPath: string): Set<string> {
51
+ if (!fs.existsSync(selectorsPath)) return new Set();
52
+ try {
53
+ const y = parseYaml(fs.readFileSync(selectorsPath, 'utf-8')) || {};
54
+ return new Set(Object.keys(y).map((k) => k.toLowerCase()));
55
+ } catch { return new Set(); }
56
+ }
57
+
58
+ export interface LegPlan {
59
+ screen: string;
60
+ scenarios: number;
61
+ refs: string[];
62
+ haveKeys: number;
63
+ readiness: 'ready' | 'partial' | 'missing';
64
+ touchedByAutomated: boolean;
65
+ }
66
+ export interface FlowPlan {
67
+ flow: string;
68
+ total: number;
69
+ legs: LegPlan[];
70
+ byReason: Record<string, number>;
71
+ capabilityManual: number;
72
+ judgmentManual: number;
73
+ contracts: Contract[];
74
+ readiness: 'ready' | 'not-ready';
75
+ missingLegs: string[];
76
+ plan: string[];
77
+ }
78
+
79
+ export function buildFlowPlan(cwd: string, flow: string): FlowPlan {
80
+ const featurePath = path.join(cwd, 'qa', 'flows', flow, 'features', `${flow}.feature`);
81
+ const selectorsPath = path.join(cwd, 'qa', 'flows', flow, 'selectors', `${flow}.yaml`);
82
+ const scenarios = parseFlowScenarios(featurePath);
83
+ const keys = selectorKeys(selectorsPath);
84
+
85
+ // Legs = distinct screen namespaces.
86
+ const legMap = new Map<string, { scenarios: Set<string>; refs: Set<string>; automated: boolean }>();
87
+ const byReason: Record<string, number> = {};
88
+ let capabilityManual = 0, judgmentManual = 0;
89
+
90
+ for (const sc of scenarios) {
91
+ if (sc.manual) {
92
+ const { code } = inferReasonCode(sc.tags, sc.reason);
93
+ byReason[code] = (byReason[code] || 0) + 1;
94
+ const cls = MANUAL_REASONS[code]?.cls;
95
+ if (cls === 'capability') capabilityManual++; else if (cls === 'keep') judgmentManual++;
96
+ }
97
+ for (const r of sc.refs) {
98
+ const leg = r.screen.toLowerCase();
99
+ const entry = legMap.get(leg) || { scenarios: new Set(), refs: new Set(), automated: false };
100
+ entry.scenarios.add(sc.name);
101
+ entry.refs.add(`${leg}:${r.element.toLowerCase()}`);
102
+ if (!sc.manual) entry.automated = true;
103
+ legMap.set(leg, entry);
104
+ }
105
+ }
106
+
107
+ const legs: LegPlan[] = [...legMap.entries()].map(([screen, e]) => {
108
+ const refs = [...e.refs];
109
+ const haveKeys = refs.filter((rk) => keys.has(rk)).length;
110
+ const readiness: LegPlan['readiness'] = haveKeys === refs.length ? 'ready' : haveKeys === 0 ? 'missing' : 'partial';
111
+ return { screen, scenarios: e.scenarios.size, refs, haveKeys, readiness, touchedByAutomated: e.automated };
112
+ }).sort((a, b) => b.scenarios - a.scenarios);
113
+
114
+ const missingLegs = legs.filter((l) => l.touchedByAutomated && l.readiness !== 'ready').map((l) => l.screen);
115
+ const fc = buildFlowCheck(cwd, flow);
116
+ const contracts = fc.contracts.filter((c) => c.flow === flow);
117
+
118
+ const readiness: FlowPlan['readiness'] = missingLegs.length ? 'not-ready' : 'ready';
119
+ const plan: string[] = [];
120
+ if (missingLegs.length) {
121
+ plan.push(`Run \`/sungen:run-test ${flow}\` → Phase 0 generates selectors for: ${missingLegs.join(', ')} (from the live pages).`);
122
+ }
123
+ if (contracts.some((c) => c.kind === 'SAME-CARD')) plan.push('Honour SAME-CARD: scope remember + Add To Cart/View Product to ONE card at run-test.');
124
+ if (contracts.some((c) => c.kind === 'ISOLATION')) plan.push('Honour ISOLATION: fresh browser context per cart/state scenario (@parallel).');
125
+ const driverReasons = Object.keys(byReason).filter((c) => MANUAL_REASONS[c]?.cls === 'capability');
126
+ if (driverReasons.length) {
127
+ const drivers = [...new Set(driverReasons.flatMap((c) => MANUAL_REASONS[c].drivers))];
128
+ if (drivers.length) plan.push(`Consider drivers (recommend-only): ${drivers.join(', ')} — for capability-manual (${driverReasons.join('/')}).`);
129
+ } else {
130
+ plan.push('No capability driver needed — all automated legs are @ui.');
131
+ }
132
+ if (readiness === 'ready') plan.unshift('Selectors present for every automated leg — ready to compile + run.');
133
+
134
+ return { flow, total: scenarios.length, legs, byReason, capabilityManual, judgmentManual, contracts, readiness, missingLegs, plan };
135
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Intent Profile (harness-roadmap P3) — lets the gate flex to project intent.
3
+ *
4
+ * Read from qa/context.md so a project can declare what it cares about:
5
+ *
6
+ * ## Intent
7
+ * focus: security # functional | e-commerce | security | smoke
8
+ * risk_tier: high # high | normal | low (reserved — future weighting)
9
+ * tier_scope: full # tier-1 | full (reserved)
10
+ *
11
+ * Keys may appear anywhere in context.md (a heading is optional). Unknown / missing
12
+ * values fall back to the safe default (functional), so behaviour is unchanged for
13
+ * projects that haven't declared an intent.
14
+ */
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+
18
+ export type IntentFocus = 'functional' | 'e-commerce' | 'security' | 'smoke';
19
+
20
+ export interface IntentProfile {
21
+ focus: IntentFocus;
22
+ riskTier: 'high' | 'normal' | 'low';
23
+ tierScope: 'tier-1' | 'full';
24
+ source: 'context.md' | 'default';
25
+ }
26
+
27
+ const DEFAULT_INTENT: IntentProfile = {
28
+ focus: 'functional', riskTier: 'normal', tierScope: 'full', source: 'default',
29
+ };
30
+
31
+ const FOCI: IntentFocus[] = ['functional', 'e-commerce', 'security', 'smoke'];
32
+
33
+ /** Resolve project root from a screen/flow dir (…/qa/screens/<name>). */
34
+ export function projectRootFromScreenDir(screenDir: string): string {
35
+ return path.resolve(screenDir, '..', '..', '..');
36
+ }
37
+
38
+ export function readIntent(projectRoot: string): IntentProfile {
39
+ const ctx = path.join(projectRoot, 'qa', 'context.md');
40
+ if (!fs.existsSync(ctx)) return DEFAULT_INTENT;
41
+ let text: string;
42
+ try { text = fs.readFileSync(ctx, 'utf-8').toLowerCase(); } catch { return DEFAULT_INTENT; }
43
+
44
+ const grab = (key: string): string | undefined => {
45
+ const m = text.match(new RegExp(`(?:^|\\n)\\s*${key}\\s*:\\s*([a-z0-9-]+)`));
46
+ return m?.[1];
47
+ };
48
+
49
+ const focusRaw = grab('focus');
50
+ const focus = (FOCI.includes(focusRaw as IntentFocus) ? focusRaw : DEFAULT_INTENT.focus) as IntentFocus;
51
+ const risk = grab('risk_tier');
52
+ const riskTier = (['high', 'normal', 'low'].includes(risk as string) ? risk : DEFAULT_INTENT.riskTier) as IntentProfile['riskTier'];
53
+ const scope = grab('tier_scope');
54
+ const tierScope = (['tier-1', 'full'].includes(scope as string) ? scope : DEFAULT_INTENT.tierScope) as IntentProfile['tierScope'];
55
+
56
+ const found = focusRaw || risk || scope;
57
+ return { focus, riskTier, tierScope, source: found ? 'context.md' : 'default' };
58
+ }