@sun-asterisk/sungen 3.3.0-alpha.1 → 3.3.0-alpha.10

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 (463) hide show
  1. package/dist/capabilities/discover.d.ts +4 -0
  2. package/dist/capabilities/discover.d.ts.map +1 -1
  3. package/dist/capabilities/discover.js +16 -2
  4. package/dist/capabilities/discover.js.map +1 -1
  5. package/dist/cli/case-triage-hint.d.ts +14 -0
  6. package/dist/cli/case-triage-hint.d.ts.map +1 -0
  7. package/dist/cli/case-triage-hint.js +23 -0
  8. package/dist/cli/case-triage-hint.js.map +1 -0
  9. package/dist/cli/commands/audit.js +1 -1
  10. package/dist/cli/commands/audit.js.map +1 -1
  11. package/dist/cli/commands/contract-derive-command.d.ts +4 -2
  12. package/dist/cli/commands/contract-derive-command.d.ts.map +1 -1
  13. package/dist/cli/commands/contract-derive-command.js +19 -5
  14. package/dist/cli/commands/contract-derive-command.js.map +1 -1
  15. package/dist/cli/commands/contract.d.ts +3 -0
  16. package/dist/cli/commands/contract.d.ts.map +1 -1
  17. package/dist/cli/commands/contract.js +40 -7
  18. package/dist/cli/commands/contract.js.map +1 -1
  19. package/dist/cli/commands/delivery.d.ts.map +1 -1
  20. package/dist/cli/commands/delivery.js +59 -18
  21. package/dist/cli/commands/delivery.js.map +1 -1
  22. package/dist/cli/commands/gate.d.ts.map +1 -1
  23. package/dist/cli/commands/gate.js +2 -1
  24. package/dist/cli/commands/gate.js.map +1 -1
  25. package/dist/cli/commands/generate.d.ts.map +1 -1
  26. package/dist/cli/commands/generate.js +14 -2
  27. package/dist/cli/commands/generate.js.map +1 -1
  28. package/dist/cli/commands/journey.d.ts.map +1 -1
  29. package/dist/cli/commands/journey.js +6 -4
  30. package/dist/cli/commands/journey.js.map +1 -1
  31. package/dist/cli/commands/next.d.ts +7 -2
  32. package/dist/cli/commands/next.d.ts.map +1 -1
  33. package/dist/cli/commands/next.js +41 -10
  34. package/dist/cli/commands/next.js.map +1 -1
  35. package/dist/cli/commands/triage.d.ts +2 -1
  36. package/dist/cli/commands/triage.d.ts.map +1 -1
  37. package/dist/cli/commands/triage.js +45 -3
  38. package/dist/cli/commands/triage.js.map +1 -1
  39. package/dist/cli/commands/update.d.ts.map +1 -1
  40. package/dist/cli/commands/update.js +6 -2
  41. package/dist/cli/commands/update.js.map +1 -1
  42. package/dist/cli/index.js +5 -1
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/cli/json-error-on-exit.d.ts +34 -0
  45. package/dist/cli/json-error-on-exit.d.ts.map +1 -0
  46. package/dist/cli/json-error-on-exit.js +63 -0
  47. package/dist/cli/json-error-on-exit.js.map +1 -0
  48. package/dist/cli/project-sungen-invocation.d.ts +13 -0
  49. package/dist/cli/project-sungen-invocation.d.ts.map +1 -0
  50. package/dist/cli/project-sungen-invocation.js +95 -0
  51. package/dist/cli/project-sungen-invocation.js.map +1 -0
  52. package/dist/cli/run-command-hints.d.ts +9 -0
  53. package/dist/cli/run-command-hints.d.ts.map +1 -0
  54. package/dist/cli/run-command-hints.js +84 -0
  55. package/dist/cli/run-command-hints.js.map +1 -0
  56. package/dist/cli/waiting-for-you-block.d.ts +18 -0
  57. package/dist/cli/waiting-for-you-block.d.ts.map +1 -0
  58. package/dist/cli/waiting-for-you-block.js +22 -0
  59. package/dist/cli/waiting-for-you-block.js.map +1 -0
  60. package/dist/contract/accept-opaque-steps.d.ts +3 -0
  61. package/dist/contract/accept-opaque-steps.d.ts.map +1 -0
  62. package/dist/contract/accept-opaque-steps.js +114 -0
  63. package/dist/contract/accept-opaque-steps.js.map +1 -0
  64. package/dist/contract/approve-contract.d.ts +8 -1
  65. package/dist/contract/approve-contract.d.ts.map +1 -1
  66. package/dist/contract/approve-contract.js +6 -0
  67. package/dist/contract/approve-contract.js.map +1 -1
  68. package/dist/contract/build-contract-verdict.js +2 -2
  69. package/dist/contract/build-contract-verdict.js.map +1 -1
  70. package/dist/contract/build-trace-conformance.d.ts +5 -1
  71. package/dist/contract/build-trace-conformance.d.ts.map +1 -1
  72. package/dist/contract/build-trace-conformance.js +12 -14
  73. package/dist/contract/build-trace-conformance.js.map +1 -1
  74. package/dist/contract/classify-contract-change.d.ts +3 -2
  75. package/dist/contract/classify-contract-change.d.ts.map +1 -1
  76. package/dist/contract/classify-contract-change.js.map +1 -1
  77. package/dist/contract/classify-trace-conformance.d.ts +18 -3
  78. package/dist/contract/classify-trace-conformance.d.ts.map +1 -1
  79. package/dist/contract/classify-trace-conformance.js +64 -17
  80. package/dist/contract/classify-trace-conformance.js.map +1 -1
  81. package/dist/contract/contract-schema.d.ts +21 -0
  82. package/dist/contract/contract-schema.d.ts.map +1 -1
  83. package/dist/contract/contract-schema.js +25 -3
  84. package/dist/contract/contract-schema.js.map +1 -1
  85. package/dist/contract/contracts-lock-file.d.ts.map +1 -1
  86. package/dist/contract/contracts-lock-file.js +1 -0
  87. package/dist/contract/contracts-lock-file.js.map +1 -1
  88. package/dist/contract/derive-free-feature.d.ts +19 -0
  89. package/dist/contract/derive-free-feature.d.ts.map +1 -1
  90. package/dist/contract/derive-free-feature.js +40 -2
  91. package/dist/contract/derive-free-feature.js.map +1 -1
  92. package/dist/contract/project-contract-blockers.d.ts.map +1 -1
  93. package/dist/contract/project-contract-blockers.js +4 -2
  94. package/dist/contract/project-contract-blockers.js.map +1 -1
  95. package/dist/contract/record-contract-approval.d.ts +16 -1
  96. package/dist/contract/record-contract-approval.d.ts.map +1 -1
  97. package/dist/contract/record-contract-approval.js +29 -1
  98. package/dist/contract/record-contract-approval.js.map +1 -1
  99. package/dist/contract/render-free-gherkin.d.ts +2 -0
  100. package/dist/contract/render-free-gherkin.d.ts.map +1 -1
  101. package/dist/contract/render-free-gherkin.js +6 -0
  102. package/dist/contract/render-free-gherkin.js.map +1 -1
  103. package/dist/contract/resolve-unit-trace-identities.d.ts +12 -0
  104. package/dist/contract/resolve-unit-trace-identities.d.ts.map +1 -1
  105. package/dist/contract/resolve-unit-trace-identities.js +33 -49
  106. package/dist/contract/resolve-unit-trace-identities.js.map +1 -1
  107. package/dist/contract/trace/collect-trace-evidence.d.ts +16 -5
  108. package/dist/contract/trace/collect-trace-evidence.d.ts.map +1 -1
  109. package/dist/contract/trace/collect-trace-evidence.js +101 -43
  110. package/dist/contract/trace/collect-trace-evidence.js.map +1 -1
  111. package/dist/contract/trace/read-file-once.d.ts +20 -0
  112. package/dist/contract/trace/read-file-once.d.ts.map +1 -0
  113. package/dist/contract/trace/read-file-once.js +135 -0
  114. package/dist/contract/trace/read-file-once.js.map +1 -0
  115. package/dist/contract/trace/scenario-id-claims.d.ts +11 -0
  116. package/dist/contract/trace/scenario-id-claims.d.ts.map +1 -0
  117. package/dist/contract/trace/scenario-id-claims.js +89 -0
  118. package/dist/contract/trace/scenario-id-claims.js.map +1 -0
  119. package/dist/contract/trace/select-final-attempt.d.ts +4 -2
  120. package/dist/contract/trace/select-final-attempt.d.ts.map +1 -1
  121. package/dist/contract/trace/select-final-attempt.js +7 -4
  122. package/dist/contract/trace/select-final-attempt.js.map +1 -1
  123. package/dist/contract/trace/test-step-join.d.ts +3 -3
  124. package/dist/contract/trace/test-step-join.d.ts.map +1 -1
  125. package/dist/contract/trace/test-step-join.js +2 -2
  126. package/dist/contract/trace/test-step-join.js.map +1 -1
  127. package/dist/contract/trace/trace-binding.d.ts +3 -2
  128. package/dist/contract/trace/trace-binding.d.ts.map +1 -1
  129. package/dist/contract/trace/trace-binding.js +6 -3
  130. package/dist/contract/trace/trace-binding.js.map +1 -1
  131. package/dist/contract/trace/trace-file-reader.d.ts +14 -2
  132. package/dist/contract/trace/trace-file-reader.d.ts.map +1 -1
  133. package/dist/contract/trace/trace-file-reader.js +27 -45
  134. package/dist/contract/trace/trace-file-reader.js.map +1 -1
  135. package/dist/contract/trace/trace-freshness.d.ts +27 -0
  136. package/dist/contract/trace/trace-freshness.d.ts.map +1 -0
  137. package/dist/contract/trace/trace-freshness.js +146 -0
  138. package/dist/contract/trace/trace-freshness.js.map +1 -0
  139. package/dist/contract/trace/trace-identity-resolution.d.ts +2 -0
  140. package/dist/contract/trace/trace-identity-resolution.d.ts.map +1 -1
  141. package/dist/contract/trace/trace-identity-resolution.js +6 -3
  142. package/dist/contract/trace/trace-identity-resolution.js.map +1 -1
  143. package/dist/contract/trace/trace-run-record.d.ts +8 -3
  144. package/dist/contract/trace/trace-run-record.d.ts.map +1 -1
  145. package/dist/contract/trace/trace-run-record.js +42 -22
  146. package/dist/contract/trace/trace-run-record.js.map +1 -1
  147. package/dist/contract/trace/trace-steps.js +1 -1
  148. package/dist/contract/trace/trace-steps.js.map +1 -1
  149. package/dist/contract/trace/trace-zip-limits.d.ts +5 -1
  150. package/dist/contract/trace/trace-zip-limits.d.ts.map +1 -1
  151. package/dist/contract/trace/trace-zip-limits.js +6 -2
  152. package/dist/contract/trace/trace-zip-limits.js.map +1 -1
  153. package/dist/contract/verify-approval-decisions.d.ts.map +1 -1
  154. package/dist/contract/verify-approval-decisions.js +9 -4
  155. package/dist/contract/verify-approval-decisions.js.map +1 -1
  156. package/dist/decisions/confirmed-decision-commands.d.ts +11 -0
  157. package/dist/decisions/confirmed-decision-commands.d.ts.map +1 -1
  158. package/dist/decisions/confirmed-decision-commands.js +21 -3
  159. package/dist/decisions/confirmed-decision-commands.js.map +1 -1
  160. package/dist/decisions/decision-schema.d.ts +2 -1
  161. package/dist/decisions/decision-schema.d.ts.map +1 -1
  162. package/dist/decisions/decision-schema.js +2 -1
  163. package/dist/decisions/decision-schema.js.map +1 -1
  164. package/dist/decisions/git-head-trust.d.ts +12 -0
  165. package/dist/decisions/git-head-trust.d.ts.map +1 -1
  166. package/dist/decisions/git-head-trust.js +52 -0
  167. package/dist/decisions/git-head-trust.js.map +1 -1
  168. package/dist/decisions/human-confirmation.d.ts +2 -0
  169. package/dist/decisions/human-confirmation.d.ts.map +1 -1
  170. package/dist/decisions/human-confirmation.js +6 -0
  171. package/dist/decisions/human-confirmation.js.map +1 -1
  172. package/dist/decisions/legacy-waiver-migration.d.ts +8 -1
  173. package/dist/decisions/legacy-waiver-migration.d.ts.map +1 -1
  174. package/dist/decisions/legacy-waiver-migration.js +13 -4
  175. package/dist/decisions/legacy-waiver-migration.js.map +1 -1
  176. package/dist/exporters/csv-exporter.d.ts +11 -2
  177. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  178. package/dist/exporters/csv-exporter.js +8 -4
  179. package/dist/exporters/csv-exporter.js.map +1 -1
  180. package/dist/exporters/matrix/export.d.ts +6 -1
  181. package/dist/exporters/matrix/export.d.ts.map +1 -1
  182. package/dist/exporters/matrix/export.js +11 -4
  183. package/dist/exporters/matrix/export.js.map +1 -1
  184. package/dist/exporters/matrix/xlsx-cells.js +1 -1
  185. package/dist/exporters/matrix/xlsx-cells.js.map +1 -1
  186. package/dist/exporters/xlsx-exporter.d.ts +3 -2
  187. package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
  188. package/dist/exporters/xlsx-exporter.js +4 -4
  189. package/dist/exporters/xlsx-exporter.js.map +1 -1
  190. package/dist/graph/board-human-actions.d.ts +5 -0
  191. package/dist/graph/board-human-actions.d.ts.map +1 -0
  192. package/dist/graph/board-human-actions.js +83 -0
  193. package/dist/graph/board-human-actions.js.map +1 -0
  194. package/dist/graph/evaluate-graph.d.ts +21 -1
  195. package/dist/graph/evaluate-graph.d.ts.map +1 -1
  196. package/dist/graph/evaluate-graph.js +37 -9
  197. package/dist/graph/evaluate-graph.js.map +1 -1
  198. package/dist/graph/human-action.d.ts +21 -0
  199. package/dist/graph/human-action.d.ts.map +1 -0
  200. package/dist/graph/human-action.js +3 -0
  201. package/dist/graph/human-action.js.map +1 -0
  202. package/dist/graph/manual-triage-decisions.d.ts +19 -0
  203. package/dist/graph/manual-triage-decisions.d.ts.map +1 -0
  204. package/dist/graph/manual-triage-decisions.js +53 -0
  205. package/dist/graph/manual-triage-decisions.js.map +1 -0
  206. package/dist/graph/node-decl.d.ts +16 -0
  207. package/dist/graph/node-decl.d.ts.map +1 -1
  208. package/dist/graph/node-decl.js +4 -1
  209. package/dist/graph/node-decl.js.map +1 -1
  210. package/dist/graph/nodes/test-case-contract-nodes.d.ts +3 -1
  211. package/dist/graph/nodes/test-case-contract-nodes.d.ts.map +1 -1
  212. package/dist/graph/nodes/test-case-contract-nodes.js +22 -6
  213. package/dist/graph/nodes/test-case-contract-nodes.js.map +1 -1
  214. package/dist/graph/nodes/test-case-nodes.d.ts +3 -0
  215. package/dist/graph/nodes/test-case-nodes.d.ts.map +1 -1
  216. package/dist/graph/nodes/test-case-nodes.js +33 -4
  217. package/dist/graph/nodes/test-case-nodes.js.map +1 -1
  218. package/dist/graph/nodes/unit-audit-nodes.d.ts.map +1 -1
  219. package/dist/graph/nodes/unit-audit-nodes.js +1 -0
  220. package/dist/graph/nodes/unit-audit-nodes.js.map +1 -1
  221. package/dist/graph/nodes/unit-contract-node.d.ts +2 -1
  222. package/dist/graph/nodes/unit-contract-node.d.ts.map +1 -1
  223. package/dist/graph/nodes/unit-contract-node.js +16 -1
  224. package/dist/graph/nodes/unit-contract-node.js.map +1 -1
  225. package/dist/graph/nodes/unit-design-nodes.d.ts.map +1 -1
  226. package/dist/graph/nodes/unit-design-nodes.js +2 -1
  227. package/dist/graph/nodes/unit-design-nodes.js.map +1 -1
  228. package/dist/graph/test-case-results.d.ts +1 -0
  229. package/dist/graph/test-case-results.d.ts.map +1 -1
  230. package/dist/graph/test-case-results.js +27 -3
  231. package/dist/graph/test-case-results.js.map +1 -1
  232. package/dist/graph/test-case-satisfied.d.ts +5 -4
  233. package/dist/graph/test-case-satisfied.d.ts.map +1 -1
  234. package/dist/graph/test-case-satisfied.js +4 -0
  235. package/dist/graph/test-case-satisfied.js.map +1 -1
  236. package/dist/graph/test-case-state-shape.d.ts +7 -1
  237. package/dist/graph/test-case-state-shape.d.ts.map +1 -1
  238. package/dist/graph/unit-artifact-probes.d.ts.map +1 -1
  239. package/dist/graph/unit-artifact-probes.js +3 -1
  240. package/dist/graph/unit-artifact-probes.js.map +1 -1
  241. package/dist/graph/unit-results-freshness.d.ts +29 -0
  242. package/dist/graph/unit-results-freshness.d.ts.map +1 -0
  243. package/dist/graph/unit-results-freshness.js +213 -0
  244. package/dist/graph/unit-results-freshness.js.map +1 -0
  245. package/dist/harness/audit.d.ts +3 -2
  246. package/dist/harness/audit.d.ts.map +1 -1
  247. package/dist/harness/audit.js +4 -17
  248. package/dist/harness/audit.js.map +1 -1
  249. package/dist/harness/catalog/gate-rules.yaml +3 -3
  250. package/dist/harness/defect-draft.d.ts.map +1 -1
  251. package/dist/harness/defect-draft.js +12 -2
  252. package/dist/harness/defect-draft.js.map +1 -1
  253. package/dist/harness/defects.d.ts +14 -0
  254. package/dist/harness/defects.d.ts.map +1 -1
  255. package/dist/harness/defects.js +14 -10
  256. package/dist/harness/defects.js.map +1 -1
  257. package/dist/harness/flow-check.d.ts +9 -0
  258. package/dist/harness/flow-check.d.ts.map +1 -1
  259. package/dist/harness/flow-check.js +11 -0
  260. package/dist/harness/flow-check.js.map +1 -1
  261. package/dist/harness/gates/audit-gate-verdict.d.ts.map +1 -1
  262. package/dist/harness/gates/audit-gate-verdict.js +2 -1
  263. package/dist/harness/gates/audit-gate-verdict.js.map +1 -1
  264. package/dist/harness/gates/audit-input-hash.d.ts +2 -0
  265. package/dist/harness/gates/audit-input-hash.d.ts.map +1 -1
  266. package/dist/harness/gates/audit-input-hash.js +30 -2
  267. package/dist/harness/gates/audit-input-hash.js.map +1 -1
  268. package/dist/harness/gates/audit-report-verification.d.ts +17 -6
  269. package/dist/harness/gates/audit-report-verification.d.ts.map +1 -1
  270. package/dist/harness/gates/audit-report-verification.js +55 -21
  271. package/dist/harness/gates/audit-report-verification.js.map +1 -1
  272. package/dist/harness/gates/delivery-checklist.d.ts +3 -0
  273. package/dist/harness/gates/delivery-checklist.d.ts.map +1 -1
  274. package/dist/harness/gates/delivery-checklist.js +39 -6
  275. package/dist/harness/gates/delivery-checklist.js.map +1 -1
  276. package/dist/harness/gates/delivery-decision-gate.d.ts +4 -0
  277. package/dist/harness/gates/delivery-decision-gate.d.ts.map +1 -1
  278. package/dist/harness/gates/delivery-decision-gate.js +26 -1
  279. package/dist/harness/gates/delivery-decision-gate.js.map +1 -1
  280. package/dist/harness/gates/delivery-decision-step.d.ts +4 -1
  281. package/dist/harness/gates/delivery-decision-step.d.ts.map +1 -1
  282. package/dist/harness/gates/delivery-decision-step.js +17 -2
  283. package/dist/harness/gates/delivery-decision-step.js.map +1 -1
  284. package/dist/harness/gates/in-run-decision-check.d.ts.map +1 -1
  285. package/dist/harness/gates/in-run-decision-check.js +40 -12
  286. package/dist/harness/gates/in-run-decision-check.js.map +1 -1
  287. package/dist/harness/gates/migrated-waiver-import-bounds.d.ts +13 -0
  288. package/dist/harness/gates/migrated-waiver-import-bounds.d.ts.map +1 -0
  289. package/dist/harness/gates/migrated-waiver-import-bounds.js +141 -0
  290. package/dist/harness/gates/migrated-waiver-import-bounds.js.map +1 -0
  291. package/dist/harness/gates/migrated-waiver-review.d.ts +14 -4
  292. package/dist/harness/gates/migrated-waiver-review.d.ts.map +1 -1
  293. package/dist/harness/gates/migrated-waiver-review.js +78 -80
  294. package/dist/harness/gates/migrated-waiver-review.js.map +1 -1
  295. package/dist/harness/journey.d.ts +40 -0
  296. package/dist/harness/journey.d.ts.map +1 -1
  297. package/dist/harness/journey.js +155 -22
  298. package/dist/harness/journey.js.map +1 -1
  299. package/dist/harness/next-step.d.ts +19 -0
  300. package/dist/harness/next-step.d.ts.map +1 -1
  301. package/dist/harness/next-step.js +21 -0
  302. package/dist/harness/next-step.js.map +1 -1
  303. package/dist/harness/provenance.d.ts +29 -0
  304. package/dist/harness/provenance.d.ts.map +1 -1
  305. package/dist/harness/provenance.js +109 -9
  306. package/dist/harness/provenance.js.map +1 -1
  307. package/dist/harness/triage-manual-classification.d.ts +32 -0
  308. package/dist/harness/triage-manual-classification.d.ts.map +1 -0
  309. package/dist/harness/triage-manual-classification.js +56 -0
  310. package/dist/harness/triage-manual-classification.js.map +1 -0
  311. package/dist/harness/unit-paths.d.ts +8 -0
  312. package/dist/harness/unit-paths.d.ts.map +1 -1
  313. package/dist/harness/unit-paths.js +23 -1
  314. package/dist/harness/unit-paths.js.map +1 -1
  315. package/dist/hooks/bash-read-only-segment.d.ts +9 -0
  316. package/dist/hooks/bash-read-only-segment.d.ts.map +1 -0
  317. package/dist/hooks/bash-read-only-segment.js +168 -0
  318. package/dist/hooks/bash-read-only-segment.js.map +1 -0
  319. package/dist/hooks/bash-write-targets.d.ts +12 -2
  320. package/dist/hooks/bash-write-targets.d.ts.map +1 -1
  321. package/dist/hooks/bash-write-targets.js +122 -29
  322. package/dist/hooks/bash-write-targets.js.map +1 -1
  323. package/dist/hooks/guard-protected-paths.d.ts +4 -0
  324. package/dist/hooks/guard-protected-paths.d.ts.map +1 -1
  325. package/dist/hooks/guard-protected-paths.js +13 -3
  326. package/dist/hooks/guard-protected-paths.js.map +1 -1
  327. package/dist/hooks/guard-rules.d.ts +0 -17
  328. package/dist/hooks/guard-rules.d.ts.map +1 -1
  329. package/dist/hooks/guard-rules.js +79 -10
  330. package/dist/hooks/guard-rules.js.map +1 -1
  331. package/dist/hooks/guard-tty-spoof-rule.d.ts +3 -1
  332. package/dist/hooks/guard-tty-spoof-rule.d.ts.map +1 -1
  333. package/dist/hooks/guard-tty-spoof-rule.js +3 -2
  334. package/dist/hooks/guard-tty-spoof-rule.js.map +1 -1
  335. package/dist/hooks/hand-back-core.d.ts +9 -4
  336. package/dist/hooks/hand-back-core.d.ts.map +1 -1
  337. package/dist/hooks/hand-back-core.js +24 -11
  338. package/dist/hooks/hand-back-core.js.map +1 -1
  339. package/dist/hooks/session-start-unit-state-hook.d.ts +2 -1
  340. package/dist/hooks/session-start-unit-state-hook.d.ts.map +1 -1
  341. package/dist/hooks/session-start-unit-state-hook.js +9 -4
  342. package/dist/hooks/session-start-unit-state-hook.js.map +1 -1
  343. package/dist/hooks/stop-next-step-hook.d.ts.map +1 -1
  344. package/dist/hooks/stop-next-step-hook.js +1 -0
  345. package/dist/hooks/stop-next-step-hook.js.map +1 -1
  346. package/dist/orchestrator/assets-drift.d.ts +5 -0
  347. package/dist/orchestrator/assets-drift.d.ts.map +1 -1
  348. package/dist/orchestrator/assets-drift.js +12 -3
  349. package/dist/orchestrator/assets-drift.js.map +1 -1
  350. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  351. package/dist/orchestrator/project-initializer.js +1 -0
  352. package/dist/orchestrator/project-initializer.js.map +1 -1
  353. package/dist/orchestrator/templates/ai-src/commands/create-test.md +6 -3
  354. package/dist/orchestrator/templates/ai-src/commands/delivery.md +2 -0
  355. package/dist/orchestrator/templates/ai-src/commands/review.md +2 -0
  356. package/dist/orchestrator/templates/ai-src/commands/run-test.md +1 -1
  357. package/dist/orchestrator/templates/ai-src/partials/waiting-for-you.claude.md +11 -0
  358. package/dist/orchestrator/templates/ai-src/partials/waiting-for-you.codex.md +11 -0
  359. package/dist/orchestrator/templates/ai-src/partials/waiting-for-you.copilot.md +11 -0
  360. package/package.json +3 -3
  361. package/src/capabilities/discover.ts +15 -2
  362. package/src/cli/case-triage-hint.ts +31 -0
  363. package/src/cli/commands/audit.ts +1 -1
  364. package/src/cli/commands/contract-derive-command.ts +20 -8
  365. package/src/cli/commands/contract.ts +41 -7
  366. package/src/cli/commands/delivery.ts +56 -18
  367. package/src/cli/commands/gate.ts +2 -1
  368. package/src/cli/commands/generate.ts +13 -2
  369. package/src/cli/commands/journey.ts +8 -6
  370. package/src/cli/commands/next.ts +41 -12
  371. package/src/cli/commands/triage.ts +41 -3
  372. package/src/cli/commands/update.ts +6 -2
  373. package/src/cli/index.ts +5 -1
  374. package/src/cli/json-error-on-exit.ts +72 -0
  375. package/src/cli/project-sungen-invocation.ts +59 -0
  376. package/src/cli/run-command-hints.ts +47 -0
  377. package/src/cli/waiting-for-you-block.ts +27 -0
  378. package/src/contract/accept-opaque-steps.ts +114 -0
  379. package/src/contract/approve-contract.ts +10 -3
  380. package/src/contract/build-contract-verdict.ts +2 -2
  381. package/src/contract/build-trace-conformance.ts +11 -8
  382. package/src/contract/classify-contract-change.ts +3 -3
  383. package/src/contract/classify-trace-conformance.ts +76 -22
  384. package/src/contract/contract-schema.ts +31 -2
  385. package/src/contract/contracts-lock-file.ts +1 -0
  386. package/src/contract/derive-free-feature.ts +46 -3
  387. package/src/contract/project-contract-blockers.ts +4 -2
  388. package/src/contract/record-contract-approval.ts +41 -1
  389. package/src/contract/render-free-gherkin.ts +5 -0
  390. package/src/contract/resolve-unit-trace-identities.ts +35 -12
  391. package/src/contract/trace/collect-trace-evidence.ts +105 -36
  392. package/src/contract/trace/read-file-once.ts +95 -0
  393. package/src/contract/trace/scenario-id-claims.ts +60 -0
  394. package/src/contract/trace/select-final-attempt.ts +12 -5
  395. package/src/contract/trace/test-step-join.ts +6 -5
  396. package/src/contract/trace/trace-binding.ts +7 -4
  397. package/src/contract/trace/trace-file-reader.ts +34 -13
  398. package/src/contract/trace/trace-freshness.ts +110 -0
  399. package/src/contract/trace/trace-identity-resolution.ts +7 -4
  400. package/src/contract/trace/trace-run-record.ts +37 -15
  401. package/src/contract/trace/trace-steps.ts +1 -1
  402. package/src/contract/trace/trace-zip-limits.ts +6 -2
  403. package/src/contract/verify-approval-decisions.ts +9 -5
  404. package/src/decisions/confirmed-decision-commands.ts +25 -4
  405. package/src/decisions/decision-schema.ts +2 -1
  406. package/src/decisions/git-head-trust.ts +44 -0
  407. package/src/decisions/human-confirmation.ts +5 -0
  408. package/src/decisions/legacy-waiver-migration.ts +14 -3
  409. package/src/exporters/csv-exporter.ts +12 -4
  410. package/src/exporters/matrix/export.ts +11 -4
  411. package/src/exporters/matrix/xlsx-cells.ts +1 -1
  412. package/src/exporters/xlsx-exporter.ts +5 -4
  413. package/src/graph/board-human-actions.ts +80 -0
  414. package/src/graph/evaluate-graph.ts +45 -10
  415. package/src/graph/human-action.ts +33 -0
  416. package/src/graph/manual-triage-decisions.ts +55 -0
  417. package/src/graph/node-decl.ts +18 -0
  418. package/src/graph/nodes/test-case-contract-nodes.ts +28 -7
  419. package/src/graph/nodes/test-case-nodes.ts +37 -5
  420. package/src/graph/nodes/unit-audit-nodes.ts +2 -1
  421. package/src/graph/nodes/unit-contract-node.ts +20 -2
  422. package/src/graph/nodes/unit-design-nodes.ts +2 -1
  423. package/src/graph/test-case-results.ts +25 -4
  424. package/src/graph/test-case-satisfied.ts +7 -4
  425. package/src/graph/test-case-state-shape.ts +7 -2
  426. package/src/graph/unit-artifact-probes.ts +3 -1
  427. package/src/graph/unit-results-freshness.ts +165 -0
  428. package/src/harness/audit.ts +4 -13
  429. package/src/harness/catalog/gate-rules.yaml +3 -3
  430. package/src/harness/defect-draft.ts +13 -2
  431. package/src/harness/defects.ts +22 -9
  432. package/src/harness/flow-check.ts +11 -0
  433. package/src/harness/gates/audit-gate-verdict.ts +2 -1
  434. package/src/harness/gates/audit-input-hash.ts +27 -3
  435. package/src/harness/gates/audit-report-verification.ts +57 -24
  436. package/src/harness/gates/delivery-checklist.ts +39 -7
  437. package/src/harness/gates/delivery-decision-gate.ts +29 -1
  438. package/src/harness/gates/delivery-decision-step.ts +19 -3
  439. package/src/harness/gates/in-run-decision-check.ts +38 -13
  440. package/src/harness/gates/migrated-waiver-import-bounds.ts +103 -0
  441. package/src/harness/gates/migrated-waiver-review.ts +90 -37
  442. package/src/harness/journey.ts +174 -21
  443. package/src/harness/next-step.ts +34 -1
  444. package/src/harness/provenance.ts +99 -11
  445. package/src/harness/triage-manual-classification.ts +76 -0
  446. package/src/harness/unit-paths.ts +19 -0
  447. package/src/hooks/bash-read-only-segment.ts +130 -0
  448. package/src/hooks/bash-write-targets.ts +109 -23
  449. package/src/hooks/guard-protected-paths.ts +13 -3
  450. package/src/hooks/guard-rules.ts +37 -18
  451. package/src/hooks/guard-tty-spoof-rule.ts +6 -3
  452. package/src/hooks/hand-back-core.ts +24 -12
  453. package/src/hooks/session-start-unit-state-hook.ts +9 -4
  454. package/src/hooks/stop-next-step-hook.ts +1 -0
  455. package/src/orchestrator/assets-drift.ts +12 -5
  456. package/src/orchestrator/project-initializer.ts +1 -0
  457. package/src/orchestrator/templates/ai-src/commands/create-test.md +6 -3
  458. package/src/orchestrator/templates/ai-src/commands/delivery.md +2 -0
  459. package/src/orchestrator/templates/ai-src/commands/review.md +2 -0
  460. package/src/orchestrator/templates/ai-src/commands/run-test.md +1 -1
  461. package/src/orchestrator/templates/ai-src/partials/waiting-for-you.claude.md +11 -0
  462. package/src/orchestrator/templates/ai-src/partials/waiting-for-you.codex.md +11 -0
  463. package/src/orchestrator/templates/ai-src/partials/waiting-for-you.copilot.md +11 -0
@@ -30,7 +30,7 @@ function render(r: AuditReport): void {
30
30
  L('');
31
31
  L(` Quality score (business-weighted): ${r.score.overall}/10 [Viewpoint Gate: ${r.gateStatus}]`);
32
32
  L(` Intent: focus=${r.intent.focus} · depth threshold ${r.depth.threshold.toFixed(2)} (${r.intent.source})`);
33
- L(` Provenance: sungen ${r.provenance.sungenVersion} · catalog ${r.provenance.catalogHash} (same here on every machine → same score)`);
33
+ L(` Provenance: sungen ${r.provenance.sungenVersion} · catalog ${r.provenance.catalogHash} · rules ${r.provenance.rulesHash} (same rules on every machine → same score)`);
34
34
  // Print the axes the score was ACTUALLY built from — a fixed five-line block omitted a flow's
35
35
  // `flowCoverage`, `specFR` and `atomicLedger` while showing `balance`, which a contract flow
36
36
  // does not score on.
@@ -2,14 +2,17 @@
2
2
  * `sungen contract derive --screen <u> [--grep <re>] [--json]` (DOS-14.8, phase 08): free tests'
3
3
  * traces → `features/<u>.free.feature`, every scenario `@free @proposed`, deterministic, no LLM.
4
4
  *
5
- * exit 0 written, unchanged, nothing to derive, or a mobile project (not supported yet)
6
- * exit 1 refused (a secret or a machine path would have been written — nothing was), or an error
5
+ * exit 0 written, unchanged, every scenario already approved, or a mobile project (not supported yet)
6
+ * exit 1 nothing to derive (no admissible trace — each skipped test says why, #932), refused (a
7
+ * secret or a machine path would have been written — nothing was), refused because two
8
+ * recorded tests claim one id (#962 — both named, nothing written), or an error
7
9
  */
8
10
  import type { Command } from 'commander';
9
11
  import * as path from 'path';
10
- import { deriveFreeFeature, DeriveResult } from '../../contract/derive-free-feature';
12
+ import { deriveFreeFeature, DeriveResult, nothingDerived } from '../../contract/derive-free-feature';
11
13
  import { registeredTraceResolver } from '../../contract/resolve-unit-trace-identities';
12
14
  import { resolveContractUnit } from '../../contract/contract-units';
15
+ import { withProjectInvocation } from '../project-sungen-invocation';
13
16
 
14
17
  function print(r: DeriveResult, unit: string): void {
15
18
  const L = console.log;
@@ -18,20 +21,29 @@ function print(r: DeriveResult, unit: string): void {
18
21
  L(`━━━ Contract derive: ${unit} — free tests' traces → ${rel} ━━━`);
19
22
  L('');
20
23
  if (r.note) L(` ⚠ ${r.note}`);
24
+ for (const w of r.warnings ?? []) L(` ⚠ ${w}`);
21
25
  for (const s of r.scenarios) L(` + ${s.title} (${s.steps} steps${s.low ? `, ${s.low} low identity` : ''}${s.opaque ? `, ${s.opaque} opaque` : ''})`);
22
26
  for (const s of r.skipped) L(` - ${s.id} skipped: ${s.reason}`);
23
- if (r.refused?.length) {
27
+ if (r.duplicateIds?.length) {
28
+ L(" ✗ Refused — nothing was written. One ID names one test: keeping one would silently drop the other's contract.");
29
+ for (const d of r.duplicateIds) {
30
+ L(` ${d.tests.length} recorded tests claim ${d.id}:`);
31
+ for (const t of d.tests) L(` ${t}`);
32
+ if (d.staleRecords?.length) L(` If that test is gone, delete ${d.staleRecords.join(', ')} — an older run's record (free tests now record under test-results-free/).`);
33
+ }
34
+ L(" Give each test its own ID (its title's leading ID, or its @tag), run the free tests again with trace on, then derive.");
35
+ } else if (r.refused?.length) {
24
36
  L(` ✗ Refused — nothing was written. ${r.refused.length} likely secret(s) / machine path(s):`);
25
37
  for (const h of r.refused) L(` ${h.where} — ${h.reason}`);
26
38
  L(' Move the value into test-data (or an env overlay) and use it from there, then derive again.');
27
39
  } else if (r.written) {
28
40
  L(` ✓ Wrote ${rel}. Review the steps (they ARE the contract), then QA approves on a terminal:`);
29
- L(` sungen contract approve --screen ${unit}`);
41
+ L(` ${withProjectInvocation(`sungen contract approve --screen ${unit}`, process.cwd())}`);
30
42
  L(' After approval, re-run the free tests (trace: "on") — a trace older than the contract proves nothing.');
31
43
  } else if (r.scenarios.length) {
32
44
  L(` = ${rel} is already up to date.`);
33
- } else if (!r.note) {
34
- L(` = Nothing to derive: no admissible trace of a test for ${unit} (try --grep).`);
45
+ } else if (nothingDerived(r)) {
46
+ L(` ✗ Nothing to derive: no admissible trace of a test for ${unit}${r.skipped.length ? ' (each skipped test above says why)' : ' (try --grep)'}.`);
35
47
  }
36
48
  L('');
37
49
  }
@@ -51,7 +63,7 @@ export function registerDeriveCommand(contract: Command): void {
51
63
  projectRoot: process.cwd(), unit, grep: options.grep ? String(options.grep) : undefined, resolver: registeredTraceResolver() ?? null,
52
64
  });
53
65
  if (options.json) console.log(JSON.stringify(r, null, 2)); else print(r, unit.name);
54
- process.exit(r.refused?.length ? 1 : 0);
66
+ process.exit(r.refused?.length || r.duplicateIds?.length || nothingDerived(r) ? 1 : 0);
55
67
  } catch (error) {
56
68
  console.error('Error:', error instanceof Error ? error.message : error);
57
69
  process.exit(1);
@@ -4,6 +4,9 @@
4
4
  * approve --screen <u> [--scenario <id>] [--reason <text>]
5
5
  * a human types a confirmation on a TTY; writes contracts.lock.yaml + a contract-approval
6
6
  * decision. No TTY → refused with the exact command (exit 1).
7
+ * approve --screen <u> --scenario <id> --accept-opaque [--reason <text>]
8
+ * the same human act for the reviewed opaque trace steps of one approved free / impl:code
9
+ * scenario (#923): bound to each step as it reads now (accept-opaque-steps.ts).
7
10
  * check --screen <u> [--trace] [--json]
8
11
  * rebuilds the verdict from .feature + lock + git HEAD + recorded traces (never from a report),
9
12
  * writes .sungen/reports/<slug>-contract.json as output (secret-scanned first). Exit 2 on a hard
@@ -17,6 +20,7 @@ import * as fs from 'fs';
17
20
  import * as path from 'path';
18
21
  import { approveContract } from '../../contract/approve-contract';
19
22
  import { buildContractVerdict, ContractVerdict } from '../../contract/build-contract-verdict';
23
+ import { LOW_IDENTITY_FINDING } from '../../contract/classify-trace-conformance';
20
24
  import { contractReportPath, writeContractReport } from '../../contract/contract-report-file';
21
25
  import { HARD_CLASSES, REVIEW_CLASSES } from '../../contract/contract-schema';
22
26
  import { processIO } from '../../decisions/human-confirmation';
@@ -24,6 +28,7 @@ import { resolveContractUnit } from '../../contract/contract-units';
24
28
  import { secretScanHits } from '../../contract/field-semantic-redaction';
25
29
  import { resolveUnitTraceIdentities } from '../../contract/resolve-unit-trace-identities';
26
30
  import { registerDeriveCommand } from './contract-derive-command';
31
+ import { withProjectInvocationInText } from '../project-sungen-invocation';
27
32
 
28
33
  const MARK: Record<string, string> = {
29
34
  match: '✓', strengthened: '↑', proposed: '+', 'hash-changed': '⚖', 'oracle-weakened': '✗', 'target-changed': '✗', 'assertion-lost': '✗',
@@ -31,10 +36,17 @@ const MARK: Record<string, string> = {
31
36
  };
32
37
  /** `contract check` judges traces with a generous deadline (the guard path keeps ~3 s). */
33
38
  const CHECK_TRACE_BUDGET_MS = 120_000;
34
- const UNPROVEN_HINT = " → free / impl:code tests need trace: 'on' and the sungen run-record reporter, then a fresh run (see `sungen contract derive`).";
39
+ const UNPROVEN_HINT = " → free / impl:code tests need their own config (outputDir 'test-results-free', trace: 'on', the sungen run-record reporter), then a fresh run (see `sungen contract derive`).";
35
40
 
36
- function printVerdict(v: ContractVerdict, reportFile: string): void {
37
- const L = console.log;
41
+ /** The command that recompiles a unit's spec — `sungen generate --screen|--flow|--api <unit>`. */
42
+ function generateCommand(unit: { name: string; dir: string }): string {
43
+ const kind = path.relative(path.join(process.cwd(), 'qa'), unit.dir).split(path.sep)[0];
44
+ return `sungen generate --${kind === 'flows' ? 'flow' : kind === 'api' ? 'api' : 'screen'} ${unit.name}`;
45
+ }
46
+
47
+ function printVerdict(v: ContractVerdict, reportFile: string, generate: string): void {
48
+ // The approve / --accept-opaque / generate lines as this project runs sungen (#963).
49
+ const L = (line: string): void => console.log(withProjectInvocationInText(line, process.cwd()));
38
50
  const head = v.verdict === 'pass' ? '✓ PASS' : v.verdict === 'review'
39
51
  ? '⚖ REVIEW — changed, not weaker. Exit 0 here, but review blocks `sungen delivery` until QA re-approves' : '✗ FAIL';
40
52
  L('');
@@ -64,7 +76,19 @@ function printVerdict(v: ContractVerdict, reportFile: string): void {
64
76
  : onlyUnproven ? " → Re-run the free / impl:code tests with trace: 'on', then check again."
65
77
  : ' → Restore the approved checks in the .feature — or, if the change is intended, QA approves it on a terminal:');
66
78
  }
67
- if (v.verdict !== 'pass' && !v.uncommitted.length && !onlyUnproven) L(` sungen contract approve --screen ${v.unit}`);
79
+ // An opaque step is not cleared by re-approving (#923): QA reviews it, then accepts it as it reads now.
80
+ const acceptable = blocking.filter((s) => s.class === 'opaque' && s.trace?.opaque?.some((o) => !o.accepted)
81
+ && !s.findings.some((f) => f.detail.startsWith(LOW_IDENTITY_FINDING)));
82
+ if (v.verdict !== 'pass' && !v.uncommitted.length && !onlyUnproven && acceptable.length < blocking.length) L(` sungen contract approve --screen ${v.unit}`);
83
+ for (const s of acceptable) {
84
+ L(` → ${s.id}: QA reviews each opaque step above (verbatim, file:line); to accept them as they read now, on a terminal:`);
85
+ L(` sungen contract approve --screen ${v.unit} --scenario ${s.id} --accept-opaque`);
86
+ }
87
+ // A weakened compiled scenario also leaves its spec out of step (#943): name the command that fixes the drift.
88
+ // (A free / impl:code scenario carries a trace summary; its test is hand-written, never regenerated.)
89
+ if (v.verdict === 'fail' && !v.uncommitted.length && v.scenarios.some((s) => !s.trace && HARD_CLASSES.has(s.class))) {
90
+ L(` → After fixing the scenario, run \`${generate}\`: until the compiled spec is regenerated, \`sungen script-check\` reports drift.`);
91
+ }
68
92
  L(` Report: ${path.relative(process.cwd(), reportFile)}`);
69
93
  L('');
70
94
  }
@@ -85,15 +109,25 @@ export function registerContractCommand(program: Command): void {
85
109
  .option('-s, --screen <name>', 'Screen / flow / api unit name')
86
110
  .option('--scenario <id>', 'Approve one scenario only (its ID, e.g. VP-LOGIN-001)')
87
111
  .option('--reason <text>', 'Why (recorded in the contract-approval decision)')
112
+ .option('--accept-opaque', 'With --scenario: accept the reviewed opaque trace step(s) of one approved free / impl:code scenario')
88
113
  .action(async (options) => {
89
114
  try {
90
115
  const unit = unitOrThrow(options.screen);
91
- const r = await approveContract({ projectRoot: process.cwd(), unit: unit.name, scenario: options.scenario, reason: options.reason, io: processIO() });
116
+ const r = await approveContract({
117
+ projectRoot: process.cwd(), unit: unit.name, scenario: options.scenario, reason: options.reason, acceptOpaque: options.acceptOpaque === true, io: processIO(),
118
+ });
92
119
  if (r.status === 'nothing-to-approve') { console.log(`\n= Nothing to approve: every chosen scenario of ${unit.name} already matches its approved lock entry.\n`); return; }
120
+ if (r.status === 'nothing-to-accept') { console.log(`\n= Nothing to accept: every opaque step of ${options.scenario} is already accepted as it reads now.\n`); return; }
121
+ if (r.status === 'accepted') {
122
+ console.log(`\n✓ Accepted ${r.accepted?.length ?? 0} opaque step(s) of ${options.scenario} → ${path.relative(process.cwd(), r.lockPath)} (decision ${r.decisionId})`);
123
+ console.log(' Commit the lock and the decision through review, then re-run the free test with its trace: a trace older than the lock proves nothing.\n');
124
+ return;
125
+ }
93
126
  console.log(`\n✓ Approved ${r.approved.length} scenario(s)${r.removed.length ? `, removed ${r.removed.length}` : ''} → ${path.relative(process.cwd(), r.lockPath)} (decision ${r.decisionId})`);
94
127
  console.log(' Commit the lock and the decision through review: until they are in git HEAD, `sungen contract check` reports contract.lock-uncommitted.\n');
95
128
  } catch (error) {
96
- console.error('Error:', error instanceof Error ? error.message : error);
129
+ // A refusal names the command a human runs instead: as this project runs sungen (#963).
130
+ console.error('Error:', error instanceof Error ? withProjectInvocationInText(error.message, process.cwd()) : error);
97
131
  process.exit(1);
98
132
  }
99
133
  });
@@ -116,7 +150,7 @@ export function registerContractCommand(program: Command): void {
116
150
  if (hits.length) throw new Error(`Refused to write the contract report — likely secret(s): ${hits.map((h) => `${h.where} ${h.reason}`).join('; ')}`);
117
151
  const file = writeContractReport(process.cwd(), verdict);
118
152
  if (options.json) console.log(JSON.stringify(verdict, null, 2));
119
- else printVerdict(verdict, file);
153
+ else printVerdict(verdict, file, generateCommand(unit));
120
154
  process.exit(verdict.exitCode);
121
155
  } catch (error) {
122
156
  // Unreadable = not run (DOS-14.0): an earlier report must not outlive the inputs it judged.
@@ -17,6 +17,7 @@ import { mergeFeatureAndSpec } from '../../exporters/scenario-merger';
17
17
  import {
18
18
  buildTestCaseRows,
19
19
  buildSummary,
20
+ deliverableBasename,
20
21
  renderCsv,
21
22
  writeCsv,
22
23
  } from '../../exporters/csv-exporter';
@@ -41,11 +42,16 @@ import { MatrixFinding } from '../../exporters/matrix/types';
41
42
  import { applyManualCarry, ManualCarryReport, PriorManualResults, readPriorManualResults } from '../../exporters/matrix/carry-manual-results';
42
43
  import { formatTestOutcome, resultTotals } from '../../exporters/matrix/result-totals';
43
44
  import { readCapabilities } from '../../harness/capability';
45
+ import { splitFeatureBlocks } from '../../contract/free-feature-blocks';
44
46
  import { processIO } from '../../decisions/human-confirmation';
45
47
  import { DeliveryDecisionGate } from '../../harness/gates/delivery-decision-gate';
46
48
  import { runDeliveryDecisionStep } from '../../harness/gates/delivery-decision-step';
47
49
  import { deliverableGateOverrides, InRunAcceptance } from '../../harness/gates/gate-override-report';
48
50
  import { ChecklistUnit, renderChecklist, unitChecklist } from '../../harness/gates/delivery-checklist';
51
+ import { withProjectInvocationInText } from '../project-sungen-invocation';
52
+ import { freeRunCommand, freeRunHasResults, generatedSubdir, plainRunCommand } from '../run-command-hints';
53
+ import { isHandWritten } from '../../harness/parse';
54
+ import { FREE_RUN_DIR } from '../../contract/trace/trace-run-record';
49
55
 
50
56
  const COLOR = {
51
57
  reset: '\x1b[0m',
@@ -101,14 +107,30 @@ function makeTarget(screen: string, featureBaseName: string, kind: UnitKind): De
101
107
  }
102
108
 
103
109
  /**
104
- * List all `.feature` files inside a screen/flow/api unit as separate targets.
105
- * Returns empty array when the directory has no features yet.
110
+ * A derived `*.free.feature` none of whose scenarios QA has approved yet (every one still `@proposed`):
111
+ * a proposal, not a delivery unit — counted as one, it ended in "Not exported" (#939). Approving a
112
+ * scenario drops its tag (`contract approve`), and from then on the file is a unit like any other.
113
+ */
114
+ function proposalOnly(featureFile: string): boolean {
115
+ if (!featureFile.endsWith('.free.feature')) return false;
116
+ try {
117
+ // No scenario block at all (empty, or not Gherkin) is not a proposal: it stays listed and the preflight names it.
118
+ const blocks = splitFeatureBlocks(fs.readFileSync(featureFile, 'utf8'));
119
+ return blocks.length > 0 && blocks.every((b) => b.proposed);
120
+ } catch {
121
+ return false; // unreadable: listed, so the preflight names the problem
122
+ }
123
+ }
124
+
125
+ /**
126
+ * List all `.feature` files inside a screen/flow/api unit as separate targets — a proposal-only
127
+ * free feature excepted. Returns empty array when the directory has no features yet.
106
128
  */
107
129
  function listFeatureTargets(cwd: string, screen: string, kind: UnitKind): DeliveryTarget[] {
108
130
  const featuresDir = path.join(cwd, 'qa', qaParent(kind), screen, 'features');
109
131
  if (!fs.existsSync(featuresDir)) return [];
110
132
  return fs.readdirSync(featuresDir)
111
- .filter((f) => f.endsWith('.feature'))
133
+ .filter((f) => f.endsWith('.feature') && !proposalOnly(path.join(featuresDir, f)))
112
134
  .map((f) => makeTarget(screen, f.slice(0, -'.feature'.length), kind))
113
135
  .sort((a, b) => a.featureBaseName.localeCompare(b.featureBaseName));
114
136
  }
@@ -169,8 +191,7 @@ function qaDir(cwd: string, target: DeliveryTarget): string {
169
191
  // dashboard snapshot builder loads the identical contract (shared + unit merge).
170
192
 
171
193
  function generatedDir(cwd: string, target: DeliveryTarget): string {
172
- const sub = target.kind === 'flow' ? path.join('flows', target.screen) : target.kind === 'api' ? path.join('api', target.screen) : target.screen;
173
- return path.join(cwd, 'specs', 'generated', sub);
194
+ return path.join(cwd, generatedSubdir(target.kind, target.screen));
174
195
  }
175
196
 
176
197
  // ----------------------------------------------------------------------------
@@ -233,6 +254,9 @@ export function runPreflight(cwd: string, target: DeliveryTarget): PreflightChec
233
254
  const selectorsFile = path.join(base, 'selectors', `${target.featureBaseName}.yaml`);
234
255
  const specFile = path.join(genBase, `${target.featureBaseName}.spec.ts`);
235
256
  const resultsFile = resolveResultsPath(cwd, target);
257
+ // A derived `*.free.feature` is implemented by hand-written tests (DOS-14.8): no selectors, never compiled,
258
+ // run with the project's free-test config into its own outputDir — so its hints are that run's (#964).
259
+ const free = isHandWritten([], featureFile);
236
260
 
237
261
  const featureOk = checkFeatureReal(featureFile);
238
262
  // test-data is only REQUIRED when the feature actually references a
@@ -243,9 +267,9 @@ export function runPreflight(cwd: string, target: DeliveryTarget): PreflightChec
243
267
  ? checkTestDataHasVars(testDataFile)
244
268
  : true;
245
269
  // API units have no DOM → no selectors; the catalog (apis.yaml) is the contract. N/A, not missing.
246
- const selectorsOk = target.kind === 'api' ? true : checkSelectorsHasEntries(selectorsFile, target.featureBaseName);
247
- const specOk = fs.existsSync(specFile);
248
- const resultsOk = resultsFile !== null;
270
+ const selectorsOk = target.kind === 'api' || free ? true : checkSelectorsHasEntries(selectorsFile, target.featureBaseName);
271
+ const specOk = free || fs.existsSync(specFile);
272
+ const resultsOk = free ? freeRunHasResults(cwd) : resultsFile !== null;
249
273
 
250
274
  const label = target.isFlow ? `flow/${target.featureBaseName}` : target.featureBaseName;
251
275
  const missing: string[] = [];
@@ -267,13 +291,15 @@ export function runPreflight(cwd: string, target: DeliveryTarget): PreflightChec
267
291
  missing.push(`compiled .spec.ts missing (optional — needed to run): ${path.relative(cwd, specFile)}`);
268
292
  suggestions.push(`sungen generate --${target.kind === 'flow' ? 'flow' : target.kind === 'api' ? 'api' : 'screen'} ${target.screen}`);
269
293
  }
270
- if (!resultsOk) {
294
+ if (!resultsOk && free) {
295
+ missing.push(`free-test results missing (optional): ${FREE_RUN_DIR}/`);
296
+ suggestions.push(freeRunCommand(cwd));
297
+ } else if (!resultsOk) {
271
298
  const env = process.env.SUNGEN_ENV;
272
299
  const envSuffix = env ? `.${env}` : '';
273
300
  missing.push(`test-result${envSuffix}.json missing (optional)`);
274
301
  const genRel = path.relative(cwd, genBase);
275
- const envPrefix = env ? `SUNGEN_ENV=${env} ` : '';
276
- suggestions.push(`${envPrefix}PLAYWRIGHT_JSON_OUTPUT_NAME=${genRel}/${target.featureBaseName}-test-result.json npx playwright test ${genRel}/${target.featureBaseName}.spec.ts`);
302
+ suggestions.push(plainRunCommand(genRel, target.featureBaseName, env));
277
303
  }
278
304
 
279
305
  return {
@@ -711,6 +737,8 @@ async function exportMatrixTarget(
711
737
  }
712
738
 
713
739
  const { model, mapMissing, mapErrors } = loadMatrixModel(paths);
740
+ // An audit that is untrusted or FAILs never yields an official deliverable: --preview renders a DRAFT (#935).
741
+ if (model && decisions?.auditDraft(qaDir(cwd, target))) model.manifest.approvalState = 'draft';
714
742
 
715
743
  if (mapMissing) {
716
744
  log(`${COLOR.yellow}${paths.label}: no delivery map${COLOR.reset} (${path.relative(cwd, paths.mapFile)})`);
@@ -729,11 +757,13 @@ async function exportMatrixTarget(
729
757
  // counted, so this line, the workbook's totals and the summary table all agree (#891). A file
730
758
  // that cannot be read is reported where it matters — when it would be overwritten.
731
759
  const format = opts.format ?? 'xlsx';
760
+ // A draft (DRAFT banner) is written to its own `…-testcases.DRAFT.<ext>`, never onto the official file (#965).
761
+ const draft = model.manifest.approvalState === 'draft';
732
762
  let prior: PriorManualResults | null = null;
733
763
  let priorError: Error | null = null;
734
764
  try { prior = await readPriorManualResults(deliverableXlsxPath(cwd, paths.unit)); }
735
- // A CSV-only export overwrites no workbook, so an unreadable one only means "nothing to carry".
736
- catch (e) { if (format !== 'csv') priorError = e as Error; }
765
+ // A CSV-only export or a draft overwrites no official workbook, so an unreadable one only means "nothing to carry".
766
+ catch (e) { if (format !== 'csv' && !draft) priorError = e as Error; }
737
767
  applyManualCarry(model, prior);
738
768
  model.gateOverrides = deliverableGateOverrides(decisions?.overridesFor(qaDir(cwd, target)) ?? [], hasReview(model.findings), decisions?.acceptedInRun);
739
769
 
@@ -783,6 +813,9 @@ async function exportMatrixTarget(
783
813
  const failed = model.items.reduce((a, i) => a + i.resultCounts.failed, 0);
784
814
  const written = [xlsxPath, csvPath].filter((p): p is string => !!p).map((p) => path.relative(cwd, p));
785
815
  for (const rel of written) log(` ${COLOR.green}→ ${rel}${COLOR.reset}`);
816
+ if (draft) {
817
+ log(` ${COLOR.yellow}draft only — not a deliverable:${COLOR.reset} it carries the DRAFT banner, and the official ${deliverableBasename(paths.unit)} file was neither written nor changed.`);
818
+ }
786
819
  if (manualCarry) printManualCarry(manualCarry);
787
820
  return {
788
821
  label: paths.label,
@@ -910,7 +943,8 @@ async function printDeliveryChecklist(cwd: string, targets: DeliveryTarget[], ac
910
943
  const rows: Array<{ label: string; boxes: Awaited<ReturnType<typeof unitChecklist>> }> = [];
911
944
  for (const u of units.values()) rows.push({ label: u.label, boxes: await unitChecklist(cwd, u, accepted) });
912
945
  log('');
913
- for (const line of renderChecklist(rows)) log(line);
946
+ // Each box's fix command as this project runs sungen (#963).
947
+ for (const line of renderChecklist(rows)) log(withProjectInvocationInText(line, cwd));
914
948
  }
915
949
 
916
950
  // ----------------------------------------------------------------------------
@@ -929,7 +963,7 @@ export function registerDeliveryCommand(program: Command): void {
929
963
  .option('--full', 'Alias of --legacy')
930
964
  .option('--check', 'Matrix gates only — validate the delivery map, write nothing')
931
965
  .option('--approve [ids]', 'Approve proposed groups (all, or a comma-separated id list) + stamp fingerprints')
932
- .option('--preview', 'Render the matrix despite review-required findings (DRAFT watermark)')
966
+ .option('--preview', 'Render the matrix despite review-required findings — a DRAFT (watermarked) written to <unit>-testcases.DRAFT.*, never onto the official file')
933
967
  .option('--format <format>', 'Matrix output format: xlsx (default) | csv | both', 'xlsx')
934
968
  .option('--accept-in-run', 'Deliver despite gate-overrides/waivers/approvals recorded during the run (decision.in-run) — a human confirms on a terminal')
935
969
  .action(async (names: string[], options: {
@@ -1023,7 +1057,8 @@ export function registerDeliveryCommand(program: Command): void {
1023
1057
  }
1024
1058
  if (options.approve === undefined) {
1025
1059
  const gate = await runDeliveryDecisionStep({
1026
- projectRoot: cwd, acceptInRun: !!options.acceptInRun, io: processIO(), log,
1060
+ // The accept-in-run command and its refusal as this project runs sungen (#963).
1061
+ projectRoot: cwd, acceptInRun: !!options.acceptInRun, io: processIO(), log: (line) => log(withProjectInvocationInText(line, cwd)),
1027
1062
  // --preview / --check soften only the matrix export; --legacy has no draft mode, so it always needs one.
1028
1063
  requireTrustedAudit: !!(options.legacy || options.full) || (!options.preview && !options.check),
1029
1064
  refs: toExport.map((t) => ({ label: t.isFlow ? `flow/${t.featureBaseName}` : t.featureBaseName, unitName: t.screen, unitDir: qaDir(cwd, t), resultsPath: resolveResultsPath(cwd, t) })),
@@ -1095,9 +1130,12 @@ export function registerDeliveryCommand(program: Command): void {
1095
1130
  // an approval are the normal state of a live project. Preflight skips are
1096
1131
  // not counted here: reaching this point in an explicit scope means
1097
1132
  // --continue-on-missing was passed, which IS the opt-in to tolerate them.
1098
- const failed = explicitScope
1133
+ // --check reports like its checklist: an audit box that does not hold is a non-zero exit (#935).
1134
+ const auditFails = (decisions?.untrustedAudits.length ?? 0) + (decisions?.failingAudits.length ?? 0);
1135
+ if (options.check && auditFails) log(`${COLOR.red}✗ --check: ${auditFails} unit(s) whose audit does not pass — see the audit box above.${COLOR.reset}`);
1136
+ const failed = (explicitScope
1099
1137
  ? notExported.length > 0
1100
- : matrixSummaries.length === 0;
1138
+ : matrixSummaries.length === 0) || (!!options.check && auditFails > 0);
1101
1139
  if (failed) process.exit(1);
1102
1140
  return;
1103
1141
  }
@@ -3,6 +3,7 @@ import * as path from 'path';
3
3
  import * as fs from 'fs';
4
4
  import { runGate, renderGate, GatePhase } from '../../harness/journey';
5
5
  import { findUnitDir } from '../resolve-unit';
6
+ import { withProjectInvocationInText } from '../project-sungen-invocation';
6
7
 
7
8
 
8
9
  const PHASES: GatePhase[] = ['create', 'run', 'deliver'];
@@ -24,7 +25,7 @@ export function registerGateCommand(program: Command): void {
24
25
 
25
26
  const verdict = runGate(process.cwd(), name, phase);
26
27
  if (options.json) console.log(JSON.stringify(verdict, null, 2));
27
- else console.log(renderGate(verdict));
28
+ else console.log(withProjectInvocationInText(renderGate(verdict), process.cwd())); // waive / sign-off hints as the project runs sungen (#963)
28
29
 
29
30
  // Exit 2 on HALT — usable in CI / the orchestration loop to block the next phase.
30
31
  process.exit(verdict.status === 'halt' ? 2 : 0);
@@ -8,6 +8,7 @@ import { capabilityRegistry } from '../../capabilities/registry';
8
8
  import { discoverAndRegisterCapabilities } from '../../capabilities/discover';
9
9
  import { readCapabilities, writeCapabilities, driverMeta, loadDriverCatalog } from '../../harness/capability';
10
10
  import { isHandWritten } from '../../harness/parse';
11
+ import { generatedSubdir, plainRunCommand } from '../run-command-hints';
11
12
 
12
13
  /**
13
14
  * Find .feature files recursively in a directory
@@ -255,8 +256,18 @@ export function registerGenerateCommand(program: Command): void {
255
256
  if (shown.length < ddWarnings.length) console.log(` … and ${ddWarnings.length - shown.length} more (run with -v to list all)`);
256
257
  }
257
258
 
258
- const nextStep = platform === 'mobile' ? 'npm run test:mobile' : 'npx playwright test --ui';
259
- console.log(`Next step: ${nextStep}\n`);
259
+ // Never the interactive `--ui` (#939): an agent cannot drive it, and run-test is what records the results.
260
+ const unit = screenName || flowName || apiName;
261
+ const nextStep = platform === 'mobile' ? 'npm run test:mobile' : unit ? `/sungen:run-test ${unit}` : 'npx playwright test';
262
+ console.log(`Next step: ${nextStep}`);
263
+ // The plain run writes each feature's results where `sungen next` and `sungen delivery` read them (#964) —
264
+ // a bare `npx playwright test` wrote them to the config's default file, which neither reads.
265
+ if (platform !== 'mobile' && unit) {
266
+ const genRel = generatedSubdir(apiName ? 'api' : flowName ? 'flow' : 'screen', unit);
267
+ console.log(' or the plain run — it writes the results file `sungen next` and `sungen delivery` read:');
268
+ for (const f of featureFiles) console.log(` ${plainRunCommand(genRel, path.basename(f, '.feature'))}`);
269
+ }
270
+ console.log('');
260
271
  } catch (error) {
261
272
  console.error('Error:', error instanceof Error ? error.message : error);
262
273
  process.exit(1);
@@ -1,10 +1,11 @@
1
1
  import { Command } from 'commander';
2
2
  import * as path from 'path';
3
- import { runJourney, signoff, renderJourneyBoard, rollupJourney, renderRollupBoard, writeJourneyFileBestEffort } from '../../harness/journey';
3
+ import { runJourney, renderJourneyBoard, rollupJourney, renderRollupBoard, writeJourneyFileBestEffort } from '../../harness/journey';
4
4
  import { reportSlug } from '../../harness/unit-paths';
5
5
  import { findUnitDir } from '../resolve-unit';
6
- import { answerPendingMigration, decideWithConfirmation, DECIDE_KINDS, waiveWithConfirmation } from '../../decisions/confirmed-decision-commands';
6
+ import { answerPendingMigration, decideWithConfirmation, DECIDE_KINDS, signoffWithConfirmation, waiveWithConfirmation } from '../../decisions/confirmed-decision-commands';
7
7
  import { processIO } from '../../decisions/human-confirmation';
8
+ import { withProjectInvocationInText } from '../project-sungen-invocation';
8
9
 
9
10
  const collect = (value: string, previous: string[]): string[] => [...previous, value];
10
11
 
@@ -22,7 +23,7 @@ export function registerJourneyCommand(program: Command): void {
22
23
  .option('--payload <json>', 'Kind-specific fields as a JSON object, e.g. a gate-override\'s {"rule","severity","scope"} (with --decide)')
23
24
  .option('--visibility <v>', 'internal (default, never exported) | client (with --decide)')
24
25
  .option('--supersedes <id>', 'The decision this one replaces — the old entry is never edited (with --decide)')
25
- .option('--signoff', 'Sign off the review queue — the single human gate (allowed only when every other obligation is satisfied/waived)')
26
+ .option('--signoff', 'Sign off the review queue — the single human gate (allowed only when every other obligation is satisfied/waived); a human types a confirmation on a TTY')
26
27
  .option('--by <name>', 'Who is signing off (recorded with --signoff)')
27
28
  .option('--json', 'Output the raw JSON report')
28
29
  .action(async (options) => {
@@ -63,7 +64,7 @@ export function registerJourneyCommand(program: Command): void {
63
64
  const report = options.waive
64
65
  ? await waiveWithConfirmation({ projectRoot: process.cwd(), unit: name, obligation: options.waive, reason: options.reason || '', io })
65
66
  : options.signoff
66
- ? signoff(process.cwd(), name, options.by)
67
+ ? await signoffWithConfirmation({ projectRoot: process.cwd(), unit: name, by: options.by, io })
67
68
  : runJourney(process.cwd(), name);
68
69
 
69
70
  const outDir = path.join(process.cwd(), '.sungen', 'journey');
@@ -77,12 +78,13 @@ export function registerJourneyCommand(program: Command): void {
77
78
  console.log(JSON.stringify(report, null, 2));
78
79
  } else {
79
80
  console.log('');
80
- console.log(board);
81
+ console.log(withProjectInvocationInText(board, process.cwd()));
81
82
  console.log(` Board: ${written ? path.relative(process.cwd(), written) : '(not written — read-only checkout)'}`);
82
83
  console.log('');
83
84
  }
84
85
  } catch (error) {
85
- console.error('Error:', error instanceof Error ? error.message : error);
86
+ // A refusal names the command a human runs instead: as this project runs sungen (#963).
87
+ console.error('Error:', error instanceof Error ? withProjectInvocationInText(error.message, process.cwd()) : error);
86
88
  process.exit(1);
87
89
  }
88
90
  });
@@ -9,8 +9,13 @@
9
9
  * sungen next <unit> --case <id> one test case: its state, and its steps after the blocking
10
10
  * unit steps before them (OES-10.3)
11
11
  * --json the same, machine-readable; steps add nodeId, granularity,
12
- * testCase?, gates?; every older field is kept. stdout is the
13
- * payload alone (#660, #836)
12
+ * testCase?, gates?; the unit adds `results` (#942) and
13
+ * `humanActions: [{command, reason, kind, rendered}]` (#954) —
14
+ * `command` canonical, `rendered` as this project runs it (#963),
15
+ * and `humanActionsHeldBy[Command]` when a blocking step holds them (#966);
16
+ * every older field is kept. stdout is the payload alone (#660, #836)
17
+ * The text names the run the results come from and warns when they are older than the suite (#942),
18
+ * and ends with "Waiting for you": every pending human-only command, with why (#954).
14
19
  */
15
20
  import { Command } from 'commander';
16
21
  import * as fs from 'fs';
@@ -18,8 +23,13 @@ import * as path from 'path';
18
23
  import { CLI_GIT_TIMEOUT_MS, headSnapshot } from '../../contract/contract-git';
19
24
  import { testCaseStatusLabel } from '../../graph/test-case-satisfied';
20
25
  import { testCaseStates } from '../../graph/test-case-states';
26
+ import { describeResultsRun } from '../../graph/unit-results-freshness';
21
27
  import { deriveSteps, NextStep, readUnitState, UnitState } from '../../harness/next-step';
22
28
  import { resolveUnit, resolveUnitCandidates, AmbiguousUnitError } from '../resolve-unit';
29
+ import { withProjectInvocation, withProjectInvocationInText } from '../project-sungen-invocation';
30
+ import { waitingForYouLines } from '../waiting-for-you-block';
31
+ import { caseTriageLines } from '../case-triage-hint';
32
+ import { testCaseNeed } from '../../graph/test-case-satisfied';
23
33
 
24
34
  /** Every unit the project holds, so `sungen next` with no argument still answers. */
25
35
  function allUnits(cwd: string): Array<{ name: string; kind: UnitState['kind']; dir: string }> {
@@ -35,15 +45,25 @@ function allUnits(cwd: string): Array<{ name: string; kind: UnitState['kind']; d
35
45
  return out;
36
46
  }
37
47
 
38
- function renderSteps(steps: NextStep[]): void {
48
+ /** Steps as text, each `sungen …` command — the reason's too (#963) — as this project runs it (#939); `--json` keeps them canonical. */
49
+ function renderSteps(steps: NextStep[], cwd: string): void {
39
50
  for (const st of steps) {
40
51
  const mark = st.kind === 'blocked' ? '⛔' : st.kind === 'optional' ? '○' : st.kind === 'decide' ? '⚖' : '→';
41
- console.log(` ${mark} ${st.command}${st.kind === 'decide' ? ' (a QA decision — ask, do not run)' : ''}`);
42
- console.log(` ${st.because}`);
52
+ console.log(` ${mark} ${withProjectInvocationInText(st.command, cwd)}${st.kind === 'decide' ? ' (a QA decision — ask, do not run)' : ''}`);
53
+ console.log(` ${withProjectInvocationInText(st.because, cwd)}`);
43
54
  }
44
55
  }
45
56
 
46
- function render(s: UnitState): void {
57
+ /**
58
+ * `--json`: each human action keeps its canonical `command` and adds `rendered` — the line a human runs in
59
+ * this project (`npx sungen …` with a local install; an instruction unchanged), as the text shows it (#963).
60
+ */
61
+ function withRenderedHumanActions(s: UnitState, cwd: string): UnitState {
62
+ if (!s.humanActions) return s;
63
+ return { ...s, humanActions: s.humanActions.map((a) => ({ ...a, rendered: a.instruction ? a.command : withProjectInvocation(a.command, cwd) })) };
64
+ }
65
+
66
+ function render(s: UnitState, cwd: string): void {
47
67
  const L = console.log;
48
68
  L('');
49
69
  L(`━━━ Next steps: ${s.unit} (${s.kind}) ━━━`);
@@ -57,10 +77,16 @@ function render(s: UnitState): void {
57
77
  ].filter(Boolean);
58
78
  L(` state: ${held.length ? held.join(' · ') : '(nothing yet)'}`);
59
79
  if (s.audit) L(` audit: ${s.audit.overall}/10 [${s.audit.gateStatus}] · ${s.audit.findings.length} finding(s)`);
60
- else if (s.auditNotCounted) L(` audit: ${s.auditNotCounted.state === 'stale' ? 'stale' : 'not trusted'} — ${s.auditNotCounted.reason}`);
80
+ else if (s.auditNotCounted) L(` audit: ${s.auditNotCounted.state === 'stale' ? 'stale' : 'not trusted'} — ${withProjectInvocationInText(s.auditNotCounted.reason, cwd)}`);
81
+ if (s.results) {
82
+ L(` results: ${s.results.files.length} file${s.results.files.length === 1 ? '' : 's'}, ${describeResultsRun(s.results)}`);
83
+ if (s.results.warning) L(` ⚠ ${s.results.warning}`);
84
+ }
61
85
  L('');
62
- renderSteps(s.steps);
86
+ renderSteps(s.steps, cwd);
63
87
  L('');
88
+ const waiting = waitingForYouLines(s.humanActions, cwd, s.humanActionsHeldBy, s.humanActionsHeldByCommand);
89
+ if (waiting.length) { for (const line of waiting) L(line); L(''); }
64
90
  }
65
91
 
66
92
  /** `--case <id>`: the case's state and its frontier. Exits 1 when the case cannot be found. */
@@ -84,13 +110,15 @@ function caseView(s: UnitState, cwd: string, unitDir: string, id: string, json:
84
110
  L('');
85
111
  L(` test case: ${tc.title}${tc.unstableId ? ' (no VP-id — this id changes when the title does)' : ''}`);
86
112
  L(` where: ${tc.file}#L${tc.lines[0]}-L${tc.lines[1]}`);
87
- L(` status: ${testCaseStatusLabel(tc)} · result ${tc.result}${tc.mocked && tc.result === 'passed' ? ' (mocked)' : ''}${tc.stale ? ` · stale: ${tc.stale}` : ''}${tc.triage ? ` · triage ${tc.triage.class}` : ''}`);
113
+ L(` status: ${testCaseStatusLabel(tc)} · result ${tc.result}${tc.mocked && tc.result === 'passed' ? ' (mocked)' : ''}${tc.stale ? ` · stale: ${tc.stale}` : ''}${tc.triage ? ` · triage ${tc.triage.class}${tc.triage.ruleId === 'manual' ? ` (by hand, ${tc.triage.decision})` : ''}` : ''}`);
88
114
  if (tc.contract) L(` contract: ${tc.contract.class}${tc.contract.uncommitted ? ' · lock entry not committed' : ''}${tc.contract.lowIdentity ? ' · low target identity' : ''}`);
89
115
  if (tc.anchors.length) L(` anchors: ${tc.anchors.map((a) => `${a.anchor} (${a.change})`).join(', ')}`);
90
116
  for (const f of tc.findings) L(` finding: ${f.code} [${f.confidence.label} ${f.confidence.score}] ${f.citation}`);
91
117
  L('');
92
- if (steps.length) renderSteps(steps);
118
+ if (steps.length) renderSteps(steps, cwd);
93
119
  else L(` ✓ nothing to do for this test case — ${testCaseStatusLabel(tc)}`);
120
+ // "needs triage" names how to classify the red, even while a unit step holds its own step back (#966).
121
+ if (testCaseNeed(tc) === 'triage') for (const line of caseTriageLines(s, tc, steps, cwd)) L(line);
94
122
  L('');
95
123
  }
96
124
 
@@ -128,10 +156,11 @@ export function registerNextCommand(program: Command): void {
128
156
  });
129
157
  }
130
158
  if (o.json) {
131
- console.log(JSON.stringify(states.length === 1 ? states[0] : states, null, 2));
159
+ const out = states.map((s) => withRenderedHumanActions(s, cwd));
160
+ console.log(JSON.stringify(out.length === 1 ? out[0] : out, null, 2));
132
161
  return;
133
162
  }
134
- for (const s of states) render(s);
163
+ for (const s of states) render(s, cwd);
135
164
  } catch (e) {
136
165
  console.error(`Error: ${e instanceof Error ? e.message : e}`);
137
166
  process.exit(1);