@codyswann/lisa 2.352.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  2. package/dist/core/upstream-evidence-manifest.js +70 -43
  3. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  4. package/expo/copy-overwrite/scripts/bdd/discover.mjs +502 -0
  5. package/expo/copy-overwrite/scripts/bdd/envelope.mjs +10 -1
  6. package/expo/copy-overwrite/scripts/bdd/render.mjs +53 -0
  7. package/expo/copy-overwrite/scripts/bdd/report.mjs +46 -0
  8. package/expo/copy-overwrite/scripts/check-bdd-coverage.mjs +39 -4
  9. package/expo/create-only/.github/workflows/maestro-e2e.yml +10 -1
  10. package/expo/create-only/.github/workflows/nightly-e2e-health.yml +5 -0
  11. package/expo/create-only/bdd/coverage-map.json +16 -0
  12. package/package.json +1 -1
  13. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  15. package/plugins/lisa/.codex-plugin/skills/lisa-agent-ready/SKILL.md +5 -3
  16. package/plugins/lisa/.codex-plugin/skills/lisa-confluence-to-tracker/SKILL.md +2 -1
  17. package/plugins/lisa/.codex-plugin/skills/lisa-debrief-apply/SKILL.md +2 -2
  18. package/plugins/lisa/.codex-plugin/skills/lisa-exploratory-qa/SKILL.md +7 -1
  19. package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +5 -0
  20. package/plugins/lisa/.codex-plugin/skills/lisa-github-create/SKILL.md +3 -1
  21. package/plugins/lisa/.codex-plugin/skills/lisa-github-to-tracker/SKILL.md +2 -1
  22. package/plugins/lisa/.codex-plugin/skills/lisa-github-validate-issue/SKILL.md +7 -3
  23. package/plugins/lisa/.codex-plugin/skills/lisa-github-write-issue/SKILL.md +16 -6
  24. package/plugins/lisa/.codex-plugin/skills/lisa-improve-harness/SKILL.md +1 -1
  25. package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +5 -0
  26. package/plugins/lisa/.codex-plugin/skills/lisa-jira-create/SKILL.md +2 -0
  27. package/plugins/lisa/.codex-plugin/skills/lisa-jira-write-ticket/SKILL.md +12 -2
  28. package/plugins/lisa/.codex-plugin/skills/lisa-learnings-audit/SKILL.md +6 -1
  29. package/plugins/lisa/.codex-plugin/skills/lisa-linear-access/SKILL.md +34 -0
  30. package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +5 -0
  31. package/plugins/lisa/.codex-plugin/skills/lisa-linear-create/SKILL.md +3 -1
  32. package/plugins/lisa/.codex-plugin/skills/lisa-linear-to-tracker/SKILL.md +2 -1
  33. package/plugins/lisa/.codex-plugin/skills/lisa-linear-write-issue/SKILL.md +13 -4
  34. package/plugins/lisa/.codex-plugin/skills/lisa-notion-to-tracker/SKILL.md +2 -1
  35. package/plugins/lisa/.codex-plugin/skills/lisa-persist-learning/SKILL.md +1 -1
  36. package/plugins/lisa/.codex-plugin/skills/lisa-prd-source-write/SKILL.md +4 -2
  37. package/plugins/lisa/.codex-plugin/skills/lisa-qa-fail/SKILL.md +3 -1
  38. package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +28 -0
  39. package/plugins/lisa/.codex-plugin/skills/lisa-rework-triage/SKILL.md +2 -2
  40. package/plugins/lisa/.codex-plugin/skills/lisa-tracker-build-intake/SKILL.md +1 -0
  41. package/plugins/lisa/.codex-plugin/skills/lisa-tracker-create/SKILL.md +1 -0
  42. package/plugins/lisa/.codex-plugin/skills/lisa-tracker-write/SKILL.md +1 -0
  43. package/plugins/lisa/.codex-plugin/skills/lisa-validate-tracker-mapping/SKILL.md +33 -4
  44. package/plugins/lisa/.codex-plugin/skills/lisa-verify/SKILL.md +1 -1
  45. package/plugins/lisa/rules/eager/bdd-e2e-coverage.md +3 -1
  46. package/plugins/lisa/rules/eager/claim-time-guards.md +30 -0
  47. package/plugins/lisa/rules/eager/ready-role-filing.md +22 -0
  48. package/plugins/lisa/rules/reference/automation-runbook-contract.md +8 -1
  49. package/plugins/lisa/rules/reference/bdd-e2e-coverage.md +42 -6
  50. package/plugins/lisa/rules/reference/claim-time-guards.md +69 -0
  51. package/plugins/lisa/rules/reference/config-resolution.md +7 -0
  52. package/plugins/lisa/rules/reference/ready-role-filing.md +66 -0
  53. package/plugins/lisa/scripts/queue-contract-resolution.mjs +50 -1
  54. package/plugins/lisa/skills/lisa-agent-ready/SKILL.md +5 -3
  55. package/plugins/lisa/skills/lisa-confluence-to-tracker/SKILL.md +2 -1
  56. package/plugins/lisa/skills/lisa-debrief-apply/SKILL.md +2 -2
  57. package/plugins/lisa/skills/lisa-exploratory-qa/SKILL.md +7 -1
  58. package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +5 -0
  59. package/plugins/lisa/skills/lisa-github-create/SKILL.md +3 -1
  60. package/plugins/lisa/skills/lisa-github-to-tracker/SKILL.md +2 -1
  61. package/plugins/lisa/skills/lisa-github-validate-issue/SKILL.md +7 -3
  62. package/plugins/lisa/skills/lisa-github-write-issue/SKILL.md +16 -6
  63. package/plugins/lisa/skills/lisa-improve-harness/SKILL.md +1 -1
  64. package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +5 -0
  65. package/plugins/lisa/skills/lisa-jira-create/SKILL.md +2 -0
  66. package/plugins/lisa/skills/lisa-jira-write-ticket/SKILL.md +12 -2
  67. package/plugins/lisa/skills/lisa-learnings-audit/SKILL.md +6 -1
  68. package/plugins/lisa/skills/lisa-linear-access/SKILL.md +34 -0
  69. package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +5 -0
  70. package/plugins/lisa/skills/lisa-linear-create/SKILL.md +3 -1
  71. package/plugins/lisa/skills/lisa-linear-to-tracker/SKILL.md +2 -1
  72. package/plugins/lisa/skills/lisa-linear-write-issue/SKILL.md +13 -4
  73. package/plugins/lisa/skills/lisa-notion-to-tracker/SKILL.md +2 -1
  74. package/plugins/lisa/skills/lisa-persist-learning/SKILL.md +1 -1
  75. package/plugins/lisa/skills/lisa-prd-source-write/SKILL.md +4 -2
  76. package/plugins/lisa/skills/lisa-qa-fail/SKILL.md +3 -1
  77. package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +28 -0
  78. package/plugins/lisa/skills/lisa-rework-triage/SKILL.md +2 -2
  79. package/plugins/lisa/skills/lisa-tracker-build-intake/SKILL.md +1 -0
  80. package/plugins/lisa/skills/lisa-tracker-create/SKILL.md +1 -0
  81. package/plugins/lisa/skills/lisa-tracker-write/SKILL.md +1 -0
  82. package/plugins/lisa/skills/lisa-validate-tracker-mapping/SKILL.md +33 -4
  83. package/plugins/lisa/skills/lisa-verify/SKILL.md +1 -1
  84. package/plugins/lisa-agy/plugin.json +1 -1
  85. package/plugins/lisa-agy/scripts/queue-contract-resolution.mjs +50 -1
  86. package/plugins/lisa-agy/skills/lisa-agent-ready/SKILL.md +5 -3
  87. package/plugins/lisa-agy/skills/lisa-confluence-to-tracker/SKILL.md +2 -1
  88. package/plugins/lisa-agy/skills/lisa-debrief-apply/SKILL.md +2 -2
  89. package/plugins/lisa-agy/skills/lisa-exploratory-qa/SKILL.md +7 -1
  90. package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +5 -0
  91. package/plugins/lisa-agy/skills/lisa-github-create/SKILL.md +3 -1
  92. package/plugins/lisa-agy/skills/lisa-github-to-tracker/SKILL.md +2 -1
  93. package/plugins/lisa-agy/skills/lisa-github-validate-issue/SKILL.md +7 -3
  94. package/plugins/lisa-agy/skills/lisa-github-write-issue/SKILL.md +16 -6
  95. package/plugins/lisa-agy/skills/lisa-improve-harness/SKILL.md +1 -1
  96. package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +5 -0
  97. package/plugins/lisa-agy/skills/lisa-jira-create/SKILL.md +2 -0
  98. package/plugins/lisa-agy/skills/lisa-jira-write-ticket/SKILL.md +12 -2
  99. package/plugins/lisa-agy/skills/lisa-learnings-audit/SKILL.md +6 -1
  100. package/plugins/lisa-agy/skills/lisa-linear-access/SKILL.md +34 -0
  101. package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +5 -0
  102. package/plugins/lisa-agy/skills/lisa-linear-create/SKILL.md +3 -1
  103. package/plugins/lisa-agy/skills/lisa-linear-to-tracker/SKILL.md +2 -1
  104. package/plugins/lisa-agy/skills/lisa-linear-write-issue/SKILL.md +13 -4
  105. package/plugins/lisa-agy/skills/lisa-notion-to-tracker/SKILL.md +2 -1
  106. package/plugins/lisa-agy/skills/lisa-persist-learning/SKILL.md +1 -1
  107. package/plugins/lisa-agy/skills/lisa-prd-source-write/SKILL.md +4 -2
  108. package/plugins/lisa-agy/skills/lisa-qa-fail/SKILL.md +3 -1
  109. package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +28 -0
  110. package/plugins/lisa-agy/skills/lisa-rework-triage/SKILL.md +2 -2
  111. package/plugins/lisa-agy/skills/lisa-tracker-build-intake/SKILL.md +1 -0
  112. package/plugins/lisa-agy/skills/lisa-tracker-create/SKILL.md +1 -0
  113. package/plugins/lisa-agy/skills/lisa-tracker-write/SKILL.md +1 -0
  114. package/plugins/lisa-agy/skills/lisa-validate-tracker-mapping/SKILL.md +33 -4
  115. package/plugins/lisa-agy/skills/lisa-verify/SKILL.md +1 -1
  116. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  118. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  119. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  120. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  121. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-copilot/rules/eager/bdd-e2e-coverage.md +3 -1
  123. package/plugins/lisa-copilot/rules/eager/claim-time-guards.md +30 -0
  124. package/plugins/lisa-copilot/rules/eager/ready-role-filing.md +22 -0
  125. package/plugins/lisa-copilot/rules/reference/automation-runbook-contract.md +8 -1
  126. package/plugins/lisa-copilot/rules/reference/bdd-e2e-coverage.md +42 -6
  127. package/plugins/lisa-copilot/rules/reference/claim-time-guards.md +69 -0
  128. package/plugins/lisa-copilot/rules/reference/config-resolution.md +7 -0
  129. package/plugins/lisa-copilot/rules/reference/ready-role-filing.md +66 -0
  130. package/plugins/lisa-copilot/scripts/queue-contract-resolution.mjs +50 -1
  131. package/plugins/lisa-copilot/skills/lisa-agent-ready/SKILL.md +5 -3
  132. package/plugins/lisa-copilot/skills/lisa-confluence-to-tracker/SKILL.md +2 -1
  133. package/plugins/lisa-copilot/skills/lisa-debrief-apply/SKILL.md +2 -2
  134. package/plugins/lisa-copilot/skills/lisa-exploratory-qa/SKILL.md +7 -1
  135. package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +5 -0
  136. package/plugins/lisa-copilot/skills/lisa-github-create/SKILL.md +3 -1
  137. package/plugins/lisa-copilot/skills/lisa-github-to-tracker/SKILL.md +2 -1
  138. package/plugins/lisa-copilot/skills/lisa-github-validate-issue/SKILL.md +7 -3
  139. package/plugins/lisa-copilot/skills/lisa-github-write-issue/SKILL.md +16 -6
  140. package/plugins/lisa-copilot/skills/lisa-improve-harness/SKILL.md +1 -1
  141. package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +5 -0
  142. package/plugins/lisa-copilot/skills/lisa-jira-create/SKILL.md +2 -0
  143. package/plugins/lisa-copilot/skills/lisa-jira-write-ticket/SKILL.md +12 -2
  144. package/plugins/lisa-copilot/skills/lisa-learnings-audit/SKILL.md +6 -1
  145. package/plugins/lisa-copilot/skills/lisa-linear-access/SKILL.md +34 -0
  146. package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +5 -0
  147. package/plugins/lisa-copilot/skills/lisa-linear-create/SKILL.md +3 -1
  148. package/plugins/lisa-copilot/skills/lisa-linear-to-tracker/SKILL.md +2 -1
  149. package/plugins/lisa-copilot/skills/lisa-linear-write-issue/SKILL.md +13 -4
  150. package/plugins/lisa-copilot/skills/lisa-notion-to-tracker/SKILL.md +2 -1
  151. package/plugins/lisa-copilot/skills/lisa-persist-learning/SKILL.md +1 -1
  152. package/plugins/lisa-copilot/skills/lisa-prd-source-write/SKILL.md +4 -2
  153. package/plugins/lisa-copilot/skills/lisa-qa-fail/SKILL.md +3 -1
  154. package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +28 -0
  155. package/plugins/lisa-copilot/skills/lisa-rework-triage/SKILL.md +2 -2
  156. package/plugins/lisa-copilot/skills/lisa-tracker-build-intake/SKILL.md +1 -0
  157. package/plugins/lisa-copilot/skills/lisa-tracker-create/SKILL.md +1 -0
  158. package/plugins/lisa-copilot/skills/lisa-tracker-write/SKILL.md +1 -0
  159. package/plugins/lisa-copilot/skills/lisa-validate-tracker-mapping/SKILL.md +33 -4
  160. package/plugins/lisa-copilot/skills/lisa-verify/SKILL.md +1 -1
  161. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  162. package/plugins/lisa-cursor/rules/automation-runbook-contract-reference.mdc +8 -1
  163. package/plugins/lisa-cursor/rules/bdd-e2e-coverage-reference.mdc +42 -6
  164. package/plugins/lisa-cursor/rules/bdd-e2e-coverage.mdc +3 -1
  165. package/plugins/lisa-cursor/rules/claim-time-guards-reference.mdc +74 -0
  166. package/plugins/lisa-cursor/rules/claim-time-guards.mdc +35 -0
  167. package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +7 -0
  168. package/plugins/lisa-cursor/rules/ready-role-filing-reference.mdc +71 -0
  169. package/plugins/lisa-cursor/rules/ready-role-filing.mdc +27 -0
  170. package/plugins/lisa-cursor/scripts/queue-contract-resolution.mjs +50 -1
  171. package/plugins/lisa-cursor/skills/lisa-agent-ready/SKILL.md +5 -3
  172. package/plugins/lisa-cursor/skills/lisa-confluence-to-tracker/SKILL.md +2 -1
  173. package/plugins/lisa-cursor/skills/lisa-debrief-apply/SKILL.md +2 -2
  174. package/plugins/lisa-cursor/skills/lisa-exploratory-qa/SKILL.md +7 -1
  175. package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +5 -0
  176. package/plugins/lisa-cursor/skills/lisa-github-create/SKILL.md +3 -1
  177. package/plugins/lisa-cursor/skills/lisa-github-to-tracker/SKILL.md +2 -1
  178. package/plugins/lisa-cursor/skills/lisa-github-validate-issue/SKILL.md +7 -3
  179. package/plugins/lisa-cursor/skills/lisa-github-write-issue/SKILL.md +16 -6
  180. package/plugins/lisa-cursor/skills/lisa-improve-harness/SKILL.md +1 -1
  181. package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +5 -0
  182. package/plugins/lisa-cursor/skills/lisa-jira-create/SKILL.md +2 -0
  183. package/plugins/lisa-cursor/skills/lisa-jira-write-ticket/SKILL.md +12 -2
  184. package/plugins/lisa-cursor/skills/lisa-learnings-audit/SKILL.md +6 -1
  185. package/plugins/lisa-cursor/skills/lisa-linear-access/SKILL.md +34 -0
  186. package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +5 -0
  187. package/plugins/lisa-cursor/skills/lisa-linear-create/SKILL.md +3 -1
  188. package/plugins/lisa-cursor/skills/lisa-linear-to-tracker/SKILL.md +2 -1
  189. package/plugins/lisa-cursor/skills/lisa-linear-write-issue/SKILL.md +13 -4
  190. package/plugins/lisa-cursor/skills/lisa-notion-to-tracker/SKILL.md +2 -1
  191. package/plugins/lisa-cursor/skills/lisa-persist-learning/SKILL.md +1 -1
  192. package/plugins/lisa-cursor/skills/lisa-prd-source-write/SKILL.md +4 -2
  193. package/plugins/lisa-cursor/skills/lisa-qa-fail/SKILL.md +3 -1
  194. package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +28 -0
  195. package/plugins/lisa-cursor/skills/lisa-rework-triage/SKILL.md +2 -2
  196. package/plugins/lisa-cursor/skills/lisa-tracker-build-intake/SKILL.md +1 -0
  197. package/plugins/lisa-cursor/skills/lisa-tracker-create/SKILL.md +1 -0
  198. package/plugins/lisa-cursor/skills/lisa-tracker-write/SKILL.md +1 -0
  199. package/plugins/lisa-cursor/skills/lisa-validate-tracker-mapping/SKILL.md +33 -4
  200. package/plugins/lisa-cursor/skills/lisa-verify/SKILL.md +1 -1
  201. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  202. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  203. package/plugins/lisa-expo-agy/plugin.json +1 -1
  204. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  205. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  206. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  207. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  208. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  209. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  210. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  211. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  212. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  213. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  214. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  215. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  216. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  217. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  218. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  219. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  220. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  221. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  222. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  223. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  224. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  225. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  226. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  227. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  228. package/plugins/lisa-rails-agy/plugin.json +1 -1
  229. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  230. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  231. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  232. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  233. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  234. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  235. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  236. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  237. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  238. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  239. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  240. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  241. package/plugins/src/base/rules/eager/bdd-e2e-coverage.md +3 -1
  242. package/plugins/src/base/rules/eager/claim-time-guards.md +30 -0
  243. package/plugins/src/base/rules/eager/ready-role-filing.md +22 -0
  244. package/plugins/src/base/rules/reference/automation-runbook-contract.md +8 -1
  245. package/plugins/src/base/rules/reference/bdd-e2e-coverage.md +42 -6
  246. package/plugins/src/base/rules/reference/claim-time-guards.md +69 -0
  247. package/plugins/src/base/rules/reference/config-resolution.md +7 -0
  248. package/plugins/src/base/rules/reference/ready-role-filing.md +66 -0
  249. package/plugins/src/base/scripts/queue-contract-resolution.mjs +50 -1
  250. package/plugins/src/base/skills/lisa-agent-ready/SKILL.md +5 -3
  251. package/plugins/src/base/skills/lisa-confluence-to-tracker/SKILL.md +2 -1
  252. package/plugins/src/base/skills/lisa-debrief-apply/SKILL.md +2 -2
  253. package/plugins/src/base/skills/lisa-exploratory-qa/SKILL.md +7 -1
  254. package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +5 -0
  255. package/plugins/src/base/skills/lisa-github-create/SKILL.md +3 -1
  256. package/plugins/src/base/skills/lisa-github-to-tracker/SKILL.md +2 -1
  257. package/plugins/src/base/skills/lisa-github-validate-issue/SKILL.md +7 -3
  258. package/plugins/src/base/skills/lisa-github-write-issue/SKILL.md +16 -6
  259. package/plugins/src/base/skills/lisa-improve-harness/SKILL.md +1 -1
  260. package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +5 -0
  261. package/plugins/src/base/skills/lisa-jira-create/SKILL.md +2 -0
  262. package/plugins/src/base/skills/lisa-jira-write-ticket/SKILL.md +12 -2
  263. package/plugins/src/base/skills/lisa-learnings-audit/SKILL.md +6 -1
  264. package/plugins/src/base/skills/lisa-linear-access/SKILL.md +34 -0
  265. package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +5 -0
  266. package/plugins/src/base/skills/lisa-linear-create/SKILL.md +3 -1
  267. package/plugins/src/base/skills/lisa-linear-to-tracker/SKILL.md +2 -1
  268. package/plugins/src/base/skills/lisa-linear-write-issue/SKILL.md +13 -4
  269. package/plugins/src/base/skills/lisa-notion-to-tracker/SKILL.md +2 -1
  270. package/plugins/src/base/skills/lisa-persist-learning/SKILL.md +1 -1
  271. package/plugins/src/base/skills/lisa-prd-source-write/SKILL.md +4 -2
  272. package/plugins/src/base/skills/lisa-qa-fail/SKILL.md +3 -1
  273. package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +28 -0
  274. package/plugins/src/base/skills/lisa-rework-triage/SKILL.md +2 -2
  275. package/plugins/src/base/skills/lisa-tracker-build-intake/SKILL.md +1 -0
  276. package/plugins/src/base/skills/lisa-tracker-create/SKILL.md +1 -0
  277. package/plugins/src/base/skills/lisa-tracker-write/SKILL.md +1 -0
  278. package/plugins/src/base/skills/lisa-validate-tracker-mapping/SKILL.md +33 -4
  279. package/plugins/src/base/skills/lisa-verify/SKILL.md +1 -1
  280. package/typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs +76 -10
@@ -0,0 +1,502 @@
1
+ /**
2
+ * Test-file DISCOVERY for the BDD gate.
3
+ *
4
+ * Validating what a manifest DECLARES can only ever find defects in the
5
+ * declarations. A test file nobody declared is invisible to that check, which
6
+ * is precisely how undeclared end-to-end specs came to sit on default branches
7
+ * with a green gate above them. This module walks the project's own declared
8
+ * roots, extracts each test's evidence string, and hands the caller the specs
9
+ * that no mapping and no exclusion accounts for.
10
+ *
11
+ * Nothing here names a runner, a directory, or a file extension: roots,
12
+ * extensions and the evidence grammar are per-runner CONTRACT DATA
13
+ * (`testDiscovery` in the coverage map), and the runner→platform pairing is
14
+ * derived from `runnerPlatforms`. A gate with `e2e/` compiled into it cannot
15
+ * see a project that keeps its flows anywhere else — the exact reason a
16
+ * subflow directory was structurally invisible to the fork this replaces.
17
+ *
18
+ * @module scripts/bdd/discover
19
+ */
20
+ import * as fs from "node:fs";
21
+ import * as path from "node:path";
22
+
23
+ import { listFiles, posix, resolveInsideRepo } from "./parse.mjs";
24
+
25
+ /**
26
+ * Build one defect record, always naming the entity it is about.
27
+ * @param {string} code - Stable machine-readable defect code.
28
+ * @param {string} message - Operator-readable description.
29
+ * @param {string} subject - The entity the finding is about.
30
+ * @returns {{code: string, message: string, subject: string}} The defect.
31
+ */
32
+ const defect = (code, message, subject) => ({ code, message, subject });
33
+
34
+ /**
35
+ * The evidence grammars a project may choose from, as SOURCE CONSTANTS.
36
+ *
37
+ * An allowlist, never a project-supplied regular expression: the coverage map
38
+ * is repo data an author edits, and compiling a pattern out of it would hand
39
+ * that author the gate's own execution. `call-title` reads a titled test call
40
+ * (`test("...")`, `it.skip('...')`); `line-field` reads a leading document
41
+ * field (`name: ...`), which is how flow-style runners title a file.
42
+ */
43
+ export const EVIDENCE_KINDS = Object.freeze(["call-title", "line-field"]);
44
+
45
+ /** A JavaScript identifier, the only shape a declared function name may take. */
46
+ const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
47
+
48
+ /** A document field name, the only shape a declared field may take. */
49
+ const FIELD_NAME = /^[A-Za-z_][A-Za-z0-9_-]*$/;
50
+
51
+ /**
52
+ * True for a plain JSON object.
53
+ * @param {unknown} value - Candidate.
54
+ * @returns {boolean} Whether it is a non-array object.
55
+ */
56
+ const isObject = value =>
57
+ typeof value === "object" && value !== null && !Array.isArray(value);
58
+
59
+ /**
60
+ * True for a non-empty array whose every entry passes a test.
61
+ * @param {unknown} value - Candidate.
62
+ * @param {(entry: unknown) => boolean} predicate - Entry test.
63
+ * @returns {boolean} Whether the array is usable.
64
+ */
65
+ const isListOf = (value, predicate) =>
66
+ Array.isArray(value) && value.length > 0 && value.every(predicate);
67
+
68
+ /**
69
+ * The problems that make one runner's discovery block unusable.
70
+ *
71
+ * Every one of these is refused rather than skipped. Skipping a malformed
72
+ * block would silently disable discovery for that runner — the same
73
+ * one-character fail-open the coverage floor already refuses.
74
+ * @param {string} runner - Runner name the block is keyed under.
75
+ * @param {unknown} config - The raw block.
76
+ * @param {object} contract - Parsed coverage map.
77
+ * @returns {string[]} Reasons, empty when the block is usable.
78
+ */
79
+ function configProblems(runner, config, contract) {
80
+ if (!contract.runnerPlatforms?.[runner]) {
81
+ return [`names runner ${runner}, which runnerPlatforms does not declare`];
82
+ }
83
+ if (!isObject(config)) return ["is not a JSON object"];
84
+ return [
85
+ ...rootProblems(config.roots),
86
+ ...(isListOf(config.extensions, entry => isExtension(entry))
87
+ ? []
88
+ : ['extensions must be a non-empty array of suffixes like ".spec.ts"']),
89
+ ...(config.ignore === undefined ||
90
+ (Array.isArray(config.ignore) &&
91
+ config.ignore.every(entry => typeof entry === "string"))
92
+ ? []
93
+ : ["ignore must be an array of repo-relative path prefixes"]),
94
+ ...evidenceProblems(config.evidence),
95
+ ];
96
+ }
97
+
98
+ /**
99
+ * Whether a declared extension is a usable suffix.
100
+ * @param {unknown} entry - Candidate extension.
101
+ * @returns {boolean} Whether it is a dot-prefixed suffix.
102
+ */
103
+ function isExtension(entry) {
104
+ return typeof entry === "string" && entry.startsWith(".") && entry.length > 1;
105
+ }
106
+
107
+ /**
108
+ * Reject unusable or escaping roots.
109
+ * @param {unknown} roots - Declared roots.
110
+ * @returns {string[]} Reasons, empty when usable.
111
+ */
112
+ function rootProblems(roots) {
113
+ if (
114
+ !isListOf(roots, entry => typeof entry === "string" && entry.length > 0)
115
+ ) {
116
+ return ["roots must be a non-empty array of repo-relative directories"];
117
+ }
118
+ return roots
119
+ .filter(
120
+ root =>
121
+ path.isAbsolute(root) ||
122
+ /^[a-zA-Z]:/.test(root) ||
123
+ root.split(/[\\/]/).includes("..")
124
+ )
125
+ .map(root => `root ${root} must be repo-relative and must not traverse up`);
126
+ }
127
+
128
+ /**
129
+ * Reject an evidence grammar the gate does not implement, or one whose
130
+ * parameters would end up inside a regular expression unvalidated.
131
+ * @param {unknown} evidence - Declared evidence grammar.
132
+ * @returns {string[]} Reasons, empty when usable.
133
+ */
134
+ function evidenceProblems(evidence) {
135
+ if (!isObject(evidence)) return ["evidence must be a JSON object"];
136
+ if (!EVIDENCE_KINDS.includes(evidence.kind)) {
137
+ return [
138
+ `evidence.kind ${JSON.stringify(evidence.kind)} is not one of ${EVIDENCE_KINDS.join(", ")}`,
139
+ ];
140
+ }
141
+ if (evidence.kind === "call-title") {
142
+ return isListOf(
143
+ evidence.functions,
144
+ entry => typeof entry === "string" && IDENTIFIER.test(entry)
145
+ )
146
+ ? []
147
+ : ["evidence.functions must be a non-empty array of function names"];
148
+ }
149
+ return typeof evidence.field === "string" && FIELD_NAME.test(evidence.field)
150
+ ? []
151
+ : ["evidence.field must be a document field name"];
152
+ }
153
+
154
+ /**
155
+ * Read and validate the whole `testDiscovery` block.
156
+ * @param {object} contract - Parsed coverage map.
157
+ * @returns {{configs: Map<string, object>, defects: object[]}} Usable configs and refusals.
158
+ */
159
+ function readDiscovery(contract) {
160
+ const raw = contract.testDiscovery;
161
+ const configs = new Map();
162
+ const subject = "coverage-map.testDiscovery";
163
+ if (raw === undefined) return { configs, defects: [] };
164
+ if (!isObject(raw)) {
165
+ return {
166
+ configs,
167
+ defects: [
168
+ defect(
169
+ "discovery-invalid",
170
+ `${subject} must be a JSON object keyed by runner`,
171
+ subject
172
+ ),
173
+ ],
174
+ };
175
+ }
176
+ const defects = [];
177
+ for (const [runner, config] of Object.entries(raw)) {
178
+ if (runner.startsWith("_")) continue;
179
+ const problems = configProblems(runner, config, contract);
180
+ for (const problem of problems) {
181
+ defects.push(
182
+ defect(
183
+ "discovery-invalid",
184
+ `${subject}.${runner} ${problem}`,
185
+ `${subject}.${runner}`
186
+ )
187
+ );
188
+ }
189
+ if (problems.length === 0) configs.set(runner, config);
190
+ }
191
+ return { configs, defects };
192
+ }
193
+
194
+ /**
195
+ * Every titled test call in a source file, with the title taken VERBATIM from
196
+ * the source.
197
+ *
198
+ * A template-literal title is kept exactly as written — `${error.name}` and
199
+ * all. Rewriting or truncating it is how the fork this replaces produced
200
+ * evidence strings that matched nothing, which authors then had to paper over
201
+ * with exclusions for artifacts of the parser rather than of the repo. The
202
+ * verbatim text is a real substring of the file, so a mapping or exclusion
203
+ * naming it stays falsifiable exactly like any other evidence string.
204
+ * @param {string} source - File contents.
205
+ * @param {readonly string[]} functions - Declared test-function names.
206
+ * @returns {{evidence: string, dynamic: boolean}[]} Discovered titles in source order.
207
+ */
208
+ function callTitles(source, functions) {
209
+ const pattern = new RegExp(
210
+ String.raw`\b(?:${functions.join("|")})(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*\s*\(\s*` +
211
+ String.raw`(?:"((?:[^"\\\n]|\\.)*)"|'((?:[^'\\\n]|\\.)*)'|\x60([^\x60]*)\x60)`,
212
+ "g"
213
+ );
214
+ const found = [];
215
+ for (const match of source.matchAll(pattern)) {
216
+ const template = match[3];
217
+ const evidence = match[1] ?? match[2] ?? template;
218
+ if (evidence === undefined || evidence.length === 0) continue;
219
+ found.push({
220
+ evidence,
221
+ dynamic: template !== undefined && template.includes("${"),
222
+ });
223
+ }
224
+ return found;
225
+ }
226
+
227
+ /**
228
+ * The leading document field a flow-style runner titles a file with.
229
+ *
230
+ * When the field is absent the file itself is the discovered unit and carries
231
+ * NO evidence — inventing a title from the filename would fabricate a string
232
+ * that appears nowhere in the file, which is the opposite of falsifiable.
233
+ * @param {string} source - File contents.
234
+ * @param {string} field - Declared field name.
235
+ * @returns {{evidence: string|null, dynamic: boolean}[]} Exactly one entry.
236
+ */
237
+ function lineField(source, field) {
238
+ const match = new RegExp(String.raw`^\s*${field}:\s*(\S.*?)\s*$`, "m").exec(
239
+ source
240
+ );
241
+ return [{ evidence: match ? match[1] : null, dynamic: false }];
242
+ }
243
+
244
+ /**
245
+ * Walk one runner's declared roots and extract its tests.
246
+ * @param {object} input - Root, runner, its config, and the contract.
247
+ * @returns {object[]} Discovered specs.
248
+ */
249
+ function discoverRunner({ root, runner, config, contract }) {
250
+ const platforms = [...(contract.runnerPlatforms?.[runner] ?? [])].sort();
251
+ const ignore = config.ignore ?? [];
252
+ const specs = [];
253
+ for (const declaredRoot of config.roots) {
254
+ for (const file of filesUnder(root, declaredRoot, config)) {
255
+ const relative = posix(path.relative(root, file));
256
+ if (ignore.some(prefix => relative.startsWith(prefix))) continue;
257
+ const source = fs.readFileSync(file, "utf8");
258
+ for (const found of extract(source, config.evidence)) {
259
+ specs.push({ runner, platforms, file: relative, ...found });
260
+ }
261
+ }
262
+ }
263
+ return specs;
264
+ }
265
+
266
+ /**
267
+ * Extract evidence from one file under the declared grammar.
268
+ * @param {string} source - File contents.
269
+ * @param {object} evidence - The declared evidence grammar.
270
+ * @returns {{evidence: string|null, dynamic: boolean}[]} Discovered entries.
271
+ */
272
+ function extract(source, evidence) {
273
+ return evidence.kind === "call-title"
274
+ ? callTitles(source, evidence.functions)
275
+ : lineField(source, evidence.field);
276
+ }
277
+
278
+ /**
279
+ * List candidate files under one declared root.
280
+ *
281
+ * A root that does not exist yet is not an error — a project may declare where
282
+ * its flows WILL live. A root that resolves outside the repo is refused, on
283
+ * the same reasoning as a mapping path: repo data must never be able to read
284
+ * the runner's filesystem.
285
+ * @param {string} root - Repo root.
286
+ * @param {string} declaredRoot - One declared root.
287
+ * @param {object} config - The runner's discovery config.
288
+ * @returns {string[]} Absolute paths.
289
+ */
290
+ function filesUnder(root, declaredRoot, config) {
291
+ const directory = path.join(root, declaredRoot);
292
+ if (!fs.existsSync(directory)) return [];
293
+ if (!resolveInsideRepo(root, declaredRoot).path) return [];
294
+ return listFiles(directory, file =>
295
+ config.extensions.some(extension => file.endsWith(extension))
296
+ );
297
+ }
298
+
299
+ /**
300
+ * Discover every test file the project's own configuration points at.
301
+ * @param {object} input - Repo root and the parsed contract.
302
+ * @returns {object} Specs, configured runners, declared roots, and refusals.
303
+ */
304
+ export function discoverSpecs({ root, contract }) {
305
+ const { configs, defects } = readDiscovery(contract);
306
+ const specs = [];
307
+ const roots = [];
308
+ for (const [runner, config] of configs) {
309
+ roots.push(...config.roots.map(entry => posix(entry).replace(/\/+$/, "")));
310
+ specs.push(...discoverRunner({ root, runner, config, contract }));
311
+ }
312
+ return {
313
+ specs: specs.sort((a, b) =>
314
+ `${a.file}${a.evidence ?? ""}`.localeCompare(
315
+ `${b.file}${b.evidence ?? ""}`
316
+ )
317
+ ),
318
+ runners: [...configs.keys()].sort(),
319
+ roots: [...new Set(roots)].sort(),
320
+ defects,
321
+ };
322
+ }
323
+
324
+ /**
325
+ * Whether one map entry accounts for one discovered spec.
326
+ *
327
+ * An entry accounts for a spec when it names the same file AND its evidence
328
+ * string CONTAINS that spec's title. Containment in that direction is
329
+ * deliberate: an author may map `test("title"` or the bare title, but one
330
+ * short string can never come to account for every test in a file — which
331
+ * would rebuild the fail-open this check exists to close. A file-level entry
332
+ * (an exclusion with no evidence) accounts for the whole file, and so does any
333
+ * entry naming a file whose discovered unit has no title of its own.
334
+ * @param {object} entry - A mapping or exclusion.
335
+ * @param {object} spec - A discovered spec.
336
+ * @param {boolean} fileLevelAllowed - Whether a missing evidence string covers the file.
337
+ * @returns {boolean} Whether the spec is accounted for.
338
+ */
339
+ function accountsFor(entry, spec, fileLevelAllowed) {
340
+ if (entry.file !== spec.file) return false;
341
+ if (typeof entry.evidence !== "string" || entry.evidence.length === 0) {
342
+ return fileLevelAllowed;
343
+ }
344
+ return spec.evidence === null || entry.evidence.includes(spec.evidence);
345
+ }
346
+
347
+ /**
348
+ * Whether any mapping or exclusion discloses a discovered spec.
349
+ * @param {object} spec - A discovered spec.
350
+ * @param {object} contract - Parsed coverage map.
351
+ * @returns {boolean} Whether it is disclosed.
352
+ */
353
+ export function isDisclosed(spec, contract) {
354
+ return (
355
+ (contract.mappings ?? []).some(mapping =>
356
+ accountsFor(mapping, spec, false)
357
+ ) ||
358
+ (contract.exclusions ?? []).some(exclusion =>
359
+ accountsFor(exclusion, spec, true)
360
+ )
361
+ );
362
+ }
363
+
364
+ /**
365
+ * Defects for every discovered spec the contract never mentions.
366
+ * @param {readonly object[]} specs - Discovered specs.
367
+ * @param {object} contract - Parsed coverage map.
368
+ * @returns {object[]} Defects found.
369
+ */
370
+ function undisclosedDefects(specs, contract) {
371
+ return specs
372
+ .filter(spec => !isDisclosed(spec, contract))
373
+ .map(spec =>
374
+ defect(
375
+ "spec-undisclosed",
376
+ `${spec.file}${spec.evidence === null ? "" : ` :: ${JSON.stringify(spec.evidence)}`} is discovered by runner ${spec.runner} but named by no mapping and no exclusion. Map it to a scenario, or record an exclusion with a reason.`,
377
+ spec.file
378
+ )
379
+ );
380
+ }
381
+
382
+ /**
383
+ * Defects for exclusions that no longer excuse anything.
384
+ *
385
+ * An exclusion is a standing claim that a real test aligns to no product
386
+ * behavior. Once its file is gone, its title has been renamed, or no
387
+ * configured root even covers it, the claim is about nothing — and a pile of
388
+ * such claims is how "unmapped test" stops meaning anything.
389
+ * @param {object} input - Root, contract, and the discovery result.
390
+ * @returns {object[]} Defects found.
391
+ */
392
+ function exclusionDefects({ root, contract, discovery }) {
393
+ const defects = [];
394
+ for (const [index, exclusion] of (contract.exclusions ?? []).entries()) {
395
+ const at = `coverage-map.exclusions[${index}]`;
396
+ const subject = typeof exclusion.file === "string" ? exclusion.file : at;
397
+ defects.push(...exclusionMetadata(exclusion, at, subject));
398
+ if (typeof exclusion.file !== "string" || exclusion.file.length === 0) {
399
+ continue;
400
+ }
401
+ defects.push(
402
+ ...exclusionStale({ root, exclusion, discovery, at, subject })
403
+ );
404
+ }
405
+ return defects;
406
+ }
407
+
408
+ /**
409
+ * The bookkeeping an exclusion owes whoever has to re-litigate it.
410
+ * @param {object} exclusion - Raw exclusion entry.
411
+ * @param {string} at - Location label.
412
+ * @param {string} subject - Finding subject.
413
+ * @returns {object[]} Defects found.
414
+ */
415
+ function exclusionMetadata(exclusion, at, subject) {
416
+ const defects = [];
417
+ if (typeof exclusion.file !== "string" || exclusion.file.length === 0) {
418
+ defects.push(defect("exclusion-metadata", `${at}: names no file`, subject));
419
+ }
420
+ if (typeof exclusion.reason !== "string" || exclusion.reason.trim() === "") {
421
+ defects.push(
422
+ defect(
423
+ "exclusion-metadata",
424
+ `${at}: has no reason; an exclusion with no stated reason is an undisclosed test with extra steps`,
425
+ subject
426
+ )
427
+ );
428
+ }
429
+ return defects;
430
+ }
431
+
432
+ /**
433
+ * The three ways an exclusion outlives what it excused.
434
+ * @param {object} input - Root, the exclusion, discovery, and labels.
435
+ * @returns {object[]} Defects found.
436
+ */
437
+ function exclusionStale({ root, exclusion, discovery, at, subject }) {
438
+ const resolved = resolveInsideRepo(root, exclusion.file);
439
+ if (!resolved.path) {
440
+ return [
441
+ defect(
442
+ "exclusion-stale",
443
+ `${at}: ${exclusion.file} — ${resolved.error}; retire the exclusion or restore the file`,
444
+ subject
445
+ ),
446
+ ];
447
+ }
448
+ if (!discovery.roots.some(entry => exclusion.file.startsWith(`${entry}/`))) {
449
+ return [
450
+ defect(
451
+ "exclusion-stale",
452
+ `${at}: ${exclusion.file} is covered by no configured discovery root (${discovery.roots.join(", ") || "none declared"}), so the exclusion suppresses nothing`,
453
+ subject
454
+ ),
455
+ ];
456
+ }
457
+ return discovery.specs.some(spec => accountsFor(exclusion, spec, true))
458
+ ? []
459
+ : [
460
+ defect(
461
+ "exclusion-stale",
462
+ `${at}: no discovered test in ${exclusion.file} matches ${JSON.stringify(exclusion.evidence)}`,
463
+ subject
464
+ ),
465
+ ];
466
+ }
467
+
468
+ /**
469
+ * Every disclosure defect: undeclared specs and dead exclusions.
470
+ * @param {object} input - Root, contract, and the discovery result.
471
+ * @returns {object[]} Defects found.
472
+ */
473
+ export function disclosureDefects({ root, contract, discovery }) {
474
+ return [
475
+ ...undisclosedDefects(discovery.specs, contract),
476
+ ...exclusionDefects({ root, contract, discovery }),
477
+ ];
478
+ }
479
+
480
+ /**
481
+ * The runners that declared a platform but no way to find their tests.
482
+ *
483
+ * Reported only where absence must fail, exactly like a missing coverage
484
+ * floor: a runner with no discovery block contributes nothing to the
485
+ * inventory, and a silent zero there is indistinguishable from a clean repo.
486
+ * @param {object} contract - Parsed coverage map.
487
+ * @param {object} discovery - The discovery result.
488
+ * @returns {object[]} Defects found.
489
+ */
490
+ export function missingDiscoveryDefects(contract, discovery) {
491
+ return Object.keys(contract.runnerPlatforms ?? {})
492
+ .filter(runner => !runner.startsWith("_"))
493
+ .filter(runner => !discovery.runners.includes(runner))
494
+ .sort()
495
+ .map(runner =>
496
+ defect(
497
+ "discovery-missing",
498
+ `enforced mode: runner ${runner} declares platforms but testDiscovery says nothing about where its tests live, so no undeclared test of that runner can ever be found`,
499
+ `coverage-map.testDiscovery.${runner}`
500
+ )
501
+ );
502
+ }
@@ -39,11 +39,17 @@ const ENVELOPE_MODULE_PATHS = Object.freeze([
39
39
  * Everything here is a contract-QUALITY defect: the manifest is legible and
40
40
  * the adoption state is coherent, the contract just is not clean yet. Codes
41
41
  * about adoption integrity itself (bootstrap-metadata, bootstrap-expired,
42
- * adoption-drift, config-*) are deliberately absent, so they always fail.
42
+ * adoption-drift, config-*, discovery-invalid) are deliberately absent, so
43
+ * they always fail: a malformed discovery block would silently switch off the
44
+ * only check that can see an undeclared test, exactly as a quoted coverage
45
+ * floor silently switches off the ratchet.
43
46
  */
44
47
  export const WARNABLE_DEFECT_CODES = Object.freeze([
45
48
  "baseline",
49
+ "discovery-missing",
46
50
  "empty-contract",
51
+ "exclusion-metadata",
52
+ "exclusion-stale",
47
53
  "execution-results",
48
54
  "floor-missing",
49
55
  "floor-ratchet",
@@ -61,6 +67,7 @@ export const WARNABLE_DEFECT_CODES = Object.freeze([
61
67
  "scenario-platform",
62
68
  "scenario-provenance",
63
69
  "scenario-steps",
70
+ "spec-undisclosed",
64
71
  "tracker-missing",
65
72
  "tracker-orphan",
66
73
  "waiver-duplicate",
@@ -205,6 +212,8 @@ export function buildSummary({ adoptionState, report, defects, filesWritten }) {
205
212
  traceabilityPercentage: report.traceability.overall.percentage,
206
213
  executionEvidenceSupplied: report.execution.supplied,
207
214
  mappedTests: report.execution.mappedTests,
215
+ testsDiscovered: report.testInventory.discovered,
216
+ testsUndisclosed: report.testInventory.undisclosed.length,
208
217
  ...executionCounts(report.execution),
209
218
  waivedObligations: report.waived.count,
210
219
  floorOk: report.floor.ok,
@@ -111,6 +111,47 @@ function floorTable(report) {
111
111
  return `| Platform | Committed floor | Current | Status |\n|---|---:|---:|---|\n${rows}`;
112
112
  }
113
113
 
114
+ /**
115
+ * Render the discovered-test inventory: what the roots hold, and what of it
116
+ * the contract never mentions.
117
+ *
118
+ * The undisclosed list is printed in full rather than counted. A count is a
119
+ * number somebody can grow accustomed to; a named file with a named test is a
120
+ * thing somebody has to either map or excuse.
121
+ * @param {object} report - The coverage report.
122
+ * @returns {string} Markdown section body.
123
+ */
124
+ function inventorySection(report) {
125
+ const inventory = report.testInventory;
126
+ if (inventory.runners.length === 0) {
127
+ return "**No `testDiscovery` roots are declared**, so no test file was walked and an undeclared test cannot be found here. In enforced mode this is a `discovery-missing` defect.";
128
+ }
129
+ const rows =
130
+ inventory.undisclosed
131
+ .map(
132
+ item =>
133
+ `| ${cell(item.runner)} | \`${cell(item.file)}\` | ${item.evidence === null ? "*(file has no title)*" : cell(item.evidence)} |`
134
+ )
135
+ .join("\n") || "| — | — | None |";
136
+ return `${inventory.discovered} tests discovered under ${inventory.roots.map(root => `\`${cell(root)}\``).join(", ")}; ${inventory.disclosed} are named by a mapping or an exclusion. ${inventory.dynamicTitles} carry a computed (template-literal) title, taken verbatim from the source — a runner result cannot be joined to those by title.\n\n| Runner | File | Test not named by the contract |\n|---|---|---|\n${rows}`;
137
+ }
138
+
139
+ /**
140
+ * Render the exclusion ledger: tests deliberately aligned to no behavior.
141
+ * @param {object} report - The coverage report.
142
+ * @returns {string} Markdown table.
143
+ */
144
+ function exclusionTable(report) {
145
+ const rows =
146
+ report.testInventory.exclusions
147
+ .map(
148
+ entry =>
149
+ `| \`${cell(entry.file)}\` | ${entry.evidence === null ? "*(whole file)*" : cell(entry.evidence)} | ${cell(entry.reason ?? "**unstated**")} |`
150
+ )
151
+ .join("\n") || "| — | — | None |";
152
+ return `| File | Test | Reason it aligns to no product behavior |\n|---|---|---|\n${rows}`;
153
+ }
154
+
114
155
  /**
115
156
  * Group the remaining gaps by feature.
116
157
  * @param {object} report - The coverage report.
@@ -186,6 +227,18 @@ ${report.waived.note}
186
227
 
187
228
  ${waiverTable(report)}
188
229
 
230
+ ## Discovered tests the contract does not mention
231
+
232
+ ${report.testInventory.note}
233
+
234
+ ${inventorySection(report)}
235
+
236
+ ## Tests deliberately aligned to no behavior
237
+
238
+ Every row here is a standing claim that a real test proves nothing about product behavior. An exclusion whose file is gone, whose title was renamed, or that no discovery root covers is a defect, not a permanent excuse.
239
+
240
+ ${exclusionTable(report)}
241
+
189
242
  ## Traceability to work items
190
243
 
191
244
  ${report.trackers.scenariosWithTag} of ${report.scenarios.declared} scenarios carry a tracker tag; ${report.trackers.scenariosWithoutTag} do not. Tags are validated for syntax and against this repo's declared trackers; the gate never contacts a tracker, so an unreachable issue can never block a merge.
@@ -21,6 +21,50 @@ import {
21
21
  runnersByPlatform,
22
22
  trackerUrl,
23
23
  } from "./contract.mjs";
24
+ import { isDisclosed } from "./discover.mjs";
25
+
26
+ /** An empty discovery result, for callers that do not walk the tree. */
27
+ const NO_DISCOVERY = Object.freeze({ specs: [], runners: [], roots: [] });
28
+
29
+ /**
30
+ * Summarize what walking the project's declared roots actually found.
31
+ *
32
+ * Traceability answers "is every declared behavior automated". This answers
33
+ * the other direction — "is every automated test accounted for" — and the two
34
+ * are not the same question: a repo can be at 100% traceability while carrying
35
+ * end-to-end tests nobody has ever mapped to a behavior.
36
+ * @param {object} discovery - The discovery result.
37
+ * @param {object} contract - Parsed coverage map.
38
+ * @returns {object} The report's `testInventory` block.
39
+ */
40
+ function buildTestInventory(discovery, contract) {
41
+ const undisclosed = discovery.specs.filter(
42
+ spec => !isDisclosed(spec, contract)
43
+ );
44
+ return {
45
+ note: "Tests found by walking the roots declared in testDiscovery. A discovered test must be named by a mapping or by an exclusion carrying a reason; anything else is an undisclosed test, not a clean repo.",
46
+ runners: discovery.runners,
47
+ roots: discovery.roots,
48
+ discovered: discovery.specs.length,
49
+ disclosed: discovery.specs.length - undisclosed.length,
50
+ dynamicTitles: discovery.specs.filter(spec => spec.dynamic).length,
51
+ undisclosed: undisclosed.map(spec => ({
52
+ runner: spec.runner,
53
+ platforms: spec.platforms,
54
+ file: spec.file,
55
+ evidence: spec.evidence,
56
+ })),
57
+ exclusions: (contract.exclusions ?? [])
58
+ .map(exclusion => ({
59
+ file: exclusion.file ?? null,
60
+ evidence: exclusion.evidence ?? null,
61
+ reason: exclusion.reason ?? null,
62
+ }))
63
+ .sort((a, b) =>
64
+ `${a.file}${a.evidence}`.localeCompare(`${b.file}${b.evidence}`)
65
+ ),
66
+ };
67
+ }
24
68
 
25
69
  /**
26
70
  * Summarize a subset of obligations against the covered set.
@@ -310,6 +354,7 @@ export function buildReport({
310
354
  runs,
311
355
  platforms,
312
356
  unresolved = new Set(),
357
+ discovery = NO_DISCOVERY,
313
358
  }) {
314
359
  const platformRunners = runnersByPlatform(contract.runnerPlatforms);
315
360
  const declared = declaredObligations(scenarios, platformRunners);
@@ -340,6 +385,7 @@ export function buildReport({
340
385
  byRunner: byRunnerSummary(contract, obligations, coveredKeys),
341
386
  },
342
387
  execution: buildExecution(contract.mappings ?? [], runs),
388
+ testInventory: buildTestInventory(discovery, contract),
343
389
  waived: waivedSummary(contract, scenarios, declared, waivedKeys),
344
390
  floor: evaluateFloor(contract, byPlatform, platforms),
345
391
  trackers: buildTrackerIndex(scenarios, contract.trackers),