@chorus-aidlc/chorus 0.8.2 → 0.9.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 (225) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-build-manifest.json +244 -244
  3. package/.next/standalone/.next/app-path-routes-manifest.json +34 -34
  4. package/.next/standalone/.next/build-manifest.json +5 -5
  5. package/.next/standalone/.next/prerender-manifest.json +39 -39
  6. package/.next/standalone/.next/server/app/(dashboard)/project-groups/[uuid]/page_client-reference-manifest.js +1 -1
  7. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/activity/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/dashboard/[ideaUuid]/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/dashboard/page.js +2 -2
  10. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/dashboard/page.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/dashboard/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/[documentUuid]/page.js +1 -1
  13. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/[documentUuid]/page_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/page.js +2 -2
  15. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/ideas/[ideaUuid]/page.js +1 -1
  17. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/ideas/[ideaUuid]/page.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/ideas/[ideaUuid]/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/ideas/page.js +1 -1
  20. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/ideas/page.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/ideas/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/[proposalUuid]/page.js +2 -2
  23. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/[proposalUuid]/page.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/[proposalUuid]/page_client-reference-manifest.js +1 -1
  25. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/new/page.js +1 -1
  26. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/new/page.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/new/page_client-reference-manifest.js +1 -1
  28. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/page.js +1 -1
  29. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/page_client-reference-manifest.js +1 -1
  30. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/tasks/[taskUuid]/page_client-reference-manifest.js +1 -1
  31. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/tasks/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/(dashboard)/projects/page_client-reference-manifest.js +1 -1
  33. package/.next/standalone/.next/server/app/(dashboard)/settings/page.js +1 -1
  34. package/.next/standalone/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
  35. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  37. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  38. package/.next/standalone/.next/server/app/admin/companies/[uuid]/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/admin/companies/new/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/admin/companies/new.html +1 -1
  41. package/.next/standalone/.next/server/app/admin/companies/new.rsc +2 -2
  42. package/.next/standalone/.next/server/app/admin/companies/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/admin/companies.html +1 -1
  44. package/.next/standalone/.next/server/app/admin/companies.rsc +2 -2
  45. package/.next/standalone/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/admin.html +1 -1
  47. package/.next/standalone/.next/server/app/admin.rsc +2 -2
  48. package/.next/standalone/.next/server/app/api/admin/companies/[uuid]/route_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/api/admin/companies/route_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/api/admin/login/route_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/api/admin/session/route_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/api/agents/[uuid]/route_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/api/agents/[uuid]/sessions/route.js +1 -1
  54. package/.next/standalone/.next/server/app/api/agents/[uuid]/sessions/route_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/api/agents/route_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/api/api-keys/[uuid]/route_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/app/api/api-keys/route_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/api/auth/callback/route_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/api/auth/check-default/route_client-reference-manifest.js +1 -1
  60. package/.next/standalone/.next/server/app/api/auth/company-oidc/route_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/app/api/auth/default-login/route_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/api/auth/identify/route_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app/api/auth/me/route_client-reference-manifest.js +1 -1
  65. package/.next/standalone/.next/server/app/api/auth/refresh/route_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/app/api/auth/session/route_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/api/auth/sync-token/route_client-reference-manifest.js +1 -1
  68. package/.next/standalone/.next/server/app/api/comments/route_client-reference-manifest.js +1 -1
  69. package/.next/standalone/.next/server/app/api/documents/[uuid]/route.js +1 -1
  70. package/.next/standalone/.next/server/app/api/documents/[uuid]/route_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/api/events/notifications/route_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/api/events/route_client-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  74. package/.next/standalone/.next/server/app/api/ideas/[uuid]/claim/route.js +1 -1
  75. package/.next/standalone/.next/server/app/api/ideas/[uuid]/claim/route.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/api/ideas/[uuid]/claim/route_client-reference-manifest.js +1 -1
  77. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/preview/route.js +1 -1
  78. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/preview/route.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/preview/route_client-reference-manifest.js +1 -1
  80. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/route.js +1 -1
  81. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/route.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/route_client-reference-manifest.js +1 -1
  83. package/.next/standalone/.next/server/app/api/ideas/[uuid]/release/route.js +1 -1
  84. package/.next/standalone/.next/server/app/api/ideas/[uuid]/release/route.js.nft.json +1 -1
  85. package/.next/standalone/.next/server/app/api/ideas/[uuid]/release/route_client-reference-manifest.js +1 -1
  86. package/.next/standalone/.next/server/app/api/ideas/[uuid]/route.js +1 -1
  87. package/.next/standalone/.next/server/app/api/ideas/[uuid]/route.js.nft.json +1 -1
  88. package/.next/standalone/.next/server/app/api/ideas/[uuid]/route_client-reference-manifest.js +1 -1
  89. package/.next/standalone/.next/server/app/api/mcp/route.js +5 -5
  90. package/.next/standalone/.next/server/app/api/mcp/route_client-reference-manifest.js +1 -1
  91. package/.next/standalone/.next/server/app/api/me/assignments/route.js +1 -1
  92. package/.next/standalone/.next/server/app/api/me/assignments/route.js.nft.json +1 -1
  93. package/.next/standalone/.next/server/app/api/me/assignments/route_client-reference-manifest.js +1 -1
  94. package/.next/standalone/.next/server/app/api/mentionables/route_client-reference-manifest.js +1 -1
  95. package/.next/standalone/.next/server/app/api/notifications/[uuid]/archive/route_client-reference-manifest.js +1 -1
  96. package/.next/standalone/.next/server/app/api/notifications/[uuid]/read/route_client-reference-manifest.js +1 -1
  97. package/.next/standalone/.next/server/app/api/notifications/preferences/route_client-reference-manifest.js +1 -1
  98. package/.next/standalone/.next/server/app/api/notifications/read-all/route_client-reference-manifest.js +1 -1
  99. package/.next/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  100. package/.next/standalone/.next/server/app/api/notifications/unread-count/route_client-reference-manifest.js +1 -1
  101. package/.next/standalone/.next/server/app/api/project-groups/[uuid]/dashboard/route_client-reference-manifest.js +1 -1
  102. package/.next/standalone/.next/server/app/api/project-groups/[uuid]/route_client-reference-manifest.js +1 -1
  103. package/.next/standalone/.next/server/app/api/project-groups/route_client-reference-manifest.js +1 -1
  104. package/.next/standalone/.next/server/app/api/projects/[uuid]/activity/route_client-reference-manifest.js +1 -1
  105. package/.next/standalone/.next/server/app/api/projects/[uuid]/available/route.js +1 -1
  106. package/.next/standalone/.next/server/app/api/projects/[uuid]/available/route.js.nft.json +1 -1
  107. package/.next/standalone/.next/server/app/api/projects/[uuid]/available/route_client-reference-manifest.js +1 -1
  108. package/.next/standalone/.next/server/app/api/projects/[uuid]/documents/route.js +1 -1
  109. package/.next/standalone/.next/server/app/api/projects/[uuid]/documents/route_client-reference-manifest.js +1 -1
  110. package/.next/standalone/.next/server/app/api/projects/[uuid]/group/route_client-reference-manifest.js +1 -1
  111. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/route.js +1 -1
  112. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/route.js.nft.json +1 -1
  113. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/route_client-reference-manifest.js +1 -1
  114. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/tracker/route.js +1 -1
  115. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/tracker/route.js.nft.json +1 -1
  116. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/tracker/route_client-reference-manifest.js +1 -1
  117. package/.next/standalone/.next/server/app/api/projects/[uuid]/proposals/[proposalUuid]/validate/route_client-reference-manifest.js +1 -1
  118. package/.next/standalone/.next/server/app/api/projects/[uuid]/proposals/route_client-reference-manifest.js +1 -1
  119. package/.next/standalone/.next/server/app/api/projects/[uuid]/proposals/summary/route_client-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/app/api/projects/[uuid]/route_client-reference-manifest.js +1 -1
  121. package/.next/standalone/.next/server/app/api/projects/[uuid]/stats/route_client-reference-manifest.js +1 -1
  122. package/.next/standalone/.next/server/app/api/projects/[uuid]/tasks/dependencies/route_client-reference-manifest.js +1 -1
  123. package/.next/standalone/.next/server/app/api/projects/[uuid]/tasks/route_client-reference-manifest.js +1 -1
  124. package/.next/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  125. package/.next/standalone/.next/server/app/api/proposals/[uuid]/approve/route_client-reference-manifest.js +1 -1
  126. package/.next/standalone/.next/server/app/api/proposals/[uuid]/close/route_client-reference-manifest.js +1 -1
  127. package/.next/standalone/.next/server/app/api/proposals/[uuid]/reject/route_client-reference-manifest.js +1 -1
  128. package/.next/standalone/.next/server/app/api/proposals/[uuid]/revoke/route_client-reference-manifest.js +1 -1
  129. package/.next/standalone/.next/server/app/api/proposals/[uuid]/route_client-reference-manifest.js +1 -1
  130. package/.next/standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  131. package/.next/standalone/.next/server/app/api/sessions/[uuid]/route.js +1 -1
  132. package/.next/standalone/.next/server/app/api/sessions/[uuid]/route_client-reference-manifest.js +1 -1
  133. package/.next/standalone/.next/server/app/api/tasks/[uuid]/claim/route_client-reference-manifest.js +1 -1
  134. package/.next/standalone/.next/server/app/api/tasks/[uuid]/dependencies/[dependsOnUuid]/route_client-reference-manifest.js +1 -1
  135. package/.next/standalone/.next/server/app/api/tasks/[uuid]/dependencies/route_client-reference-manifest.js +1 -1
  136. package/.next/standalone/.next/server/app/api/tasks/[uuid]/release/route_client-reference-manifest.js +1 -1
  137. package/.next/standalone/.next/server/app/api/tasks/[uuid]/route_client-reference-manifest.js +1 -1
  138. package/.next/standalone/.next/server/app/api/tasks/[uuid]/sessions/route.js +1 -1
  139. package/.next/standalone/.next/server/app/api/tasks/[uuid]/sessions/route_client-reference-manifest.js +1 -1
  140. package/.next/standalone/.next/server/app/index.html +1 -1
  141. package/.next/standalone/.next/server/app/index.rsc +2 -2
  142. package/.next/standalone/.next/server/app/login/admin/page_client-reference-manifest.js +1 -1
  143. package/.next/standalone/.next/server/app/login/admin.html +1 -1
  144. package/.next/standalone/.next/server/app/login/admin.rsc +2 -2
  145. package/.next/standalone/.next/server/app/login/callback/page_client-reference-manifest.js +1 -1
  146. package/.next/standalone/.next/server/app/login/callback.html +1 -1
  147. package/.next/standalone/.next/server/app/login/callback.rsc +2 -2
  148. package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  149. package/.next/standalone/.next/server/app/login/pick-workspace/page_client-reference-manifest.js +1 -1
  150. package/.next/standalone/.next/server/app/login/pick-workspace.html +1 -1
  151. package/.next/standalone/.next/server/app/login/pick-workspace.rsc +2 -2
  152. package/.next/standalone/.next/server/app/login/silent-refresh/page_client-reference-manifest.js +1 -1
  153. package/.next/standalone/.next/server/app/login/silent-refresh.html +1 -1
  154. package/.next/standalone/.next/server/app/login/silent-refresh.rsc +2 -2
  155. package/.next/standalone/.next/server/app/login.html +1 -1
  156. package/.next/standalone/.next/server/app/login.rsc +2 -2
  157. package/.next/standalone/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  158. package/.next/standalone/.next/server/app/onboarding.html +1 -1
  159. package/.next/standalone/.next/server/app/onboarding.rsc +2 -2
  160. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  161. package/.next/standalone/.next/server/app/projects.html +1 -1
  162. package/.next/standalone/.next/server/app/projects.rsc +2 -2
  163. package/.next/standalone/.next/server/app/settings.html +1 -1
  164. package/.next/standalone/.next/server/app/settings.rsc +3 -3
  165. package/.next/standalone/.next/server/app-paths-manifest.json +34 -34
  166. package/.next/standalone/.next/server/chunks/1871.js +1 -1
  167. package/.next/standalone/.next/server/chunks/2618.js +1 -1
  168. package/.next/standalone/.next/server/chunks/2961.js +1 -1
  169. package/.next/standalone/.next/server/chunks/4043.js +1 -0
  170. package/.next/standalone/.next/server/chunks/5044.js +1 -1
  171. package/.next/standalone/.next/server/chunks/6113.js +1 -1
  172. package/.next/standalone/.next/server/chunks/6592.js +1 -0
  173. package/.next/standalone/.next/server/chunks/6643.js +1 -1
  174. package/.next/standalone/.next/server/chunks/7368.js +1 -1
  175. package/.next/standalone/.next/server/chunks/7578.js +2 -2
  176. package/.next/standalone/.next/server/chunks/8637.js +1 -1
  177. package/.next/standalone/.next/server/chunks/920.js +1 -1
  178. package/.next/standalone/.next/server/chunks/937.js +1 -1
  179. package/.next/standalone/.next/server/chunks/9508.js +2 -2
  180. package/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
  181. package/.next/standalone/.next/server/middleware-manifest.json +1 -1
  182. package/.next/standalone/.next/server/pages/404.html +1 -1
  183. package/.next/standalone/.next/server/pages/500.html +1 -1
  184. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  185. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  186. package/.next/standalone/.next/static/chunks/{4860.4aa0288b2953186b.js → 4860.29e14c631b3a4ed0.js} +1 -1
  187. package/.next/standalone/.next/static/chunks/{99373.00844a02fe44575b.js → 99373.20008a2c9ebbec89.js} +1 -1
  188. package/.next/standalone/.next/static/chunks/app/(dashboard)/projects/[uuid]/dashboard/page-6d49b00568c97056.js +1 -0
  189. package/.next/standalone/.next/static/chunks/app/(dashboard)/projects/[uuid]/documents/page-95cdcec5e6db551f.js +1 -0
  190. package/.next/standalone/.next/static/chunks/app/(dashboard)/settings/{page-a01b45dbe80aeecf.js → page-b153aa34b940a24c.js} +1 -1
  191. package/.next/standalone/.next/static/chunks/{webpack-b999409365c6a0aa.js → webpack-3cb38d4d37d2765b.js} +1 -1
  192. package/.next/standalone/.next/static/css/26282a3639f03089.css +1 -0
  193. package/.next/standalone/package.json +1 -1
  194. package/.next/standalone/public/chorus-plugin/.claude-plugin/plugin.json +1 -1
  195. package/.next/standalone/public/chorus-plugin/agents/proposal-reviewer.md +1 -1
  196. package/.next/standalone/public/chorus-plugin/agents/task-reviewer.md +1 -1
  197. package/.next/standalone/public/chorus-plugin/bin/on-post-verify-task.sh +198 -115
  198. package/.next/standalone/public/chorus-plugin/bin/tests/test-on-post-verify-task.sh +302 -50
  199. package/.next/standalone/public/chorus-plugin/skills/brainstorm/SKILL.md +166 -0
  200. package/.next/standalone/public/chorus-plugin/skills/chorus/SKILL.md +10 -6
  201. package/.next/standalone/public/chorus-plugin/skills/develop/SKILL.md +6 -2
  202. package/.next/standalone/public/chorus-plugin/skills/idea/SKILL.md +17 -1
  203. package/.next/standalone/public/chorus-plugin/skills/openspec-aware/SKILL.md +1 -1
  204. package/.next/standalone/public/chorus-plugin/skills/proposal/SKILL.md +6 -7
  205. package/.next/standalone/public/chorus-plugin/skills/quick-dev/SKILL.md +1 -1
  206. package/.next/standalone/public/chorus-plugin/skills/review/SKILL.md +1 -1
  207. package/.next/standalone/public/chorus-plugin/skills/yolo/SKILL.md +7 -1
  208. package/.next/standalone/public/skill/brainstorm-chorus/SKILL.md +166 -0
  209. package/.next/standalone/public/skill/chorus/SKILL.md +10 -6
  210. package/.next/standalone/public/skill/develop-chorus/SKILL.md +5 -1
  211. package/.next/standalone/public/skill/idea-chorus/SKILL.md +17 -1
  212. package/.next/standalone/public/skill/package.json +1 -1
  213. package/.next/standalone/public/skill/proposal-chorus/SKILL.md +6 -7
  214. package/.next/standalone/public/skill/quick-dev-chorus/SKILL.md +1 -1
  215. package/.next/standalone/public/skill/review-chorus/SKILL.md +1 -1
  216. package/package.json +1 -1
  217. package/prisma/migrations/20260524144057_remove_session_expires_at/migration.sql +11 -0
  218. package/prisma/schema.prisma +2 -2
  219. package/.next/standalone/.next/server/chunks/554.js +0 -1
  220. package/.next/standalone/.next/server/chunks/8163.js +0 -1
  221. package/.next/standalone/.next/static/chunks/app/(dashboard)/projects/[uuid]/dashboard/page-ae96fc9386afff88.js +0 -1
  222. package/.next/standalone/.next/static/chunks/app/(dashboard)/projects/[uuid]/documents/page-062c6fef8aef25a6.js +0 -1
  223. package/.next/standalone/.next/static/css/a3f1e693b51f7540.css +0 -1
  224. /package/.next/standalone/.next/static/{3AEr6cS1BaPlwTuyPvMQF → lMRAkQ7TedkdqmIWybcRT}/_buildManifest.js +0 -0
  225. /package/.next/standalone/.next/static/{3AEr6cS1BaPlwTuyPvMQF → lMRAkQ7TedkdqmIWybcRT}/_ssgManifest.js +0 -0
@@ -12,7 +12,11 @@
12
12
  # hook into a sandbox dir alongside our shim wrapper — the hook's
13
13
  # `dirname "$0"` lookup then picks up our shim, not the real wrapper.
14
14
  #
15
- # Fixtures cover all gating branches:
15
+ # Two independent reminder branches share the post-verify trigger:
16
+ # - Branch A (OpenSpec archive trigger) — emits `openspec archive`
17
+ # - Branch B (Idea-completion report) — emits `create idea-completion report`
18
+ #
19
+ # Fixtures for Branch A (legacy):
16
20
  # positive — last task verified, slug present, openspec/ folder + CLI both present
17
21
  # -> stdout MUST contain `openspec archive my-feature`
18
22
  # not-last-task — slug present, both signals present, but one task still in_progress
@@ -26,6 +30,30 @@
26
30
  # mode-off — CHORUS_OPENSPEC_MODE=off (explicit opt-out), even with both signals present
27
31
  # -> stdout MUST NOT contain `openspec archive`
28
32
  #
33
+ # Fixtures for Branch B (idea-completion report — see spec
34
+ # idea-completion-report scenarios "Hook injects a reminder ..."):
35
+ # report-positive — idea-rooted proposal, all its tasks are
36
+ # done|closed, no report Document on this proposal
37
+ # -> stdout MUST contain `create idea-completion report`
38
+ # report-existing — same as report-positive but a `type=report`
39
+ # Document already exists on this proposal
40
+ # -> stdout MUST NOT contain `create idea-completion report`
41
+ # report-not-last — idea-rooted, but one task is still in_progress
42
+ # -> stdout MUST NOT contain `create idea-completion report`
43
+ # report-quick-task — proposal's inputType is not "idea" (e.g. a
44
+ # non-idea-rooted proposal); branch B must skip
45
+ # regardless of task status
46
+ # -> stdout MUST NOT contain `create idea-completion report`
47
+ # report-task-overflow — chorus_list_tasks returns total > tasks.length
48
+ # (page-1 of a wider task set, all returned tasks
49
+ # are done but more remain). Hook MUST refuse to
50
+ # conclude "all done" — silent skip.
51
+ # -> stdout MUST NOT contain `create idea-completion report`
52
+ # report-doc-overflow — chorus_get_documents returns total > documents.length
53
+ # (existing report sits on a later page). Hook MUST
54
+ # refuse to conclude "no report" — silent skip.
55
+ # -> stdout MUST NOT contain `create idea-completion report`
56
+ #
29
57
  # All fixtures must end with exit 0.
30
58
  #
31
59
  # Bash 3.2 compatible. Run:
@@ -90,11 +118,18 @@ case "$cmd" in
90
118
  mode-off|no-folder|no-cli) fixture=positive ;;
91
119
  esac
92
120
  case "${fixture}:${tool}" in
121
+ # ----- Branch A fixtures (existing) -----
122
+ # NOTE: Branch B's idea-rooted check requires `inputType:"idea"`. We
123
+ # set it on the OpenSpec fixtures too because real proposals always
124
+ # carry inputType. To keep Branch A fixtures independent of Branch B,
125
+ # we deliberately DO NOT register chorus_get_proposals /
126
+ # chorus_get_documents responses for them — Branch B hits the default
127
+ # "unhandled" case (empty body) and silent-skips.
93
128
  positive:chorus_get_task)
94
129
  echo '{"uuid":"task-3","title":"Task 3","status":"done","proposalUuid":"prop-1","project":{"uuid":"proj-1","name":"P"}}'
95
130
  ;;
96
131
  positive:chorus_get_proposal)
97
- printf '%s' '{"uuid":"prop-1","title":"P1","description":"Some intro line.\nOpenSpec change slug: my-feature\nMore body.","inputUuids":["idea-1"]}'
132
+ printf '%s' '{"uuid":"prop-1","title":"P1","description":"Some intro line.\nOpenSpec change slug: my-feature\nMore body.","inputType":"idea","inputUuids":["idea-1"]}'
98
133
  ;;
99
134
  positive:chorus_list_tasks)
100
135
  echo '{"tasks":[{"uuid":"task-1","status":"done"},{"uuid":"task-2","status":"done"},{"uuid":"task-3","status":"done"}],"total":3,"page":1,"pageSize":200}'
@@ -103,7 +138,7 @@ case "$cmd" in
103
138
  echo '{"uuid":"task-2","title":"Task 2","status":"done","proposalUuid":"prop-1","project":{"uuid":"proj-1","name":"P"}}'
104
139
  ;;
105
140
  not-last-task:chorus_get_proposal)
106
- printf '%s' '{"uuid":"prop-1","title":"P1","description":"Intro.\nOpenSpec change slug: my-feature\nMore.","inputUuids":["idea-1"]}'
141
+ printf '%s' '{"uuid":"prop-1","title":"P1","description":"Intro.\nOpenSpec change slug: my-feature\nMore.","inputType":"idea","inputUuids":["idea-1"]}'
107
142
  ;;
108
143
  not-last-task:chorus_list_tasks)
109
144
  echo '{"tasks":[{"uuid":"task-1","status":"done"},{"uuid":"task-2","status":"done"},{"uuid":"task-3","status":"in_progress"}],"total":3,"page":1,"pageSize":200}'
@@ -112,11 +147,104 @@ case "$cmd" in
112
147
  echo '{"uuid":"task-3","title":"Task 3","status":"done","proposalUuid":"prop-2","project":{"uuid":"proj-1","name":"P"}}'
113
148
  ;;
114
149
  no-slug:chorus_get_proposal)
115
- printf '%s' '{"uuid":"prop-2","title":"P2","description":"Free-form proposal description with no slug marker.","inputUuids":["idea-2"]}'
150
+ printf '%s' '{"uuid":"prop-2","title":"P2","description":"Free-form proposal description with no slug marker.","inputType":"idea","inputUuids":["idea-2"]}'
116
151
  ;;
117
152
  no-slug:chorus_list_tasks)
118
153
  echo '{"tasks":[{"uuid":"task-1","status":"done"},{"uuid":"task-2","status":"done"},{"uuid":"task-3","status":"done"}],"total":3,"page":1,"pageSize":200}'
119
154
  ;;
155
+
156
+ # ----- Branch B fixtures (idea-completion report reminder) -----
157
+ # report-positive: idea-rooted proposal whose tasks are all
158
+ # done|closed, no report Document on it -> emit.
159
+ report-positive:chorus_get_task)
160
+ echo '{"uuid":"task-9","title":"Task 9","status":"done","proposalUuid":"prop-9","project":{"uuid":"proj-1","name":"P"}}'
161
+ ;;
162
+ report-positive:chorus_get_proposal)
163
+ printf '%s' '{"uuid":"prop-9","title":"P9","description":"Free-form proposal — no OpenSpec slug here.","inputType":"idea","inputUuids":["idea-9"]}'
164
+ ;;
165
+ report-positive:chorus_list_tasks)
166
+ echo '{"tasks":[{"uuid":"task-7","status":"done","proposalUuid":"prop-9"},{"uuid":"task-8","status":"closed","proposalUuid":"prop-9"},{"uuid":"task-9","status":"done","proposalUuid":"prop-9"}],"total":3,"page":1,"pageSize":200}'
167
+ ;;
168
+ report-positive:chorus_get_documents)
169
+ echo '{"documents":[],"total":0,"page":1,"pageSize":200}'
170
+ ;;
171
+
172
+ # report-existing: same as positive but a report Document already
173
+ # exists on this proposal -> silent skip.
174
+ report-existing:chorus_get_task)
175
+ echo '{"uuid":"task-9","title":"Task 9","status":"done","proposalUuid":"prop-9","project":{"uuid":"proj-1","name":"P"}}'
176
+ ;;
177
+ report-existing:chorus_get_proposal)
178
+ printf '%s' '{"uuid":"prop-9","title":"P9","description":"Free-form proposal.","inputType":"idea","inputUuids":["idea-9"]}'
179
+ ;;
180
+ report-existing:chorus_list_tasks)
181
+ echo '{"tasks":[{"uuid":"task-7","status":"done","proposalUuid":"prop-9"},{"uuid":"task-8","status":"closed","proposalUuid":"prop-9"},{"uuid":"task-9","status":"done","proposalUuid":"prop-9"}],"total":3,"page":1,"pageSize":200}'
182
+ ;;
183
+ report-existing:chorus_get_documents)
184
+ echo '{"documents":[{"uuid":"doc-r1","type":"report","title":"Idea 9 — completion report","proposalUuid":"prop-9"}],"total":1,"page":1,"pageSize":200}'
185
+ ;;
186
+
187
+ # report-not-last: a task is still in_progress -> silent skip.
188
+ report-not-last:chorus_get_task)
189
+ echo '{"uuid":"task-9","title":"Task 9","status":"done","proposalUuid":"prop-9","project":{"uuid":"proj-1","name":"P"}}'
190
+ ;;
191
+ report-not-last:chorus_get_proposal)
192
+ printf '%s' '{"uuid":"prop-9","title":"P9","description":"Free-form proposal.","inputType":"idea","inputUuids":["idea-9"]}'
193
+ ;;
194
+ report-not-last:chorus_list_tasks)
195
+ echo '{"tasks":[{"uuid":"task-7","status":"done","proposalUuid":"prop-9"},{"uuid":"task-8","status":"in_progress","proposalUuid":"prop-9"},{"uuid":"task-9","status":"done","proposalUuid":"prop-9"}],"total":3,"page":1,"pageSize":200}'
196
+ ;;
197
+ report-not-last:chorus_get_documents)
198
+ echo '{"documents":[],"total":0,"page":1,"pageSize":200}'
199
+ ;;
200
+
201
+ # report-quick-task: proposal.inputType != "idea" -> Branch B must
202
+ # skip at the first gate, before any further calls.
203
+ report-quick-task:chorus_get_task)
204
+ echo '{"uuid":"task-9","title":"Task 9","status":"done","proposalUuid":"prop-9","project":{"uuid":"proj-1","name":"P"}}'
205
+ ;;
206
+ report-quick-task:chorus_get_proposal)
207
+ printf '%s' '{"uuid":"prop-9","title":"P9","description":"Free-form proposal.","inputType":"manual","inputUuids":[]}'
208
+ ;;
209
+ report-quick-task:chorus_list_tasks)
210
+ echo '{"tasks":[{"uuid":"task-9","status":"done","proposalUuid":"prop-9"}],"total":1,"page":1,"pageSize":200}'
211
+ ;;
212
+ report-quick-task:chorus_get_documents)
213
+ echo '{"documents":[],"total":0,"page":1,"pageSize":200}'
214
+ ;;
215
+
216
+ # report-task-overflow: chorus_list_tasks returns 5 done tasks but
217
+ # total=25 — more tasks exist on later pages. Hook MUST refuse to
218
+ # conclude "all done" and silent-skip Branch B.
219
+ report-task-overflow:chorus_get_task)
220
+ echo '{"uuid":"task-9","title":"Task 9","status":"done","proposalUuid":"prop-9","project":{"uuid":"proj-1","name":"P"}}'
221
+ ;;
222
+ report-task-overflow:chorus_get_proposal)
223
+ printf '%s' '{"uuid":"prop-9","title":"P9","description":"Free-form proposal.","inputType":"idea","inputUuids":["idea-9"]}'
224
+ ;;
225
+ report-task-overflow:chorus_list_tasks)
226
+ echo '{"tasks":[{"uuid":"t1","status":"done","proposalUuid":"prop-9"},{"uuid":"t2","status":"done","proposalUuid":"prop-9"},{"uuid":"t3","status":"done","proposalUuid":"prop-9"},{"uuid":"t4","status":"done","proposalUuid":"prop-9"},{"uuid":"t5","status":"done","proposalUuid":"prop-9"}],"total":25,"page":1,"pageSize":200}'
227
+ ;;
228
+ report-task-overflow:chorus_get_documents)
229
+ echo '{"documents":[],"total":0,"page":1,"pageSize":200}'
230
+ ;;
231
+
232
+ # report-doc-overflow: chorus_get_documents returns 1 unrelated
233
+ # report but total=25 — this proposal's existing report sits on a
234
+ # later page. Hook MUST refuse to conclude "no report" and silent-skip.
235
+ report-doc-overflow:chorus_get_task)
236
+ echo '{"uuid":"task-9","title":"Task 9","status":"done","proposalUuid":"prop-9","project":{"uuid":"proj-1","name":"P"}}'
237
+ ;;
238
+ report-doc-overflow:chorus_get_proposal)
239
+ printf '%s' '{"uuid":"prop-9","title":"P9","description":"Free-form proposal.","inputType":"idea","inputUuids":["idea-9"]}'
240
+ ;;
241
+ report-doc-overflow:chorus_list_tasks)
242
+ echo '{"tasks":[{"uuid":"task-7","status":"done","proposalUuid":"prop-9"},{"uuid":"task-8","status":"closed","proposalUuid":"prop-9"},{"uuid":"task-9","status":"done","proposalUuid":"prop-9"}],"total":3,"page":1,"pageSize":200}'
243
+ ;;
244
+ report-doc-overflow:chorus_get_documents)
245
+ echo '{"documents":[{"uuid":"doc-other","type":"report","title":"Some other report","proposalUuid":"prop-OTHER"}],"total":25,"page":1,"pageSize":200}'
246
+ ;;
247
+
120
248
  *)
121
249
  # Unhandled (tool, fixture) — return empty, hook should silent-skip.
122
250
  echo ""
@@ -153,11 +281,12 @@ case "$fixture" in
153
281
  mode-off|no-folder|no-cli) fixture=positive ;;
154
282
  esac
155
283
  case "${fixture}:${tool}" in
284
+ # ----- Branch A fixtures (existing) -----
156
285
  positive:chorus_get_task)
157
286
  echo '{"uuid":"task-3","title":"Task 3","status":"done","proposalUuid":"prop-1","project":{"uuid":"proj-1","name":"P"}}'
158
287
  ;;
159
288
  positive:chorus_get_proposal)
160
- printf '%s' '{"uuid":"prop-1","title":"P1","description":"Some intro line.\nOpenSpec change slug: my-feature\nMore body.","inputUuids":["idea-1"]}'
289
+ printf '%s' '{"uuid":"prop-1","title":"P1","description":"Some intro line.\nOpenSpec change slug: my-feature\nMore body.","inputType":"idea","inputUuids":["idea-1"]}'
161
290
  ;;
162
291
  positive:chorus_list_tasks)
163
292
  echo '{"tasks":[{"uuid":"task-1","status":"done"},{"uuid":"task-2","status":"done"},{"uuid":"task-3","status":"done"}],"total":3,"page":1,"pageSize":200}'
@@ -166,7 +295,7 @@ case "${fixture}:${tool}" in
166
295
  echo '{"uuid":"task-2","title":"Task 2","status":"done","proposalUuid":"prop-1","project":{"uuid":"proj-1","name":"P"}}'
167
296
  ;;
168
297
  not-last-task:chorus_get_proposal)
169
- printf '%s' '{"uuid":"prop-1","title":"P1","description":"Intro.\nOpenSpec change slug: my-feature\nMore.","inputUuids":["idea-1"]}'
298
+ printf '%s' '{"uuid":"prop-1","title":"P1","description":"Intro.\nOpenSpec change slug: my-feature\nMore.","inputType":"idea","inputUuids":["idea-1"]}'
170
299
  ;;
171
300
  not-last-task:chorus_list_tasks)
172
301
  echo '{"tasks":[{"uuid":"task-1","status":"done"},{"uuid":"task-2","status":"done"},{"uuid":"task-3","status":"in_progress"}],"total":3,"page":1,"pageSize":200}'
@@ -175,11 +304,93 @@ case "${fixture}:${tool}" in
175
304
  echo '{"uuid":"task-3","title":"Task 3","status":"done","proposalUuid":"prop-2","project":{"uuid":"proj-1","name":"P"}}'
176
305
  ;;
177
306
  no-slug:chorus_get_proposal)
178
- printf '%s' '{"uuid":"prop-2","title":"P2","description":"Free-form proposal description with no slug marker.","inputUuids":["idea-2"]}'
307
+ printf '%s' '{"uuid":"prop-2","title":"P2","description":"Free-form proposal description with no slug marker.","inputType":"idea","inputUuids":["idea-2"]}'
179
308
  ;;
180
309
  no-slug:chorus_list_tasks)
181
310
  echo '{"tasks":[{"uuid":"task-1","status":"done"},{"uuid":"task-2","status":"done"},{"uuid":"task-3","status":"done"}],"total":3,"page":1,"pageSize":200}'
182
311
  ;;
312
+
313
+ # ----- Branch B fixtures (idea-completion report reminder) -----
314
+ report-positive:chorus_get_task)
315
+ echo '{"uuid":"task-9","title":"Task 9","status":"done","proposalUuid":"prop-9","project":{"uuid":"proj-1","name":"P"}}'
316
+ ;;
317
+ report-positive:chorus_get_proposal)
318
+ printf '%s' '{"uuid":"prop-9","title":"P9","description":"Free-form proposal — no OpenSpec slug here.","inputType":"idea","inputUuids":["idea-9"]}'
319
+ ;;
320
+ report-positive:chorus_list_tasks)
321
+ echo '{"tasks":[{"uuid":"task-7","status":"done","proposalUuid":"prop-9"},{"uuid":"task-8","status":"closed","proposalUuid":"prop-9"},{"uuid":"task-9","status":"done","proposalUuid":"prop-9"}],"total":3,"page":1,"pageSize":200}'
322
+ ;;
323
+ report-positive:chorus_get_documents)
324
+ echo '{"documents":[],"total":0,"page":1,"pageSize":200}'
325
+ ;;
326
+
327
+ report-existing:chorus_get_task)
328
+ echo '{"uuid":"task-9","title":"Task 9","status":"done","proposalUuid":"prop-9","project":{"uuid":"proj-1","name":"P"}}'
329
+ ;;
330
+ report-existing:chorus_get_proposal)
331
+ printf '%s' '{"uuid":"prop-9","title":"P9","description":"Free-form proposal.","inputType":"idea","inputUuids":["idea-9"]}'
332
+ ;;
333
+ report-existing:chorus_list_tasks)
334
+ echo '{"tasks":[{"uuid":"task-7","status":"done","proposalUuid":"prop-9"},{"uuid":"task-8","status":"closed","proposalUuid":"prop-9"},{"uuid":"task-9","status":"done","proposalUuid":"prop-9"}],"total":3,"page":1,"pageSize":200}'
335
+ ;;
336
+ report-existing:chorus_get_documents)
337
+ echo '{"documents":[{"uuid":"doc-r1","type":"report","title":"Idea 9 — completion report","proposalUuid":"prop-9"}],"total":1,"page":1,"pageSize":200}'
338
+ ;;
339
+
340
+ report-not-last:chorus_get_task)
341
+ echo '{"uuid":"task-9","title":"Task 9","status":"done","proposalUuid":"prop-9","project":{"uuid":"proj-1","name":"P"}}'
342
+ ;;
343
+ report-not-last:chorus_get_proposal)
344
+ printf '%s' '{"uuid":"prop-9","title":"P9","description":"Free-form proposal.","inputType":"idea","inputUuids":["idea-9"]}'
345
+ ;;
346
+ report-not-last:chorus_list_tasks)
347
+ echo '{"tasks":[{"uuid":"task-7","status":"done","proposalUuid":"prop-9"},{"uuid":"task-8","status":"in_progress","proposalUuid":"prop-9"},{"uuid":"task-9","status":"done","proposalUuid":"prop-9"}],"total":3,"page":1,"pageSize":200}'
348
+ ;;
349
+ report-not-last:chorus_get_documents)
350
+ echo '{"documents":[],"total":0,"page":1,"pageSize":200}'
351
+ ;;
352
+
353
+ report-quick-task:chorus_get_task)
354
+ echo '{"uuid":"task-9","title":"Task 9","status":"done","proposalUuid":"prop-9","project":{"uuid":"proj-1","name":"P"}}'
355
+ ;;
356
+ report-quick-task:chorus_get_proposal)
357
+ printf '%s' '{"uuid":"prop-9","title":"P9","description":"Free-form proposal.","inputType":"manual","inputUuids":[]}'
358
+ ;;
359
+ report-quick-task:chorus_list_tasks)
360
+ echo '{"tasks":[{"uuid":"task-9","status":"done","proposalUuid":"prop-9"}],"total":1,"page":1,"pageSize":200}'
361
+ ;;
362
+ report-quick-task:chorus_get_documents)
363
+ echo '{"documents":[],"total":0,"page":1,"pageSize":200}'
364
+ ;;
365
+
366
+ # report-task-overflow: tasks total > returned -> silent skip.
367
+ report-task-overflow:chorus_get_task)
368
+ echo '{"uuid":"task-9","title":"Task 9","status":"done","proposalUuid":"prop-9","project":{"uuid":"proj-1","name":"P"}}'
369
+ ;;
370
+ report-task-overflow:chorus_get_proposal)
371
+ printf '%s' '{"uuid":"prop-9","title":"P9","description":"Free-form proposal.","inputType":"idea","inputUuids":["idea-9"]}'
372
+ ;;
373
+ report-task-overflow:chorus_list_tasks)
374
+ echo '{"tasks":[{"uuid":"t1","status":"done","proposalUuid":"prop-9"},{"uuid":"t2","status":"done","proposalUuid":"prop-9"},{"uuid":"t3","status":"done","proposalUuid":"prop-9"},{"uuid":"t4","status":"done","proposalUuid":"prop-9"},{"uuid":"t5","status":"done","proposalUuid":"prop-9"}],"total":25,"page":1,"pageSize":200}'
375
+ ;;
376
+ report-task-overflow:chorus_get_documents)
377
+ echo '{"documents":[],"total":0,"page":1,"pageSize":200}'
378
+ ;;
379
+
380
+ # report-doc-overflow: docs total > returned -> silent skip.
381
+ report-doc-overflow:chorus_get_task)
382
+ echo '{"uuid":"task-9","title":"Task 9","status":"done","proposalUuid":"prop-9","project":{"uuid":"proj-1","name":"P"}}'
383
+ ;;
384
+ report-doc-overflow:chorus_get_proposal)
385
+ printf '%s' '{"uuid":"prop-9","title":"P9","description":"Free-form proposal.","inputType":"idea","inputUuids":["idea-9"]}'
386
+ ;;
387
+ report-doc-overflow:chorus_list_tasks)
388
+ echo '{"tasks":[{"uuid":"task-7","status":"done","proposalUuid":"prop-9"},{"uuid":"task-8","status":"closed","proposalUuid":"prop-9"},{"uuid":"task-9","status":"done","proposalUuid":"prop-9"}],"total":3,"page":1,"pageSize":200}'
389
+ ;;
390
+ report-doc-overflow:chorus_get_documents)
391
+ echo '{"documents":[{"uuid":"doc-other","type":"report","title":"Some other report","proposalUuid":"prop-OTHER"}],"total":25,"page":1,"pageSize":200}'
392
+ ;;
393
+
183
394
  *)
184
395
  echo "" ;;
185
396
  esac
@@ -203,16 +414,20 @@ EVENT_JSON='{"tool_name":"chorus_admin_verify_task","tool_input":{"taskUuid":"ta
203
414
 
204
415
  # ----- Test runner -----
205
416
 
206
- # Args: name fixture variant expected_substring should_contain
417
+ # Args: name fixture variant assertion_mode expected_substring forbidden_substring
207
418
  # variant in {claude, codex}
208
- # should_contain: "yes" -> stdout MUST contain expected_substring
209
- # should_contain: "no" -> stdout MUST NOT contain "openspec archive"
419
+ # assertion_mode:
420
+ # "must-contain" -> stdout MUST contain $expected_substring
421
+ # "must-not-contain" -> stdout MUST NOT contain $forbidden_substring
422
+ # (For Branch B fixtures, $forbidden_substring is `create idea-completion report`.
423
+ # For Branch A "no" fixtures, $forbidden_substring is `openspec archive`.)
210
424
  run_one() {
211
425
  local name="$1"
212
426
  local fixture="$2"
213
427
  local variant="$3"
214
- local should_contain="$4"
428
+ local assertion_mode="$4"
215
429
  local expected="$5"
430
+ local forbidden="${6:-openspec archive}"
216
431
 
217
432
  local hook_dir
218
433
  if [ "$variant" = "claude" ]; then
@@ -292,7 +507,7 @@ run_one() {
292
507
  stdout=$(cat "$stdout_file")
293
508
  rm -f "$stdout_file" "$stderr_file"
294
509
 
295
- if [ "$should_contain" = "yes" ]; then
510
+ if [ "$assertion_mode" = "must-contain" ]; then
296
511
  if printf '%s' "$stdout" | grep -q "$expected"; then
297
512
  echo " PASS $name [$variant]"
298
513
  PASS=$((PASS + 1))
@@ -303,9 +518,9 @@ run_one() {
303
518
  FAILED="$FAILED $name[$variant]"
304
519
  fi
305
520
  else
306
- # should_contain == "no": archive reminder MUST be absent.
307
- if printf '%s' "$stdout" | grep -q "openspec archive"; then
308
- echo " FAIL $name [$variant]: stdout unexpectedly contains 'openspec archive'"
521
+ # must-not-contain: $forbidden MUST be absent.
522
+ if printf '%s' "$stdout" | grep -q "$forbidden"; then
523
+ echo " FAIL $name [$variant]: stdout unexpectedly contains '$forbidden'"
309
524
  printf '%s\n' "$stdout" | sed 's/^/ /'
310
525
  FAIL=$((FAIL + 1))
311
526
  FAILED="$FAILED $name[$variant]"
@@ -319,41 +534,78 @@ run_one() {
319
534
  echo "Sandbox: $SANDBOX"
320
535
  echo ""
321
536
 
322
- # ----- Branch 1: positive (slug + last task + CLI) -----
323
- run_one "positive" "positive" "claude" "yes" "openspec archive my-feature"
324
- run_one "positive" "positive" "codex" "yes" "openspec archive my-feature"
325
-
326
- # ----- Branch 2: not-last-task -----
327
- run_one "not-last-task" "not-last-task" "claude" "no" ""
328
- run_one "not-last-task" "not-last-task" "codex" "no" ""
329
-
330
- # ----- Branch 3: no-slug (free-form proposal) -----
331
- run_one "no-slug" "no-slug" "claude" "no" ""
332
- run_one "no-slug" "no-slug" "codex" "no" ""
333
-
334
- # ----- Branch 4: no-cli (folder present, CLI absent) -----
335
- # Strip $SANDBOX/bin from PATH so `command -v openspec` fails. Folder is
336
- # still there in $SANDBOX/project/openspec. Hook must short-circuit at
337
- # step 4 (CLI gate) and emit no archive reminder.
338
- run_one "no-cli" "no-cli" "claude" "no" ""
339
- run_one "no-cli" "no-cli" "codex" "no" ""
340
-
341
- # ----- Branch 5: no-folder (CLI present, folder absent) -----
342
- # CLAUDE_PROJECT_DIR points at $SANDBOX (no openspec/ dir there). Hook
343
- # must short-circuit at step 4 (folder gate) before trying any MCP calls.
344
- # We don't actually exercise any MCP fixture here the hook should bail
345
- # before reaching the wrappers. We pass "positive" only because the
346
- # unhandled-fixture branch in the shim returns an empty body and the hook
347
- # would then silent-skip on get_task. Either way the assertion ("no
348
- # archive reminder") holds.
349
- run_one "no-folder" "no-folder" "claude" "no" ""
350
- run_one "no-folder" "no-folder" "codex" "no" ""
351
-
352
- # ----- Branch 6: mode-off (explicit opt-out via env) -----
353
- # Both folder and CLI are present, but CHORUS_OPENSPEC_MODE=off. Hook
354
- # must honor the opt-out and exit silently.
355
- run_one "mode-off" "mode-off" "claude" "no" ""
356
- run_one "mode-off" "mode-off" "codex" "no" ""
537
+ # ===== Branch A: OpenSpec archive trigger fixtures =====
538
+ # (assertion: must-contain `openspec archive my-feature` / must-not-contain `openspec archive`)
539
+
540
+ # A1: positive (slug + last task + CLI)
541
+ run_one "positive" "positive" "claude" "must-contain" "openspec archive my-feature"
542
+ run_one "positive" "positive" "codex" "must-contain" "openspec archive my-feature"
543
+
544
+ # A2: not-last-task
545
+ run_one "not-last-task" "not-last-task" "claude" "must-not-contain" "" "openspec archive"
546
+ run_one "not-last-task" "not-last-task" "codex" "must-not-contain" "" "openspec archive"
547
+
548
+ # A3: no-slug (free-form proposal)
549
+ run_one "no-slug" "no-slug" "claude" "must-not-contain" "" "openspec archive"
550
+ run_one "no-slug" "no-slug" "codex" "must-not-contain" "" "openspec archive"
551
+
552
+ # A4: no-cli (folder present, CLI absent). Strip $SANDBOX/bin from PATH
553
+ # so `command -v openspec` fails. Folder is still there in
554
+ # $SANDBOX/project/openspec. Hook must short-circuit at the CLI gate.
555
+ run_one "no-cli" "no-cli" "claude" "must-not-contain" "" "openspec archive"
556
+ run_one "no-cli" "no-cli" "codex" "must-not-contain" "" "openspec archive"
557
+
558
+ # A5: no-folder (CLI present, folder absent). CLAUDE_PROJECT_DIR points
559
+ # at $SANDBOX (no openspec/ dir there). Hook must short-circuit at the
560
+ # folder gate before reaching any MCP call.
561
+ run_one "no-folder" "no-folder" "claude" "must-not-contain" "" "openspec archive"
562
+ run_one "no-folder" "no-folder" "codex" "must-not-contain" "" "openspec archive"
563
+
564
+ # A6: mode-off (explicit opt-out via env). Both folder and CLI are
565
+ # present, but CHORUS_OPENSPEC_MODE=off. Hook must honor the opt-out.
566
+ run_one "mode-off" "mode-off" "claude" "must-not-contain" "" "openspec archive"
567
+ run_one "mode-off" "mode-off" "codex" "must-not-contain" "" "openspec archive"
568
+
569
+ # ===== Branch B: Idea-completion report reminder fixtures =====
570
+ # (assertion: must-contain `create idea-completion report` /
571
+ # must-not-contain `create idea-completion report`)
572
+ # These fixtures all use a non-OpenSpec proposal description (no slug
573
+ # line), so Branch A silent-skips. The PATH still has the fake openspec
574
+ # CLI and the synthetic project dir still has openspec/ so Branch A's
575
+ # folder/CLI gates pass — the slug gate is what blocks Branch A here,
576
+ # isolating the assertion to Branch B's behavior.
577
+
578
+ # B1: report-positive — last task of an Idea verified, no report
579
+ # Document yet -> reminder MUST fire.
580
+ run_one "report-positive" "report-positive" "claude" "must-contain" "create idea-completion report"
581
+ run_one "report-positive" "report-positive" "codex" "must-contain" "create idea-completion report"
582
+
583
+ # B2: report-existing — same conditions but a report Document already
584
+ # exists for the Idea -> reminder MUST be silent.
585
+ run_one "report-existing" "report-existing" "claude" "must-not-contain" "" "create idea-completion report"
586
+ run_one "report-existing" "report-existing" "codex" "must-not-contain" "" "create idea-completion report"
587
+
588
+ # B3: report-not-last — at least one task is still in_progress
589
+ # (non-terminal) -> reminder MUST be silent.
590
+ run_one "report-not-last" "report-not-last" "claude" "must-not-contain" "" "create idea-completion report"
591
+ run_one "report-not-last" "report-not-last" "codex" "must-not-contain" "" "create idea-completion report"
592
+
593
+ # B4: report-quick-task — proposal.inputType != "idea" (e.g. a
594
+ # manually-created proposal) -> reminder MUST be silent at first gate.
595
+ run_one "report-quick-task" "report-quick-task" "claude" "must-not-contain" "" "create idea-completion report"
596
+ run_one "report-quick-task" "report-quick-task" "codex" "must-not-contain" "" "create idea-completion report"
597
+
598
+ # B5: report-task-overflow — chorus_list_tasks total > returned (more
599
+ # tasks remain on later pages). Hook must refuse to assume "all done"
600
+ # and silent-skip Branch B.
601
+ run_one "report-task-overflow" "report-task-overflow" "claude" "must-not-contain" "" "create idea-completion report"
602
+ run_one "report-task-overflow" "report-task-overflow" "codex" "must-not-contain" "" "create idea-completion report"
603
+
604
+ # B6: report-doc-overflow — chorus_get_documents total > returned (the
605
+ # proposal's existing report could be on a later page). Hook must refuse
606
+ # to assume "no report" and silent-skip Branch B.
607
+ run_one "report-doc-overflow" "report-doc-overflow" "claude" "must-not-contain" "" "create idea-completion report"
608
+ run_one "report-doc-overflow" "report-doc-overflow" "codex" "must-not-contain" "" "create idea-completion report"
357
609
 
358
610
  echo ""
359
611
  echo "Results: $PASS passed, $FAIL failed"
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: brainstorm
3
+ description: Optional divergent-then-convergent dialogue for fuzzy ideas. Invoked from the idea skill as a prelude to structured elaboration; produces one ElaborationRound of decision-point Q&A and returns control. Never writes files, never posts comments, never validates elaboration.
4
+ license: AGPL-3.0
5
+ metadata:
6
+ author: chorus
7
+ version: "0.9.0"
8
+ category: project-management
9
+ mcp_server: chorus
10
+ ---
11
+
12
+ # Brainstorm Skill
13
+
14
+ A divergent-then-convergent dialogue cadence for ideas whose direction is still being formed. Compresses the conversation into one `ElaborationRound` of decision-point Q&A — same shape as a structured elaboration round, but the questions, options and answers are synthesized at the end of the conversation rather than asked up front.
15
+
16
+ This skill is a **producer** of one elaboration round; the **scheduler** decision (validate vs. follow-up) belongs to the calling idea skill.
17
+
18
+ ---
19
+
20
+ ## When invoked
21
+
22
+ Only as a sub-step of the idea skill, only after the user has explicitly opted in via `AskUserQuestion`. Never run standalone, never run without user opt-in. The expected entry point is the idea skill's "Step 4.5: Brainstorm Mode (Optional Prelude)" — see the idea skill for the surrounding flow.
23
+
24
+ ---
25
+
26
+ ## Hard rules
27
+
28
+ 1. **One question at a time.** Each `AskUserQuestion` call MUST contain exactly one question entry. Wait for the answer before asking the next.
29
+ 2. **Multi-choice preferred.** Frame each question as 2-4 options where possible. Open-ended is acceptable when options would be premature, but lean toward concrete choices.
30
+ 3. **Propose 2-3 directions before stopping divergence.** Once the requirement direction is clear enough to enumerate, present 2-3 distinct approaches in a single `AskUserQuestion`. Mark exactly one as the recommended option per the host tool's `AskUserQuestion` recommendation convention (the spec does not dictate a specific marking format — follow the tool's documentation).
31
+ 4. **Explicit user approval required to exit divergence.** Do NOT proceed to synthesis until the user has selected one of the proposed directions.
32
+ 5. **No files written.** Do NOT write any markdown, design doc, scratch file, or any other file to disk. The conversation produces an `ElaborationRound` and nothing else on disk.
33
+ 6. **No comments posted.** Do NOT call `chorus_add_comment` from this skill. Comments belong to the idea skill or the user, not to the brainstorm step.
34
+ 7. **No design-doc handoff.** Do NOT invoke `writing-plans`, `writing-skills`, or any skill whose purpose is to produce a design document. The brainstorm output is the synthesized round — there is no separate doc.
35
+ 8. **No `validate_elaboration` call.** Do NOT call `chorus_pm_validate_elaboration` from this skill. Whether to validate (resolve elaboration) or follow up (issues + followUpQuestions) is the calling idea skill's decision, not this skill's.
36
+
37
+ ---
38
+
39
+ ## Step-by-step
40
+
41
+ ### 1. Gather context
42
+
43
+ Before asking the first divergent question, read the idea and surrounding project state. Mirror the idea skill's gather-context list:
44
+
45
+ ```
46
+ chorus_get_idea({ ideaUuid })
47
+ chorus_get_documents({ projectUuid })
48
+ chorus_get_document({ documentUuid }) # for any document worth reading in full
49
+ chorus_get_proposals({ projectUuid, status: "approved" }) # to understand patterns
50
+ chorus_list_tasks({ projectUuid }) # to avoid duplicating existing work
51
+ chorus_get_comments({ targetType: "idea", targetUuid: ideaUuid })
52
+ ```
53
+
54
+ Skim each result for: stated background, stated requirements, stated constraints, and what is conspicuously NOT stated. The gaps are the questions worth asking.
55
+
56
+ ### 2. Divergent Q&A
57
+
58
+ Ask one question at a time via `AskUserQuestion`. Aim to surface:
59
+
60
+ - The **goal** the idea is trying to serve (often more abstract than the idea statement).
61
+ - The **constraints** that exclude entire branches of solution space (deadlines, compatibility, scope).
62
+ - The **success criteria** — how will the user know this is done.
63
+
64
+ Keep each question single-purpose. If you need to ask three things, that is three rounds, not one combined `AskUserQuestion`.
65
+
66
+ ### 3. Propose 2-3 directions
67
+
68
+ When the goal, constraints, and success criteria are clear enough that you can name distinct approaches, present them in a single `AskUserQuestion`:
69
+
70
+ ```
71
+ AskUserQuestion({
72
+ questions: [
73
+ {
74
+ question: "<the convergence question>",
75
+ header: "<short header>",
76
+ options: [
77
+ { label: "Option A (Recommended)", description: "<what + tradeoff>" },
78
+ { label: "Option B", description: "<what + tradeoff>" },
79
+ { label: "Option C", description: "<what + tradeoff>" }
80
+ ],
81
+ multiSelect: false
82
+ }
83
+ ]
84
+ })
85
+ ```
86
+
87
+ The recommendation must be visibly marked to the user using the host tool's `AskUserQuestion` convention. State **why** you recommend it — usually a sentence about the dominant tradeoff.
88
+
89
+ ### 4. Wait for explicit approval
90
+
91
+ Do not proceed to synthesis if the user has not selected one of the options. If the user picks "Other" with free text, treat that as a new constraint — go back to step 2 or step 3 with the refined direction.
92
+
93
+ ### 5. Synthesize decision-point Q&A
94
+
95
+ For each material decision the user made during the conversation, build one `ElaborationQuestion`. A "material decision" is a moment where the user chose between alternatives or set scope explicitly. Map each decision per the synthesis spec below.
96
+
97
+ ### 6. Persist the round
98
+
99
+ Call `chorus_pm_start_elaboration` with the synthesized questions:
100
+
101
+ ```
102
+ chorus_pm_start_elaboration({
103
+ ideaUuid,
104
+ depth: "standard",
105
+ questions: [
106
+ { id: "q1", text: "...", category: "...", options: [...] },
107
+ ...
108
+ ]
109
+ })
110
+ ```
111
+
112
+ Then submit the answers in one call:
113
+
114
+ ```
115
+ chorus_answer_elaboration({
116
+ ideaUuid,
117
+ roundUuid,
118
+ answers: [
119
+ { questionId: "q1", selectedOptionId: "...", customText: "<rationale>" },
120
+ ...
121
+ ]
122
+ })
123
+ ```
124
+
125
+ ### 7. Return control
126
+
127
+ Stop here. Do **NOT** call `chorus_pm_validate_elaboration`. The idea skill's caller now decides:
128
+
129
+ - If the synthesized round answers cover everything → caller validates with `issues: []`.
130
+ - If gaps remain → caller validates with `issues + followUpQuestions` to start a structured Round 2.
131
+
132
+ The depth of any follow-up round is the caller's call, not yours.
133
+
134
+ ---
135
+
136
+ ## Synthesis spec
137
+
138
+ Each material decision becomes exactly one `ElaborationQuestion` with these fields:
139
+
140
+ | Field | Source |
141
+ |---|---|
142
+ | `text` | The decision question, phrased neutrally. Example: "Which depth-model placement?" |
143
+ | `category` | `functional`, `non_functional`, `business_context`, `technical_context`, `user_scenario`, or `scope` — derived from the topic. |
144
+ | `options` | All directions that were considered, length 2-5. Collapse near-duplicates into one option. |
145
+ | `selectedOptionId` | The id of the option the user approved. |
146
+ | `customText` | A 1-3 sentence rationale capturing the constraint or tradeoff that drove the choice. Not a transcript dump. |
147
+
148
+ Rules:
149
+
150
+ - A `customText` longer than ~3 sentences is a sign you are summarizing transcript instead of capturing rationale. Cut.
151
+ - An `options` array of length 2 with binary "yes / no" framing is a sign you pre-narrowed alternatives. Re-examine — there are usually at least three meaningfully different paths, even if two of them get rejected quickly.
152
+ - Skip "decisions" that were never genuinely contested. If the user agreed instantly to the only proposal, that is information for the idea content, not a decision-point Q&A.
153
+
154
+ ---
155
+
156
+ ## Anti-patterns
157
+
158
+ Do not do any of the following. Each has a specific failure mode that this skill must prevent:
159
+
160
+ - **Single-summary `customText` blob.** Compressing the entire conversation into one ElaborationQuestion with a long markdown summary in `customText`. The schema is multi-question for a reason — preserve the decision granularity.
161
+ - **Transcript-as-comment.** Posting the raw conversation log as a comment on the idea (or anywhere). The synthesized round IS the artifact. Raw transcripts pollute the audit trail with noise.
162
+ - **File writes.** Writing any markdown, design doc, plan, or scratch file to disk. There is no design doc in this flow. This is a deliberate divergence from the upstream `superpowers/brainstorming` cadence — read the design.md if confused.
163
+ - **`validate_elaboration` calls.** Closing the elaboration phase from this skill. The lifecycle decision belongs to the idea skill. Calling validate here strips the caller of its scheduler role.
164
+ - **`writing-plans` / design-doc handoff.** Invoking any skill that produces an implementation plan or design document. The Chorus pipeline already has Proposal → Document Drafts → Task Drafts for that — the brainstorm output feeds them through ElaborationRound, not through external doc skills.
165
+ - **Length-2 binary "yes / no" framings.** Reducing every decision to "do this thing — yes / no". Almost always the genuine alternatives are 3+ approaches with meaningfully different tradeoffs. Length-2 framings often mean the divergent phase ended too early.
166
+ - **Asking multiple questions in one `AskUserQuestion`.** The cadence is one question per turn during divergence, then one final convergence question with 2-3 options. Combining unrelated questions is a sign you are rushing.