@blxzer/cursor-trellis 0.1.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 (687) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +235 -0
  3. package/README.md +9 -0
  4. package/bin/smart-search.js +79 -0
  5. package/bin/trellis.js +3 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +258 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/commands/channel/adapters/claude.d.ts +29 -0
  11. package/dist/commands/channel/adapters/claude.d.ts.map +1 -0
  12. package/dist/commands/channel/adapters/claude.js +203 -0
  13. package/dist/commands/channel/adapters/claude.js.map +1 -0
  14. package/dist/commands/channel/adapters/codex.d.ts +85 -0
  15. package/dist/commands/channel/adapters/codex.d.ts.map +1 -0
  16. package/dist/commands/channel/adapters/codex.js +505 -0
  17. package/dist/commands/channel/adapters/codex.js.map +1 -0
  18. package/dist/commands/channel/adapters/index.d.ts +84 -0
  19. package/dist/commands/channel/adapters/index.d.ts.map +1 -0
  20. package/dist/commands/channel/adapters/index.js +115 -0
  21. package/dist/commands/channel/adapters/index.js.map +1 -0
  22. package/dist/commands/channel/adapters/types.d.ts +33 -0
  23. package/dist/commands/channel/adapters/types.d.ts.map +1 -0
  24. package/dist/commands/channel/adapters/types.js +2 -0
  25. package/dist/commands/channel/adapters/types.js.map +1 -0
  26. package/dist/commands/channel/agent-loader.d.ts +32 -0
  27. package/dist/commands/channel/agent-loader.d.ts.map +1 -0
  28. package/dist/commands/channel/agent-loader.js +154 -0
  29. package/dist/commands/channel/agent-loader.js.map +1 -0
  30. package/dist/commands/channel/context-loader.d.ts +26 -0
  31. package/dist/commands/channel/context-loader.d.ts.map +1 -0
  32. package/dist/commands/channel/context-loader.js +290 -0
  33. package/dist/commands/channel/context-loader.js.map +1 -0
  34. package/dist/commands/channel/context.d.ts +16 -0
  35. package/dist/commands/channel/context.d.ts.map +1 -0
  36. package/dist/commands/channel/context.js +83 -0
  37. package/dist/commands/channel/context.js.map +1 -0
  38. package/dist/commands/channel/create.d.ts +27 -0
  39. package/dist/commands/channel/create.d.ts.map +1 -0
  40. package/dist/commands/channel/create.js +39 -0
  41. package/dist/commands/channel/create.js.map +1 -0
  42. package/dist/commands/channel/dev-parse-trace.d.ts +14 -0
  43. package/dist/commands/channel/dev-parse-trace.d.ts.map +1 -0
  44. package/dist/commands/channel/dev-parse-trace.js +70 -0
  45. package/dist/commands/channel/dev-parse-trace.js.map +1 -0
  46. package/dist/commands/channel/guard.d.ts +150 -0
  47. package/dist/commands/channel/guard.d.ts.map +1 -0
  48. package/dist/commands/channel/guard.js +474 -0
  49. package/dist/commands/channel/guard.js.map +1 -0
  50. package/dist/commands/channel/index.d.ts +3 -0
  51. package/dist/commands/channel/index.d.ts.map +1 -0
  52. package/dist/commands/channel/index.js +531 -0
  53. package/dist/commands/channel/index.js.map +1 -0
  54. package/dist/commands/channel/interrupt.d.ts +10 -0
  55. package/dist/commands/channel/interrupt.d.ts.map +1 -0
  56. package/dist/commands/channel/interrupt.js +22 -0
  57. package/dist/commands/channel/interrupt.js.map +1 -0
  58. package/dist/commands/channel/kill.d.ts +7 -0
  59. package/dist/commands/channel/kill.d.ts.map +1 -0
  60. package/dist/commands/channel/kill.js +121 -0
  61. package/dist/commands/channel/kill.js.map +1 -0
  62. package/dist/commands/channel/list.d.ts +17 -0
  63. package/dist/commands/channel/list.d.ts.map +1 -0
  64. package/dist/commands/channel/list.js +233 -0
  65. package/dist/commands/channel/list.js.map +1 -0
  66. package/dist/commands/channel/messages.d.ts +15 -0
  67. package/dist/commands/channel/messages.d.ts.map +1 -0
  68. package/dist/commands/channel/messages.js +245 -0
  69. package/dist/commands/channel/messages.js.map +1 -0
  70. package/dist/commands/channel/rm.d.ts +27 -0
  71. package/dist/commands/channel/rm.d.ts.map +1 -0
  72. package/dist/commands/channel/rm.js +216 -0
  73. package/dist/commands/channel/rm.js.map +1 -0
  74. package/dist/commands/channel/run.d.ts +30 -0
  75. package/dist/commands/channel/run.d.ts.map +1 -0
  76. package/dist/commands/channel/run.js +130 -0
  77. package/dist/commands/channel/run.js.map +1 -0
  78. package/dist/commands/channel/send.d.ts +11 -0
  79. package/dist/commands/channel/send.d.ts.map +1 -0
  80. package/dist/commands/channel/send.js +24 -0
  81. package/dist/commands/channel/send.js.map +1 -0
  82. package/dist/commands/channel/spawn.d.ts +40 -0
  83. package/dist/commands/channel/spawn.d.ts.map +1 -0
  84. package/dist/commands/channel/spawn.js +244 -0
  85. package/dist/commands/channel/spawn.js.map +1 -0
  86. package/dist/commands/channel/store/events.d.ts +39 -0
  87. package/dist/commands/channel/store/events.d.ts.map +1 -0
  88. package/dist/commands/channel/store/events.js +87 -0
  89. package/dist/commands/channel/store/events.js.map +1 -0
  90. package/dist/commands/channel/store/filter.d.ts +3 -0
  91. package/dist/commands/channel/store/filter.d.ts.map +1 -0
  92. package/dist/commands/channel/store/filter.js +2 -0
  93. package/dist/commands/channel/store/filter.js.map +1 -0
  94. package/dist/commands/channel/store/lock.d.ts +23 -0
  95. package/dist/commands/channel/store/lock.d.ts.map +1 -0
  96. package/dist/commands/channel/store/lock.js +99 -0
  97. package/dist/commands/channel/store/lock.js.map +1 -0
  98. package/dist/commands/channel/store/paths.d.ts +63 -0
  99. package/dist/commands/channel/store/paths.d.ts.map +1 -0
  100. package/dist/commands/channel/store/paths.js +246 -0
  101. package/dist/commands/channel/store/paths.js.map +1 -0
  102. package/dist/commands/channel/store/schema.d.ts +27 -0
  103. package/dist/commands/channel/store/schema.d.ts.map +1 -0
  104. package/dist/commands/channel/store/schema.js +34 -0
  105. package/dist/commands/channel/store/schema.js.map +1 -0
  106. package/dist/commands/channel/store/thread-state.d.ts +5 -0
  107. package/dist/commands/channel/store/thread-state.d.ts.map +1 -0
  108. package/dist/commands/channel/store/thread-state.js +16 -0
  109. package/dist/commands/channel/store/thread-state.js.map +1 -0
  110. package/dist/commands/channel/store/watch.d.ts +19 -0
  111. package/dist/commands/channel/store/watch.d.ts.map +1 -0
  112. package/dist/commands/channel/store/watch.js +146 -0
  113. package/dist/commands/channel/store/watch.js.map +1 -0
  114. package/dist/commands/channel/supervisor/idle.d.ts +46 -0
  115. package/dist/commands/channel/supervisor/idle.d.ts.map +1 -0
  116. package/dist/commands/channel/supervisor/idle.js +72 -0
  117. package/dist/commands/channel/supervisor/idle.js.map +1 -0
  118. package/dist/commands/channel/supervisor/inbox.d.ts +30 -0
  119. package/dist/commands/channel/supervisor/inbox.d.ts.map +1 -0
  120. package/dist/commands/channel/supervisor/inbox.js +160 -0
  121. package/dist/commands/channel/supervisor/inbox.js.map +1 -0
  122. package/dist/commands/channel/supervisor/shutdown.d.ts +68 -0
  123. package/dist/commands/channel/supervisor/shutdown.d.ts.map +1 -0
  124. package/dist/commands/channel/supervisor/shutdown.js +146 -0
  125. package/dist/commands/channel/supervisor/shutdown.js.map +1 -0
  126. package/dist/commands/channel/supervisor/stdout.d.ts +51 -0
  127. package/dist/commands/channel/supervisor/stdout.d.ts.map +1 -0
  128. package/dist/commands/channel/supervisor/stdout.js +121 -0
  129. package/dist/commands/channel/supervisor/stdout.js.map +1 -0
  130. package/dist/commands/channel/supervisor/turns.d.ts +31 -0
  131. package/dist/commands/channel/supervisor/turns.d.ts.map +1 -0
  132. package/dist/commands/channel/supervisor/turns.js +45 -0
  133. package/dist/commands/channel/supervisor/turns.js.map +1 -0
  134. package/dist/commands/channel/supervisor/warning.d.ts +48 -0
  135. package/dist/commands/channel/supervisor/warning.d.ts.map +1 -0
  136. package/dist/commands/channel/supervisor/warning.js +77 -0
  137. package/dist/commands/channel/supervisor/warning.js.map +1 -0
  138. package/dist/commands/channel/supervisor.d.ts +59 -0
  139. package/dist/commands/channel/supervisor.d.ts.map +1 -0
  140. package/dist/commands/channel/supervisor.js +344 -0
  141. package/dist/commands/channel/supervisor.js.map +1 -0
  142. package/dist/commands/channel/text-body.d.ts +13 -0
  143. package/dist/commands/channel/text-body.d.ts.map +1 -0
  144. package/dist/commands/channel/text-body.js +47 -0
  145. package/dist/commands/channel/text-body.js.map +1 -0
  146. package/dist/commands/channel/threads.d.ts +39 -0
  147. package/dist/commands/channel/threads.d.ts.map +1 -0
  148. package/dist/commands/channel/threads.js +106 -0
  149. package/dist/commands/channel/threads.js.map +1 -0
  150. package/dist/commands/channel/title.d.ts +12 -0
  151. package/dist/commands/channel/title.d.ts.map +1 -0
  152. package/dist/commands/channel/title.js +24 -0
  153. package/dist/commands/channel/title.js.map +1 -0
  154. package/dist/commands/channel/wait.d.ts +17 -0
  155. package/dist/commands/channel/wait.d.ts.map +1 -0
  156. package/dist/commands/channel/wait.js +75 -0
  157. package/dist/commands/channel/wait.js.map +1 -0
  158. package/dist/commands/init.d.ts +62 -0
  159. package/dist/commands/init.d.ts.map +1 -0
  160. package/dist/commands/init.js +1656 -0
  161. package/dist/commands/init.js.map +1 -0
  162. package/dist/commands/mem.d.ts +30 -0
  163. package/dist/commands/mem.d.ts.map +1 -0
  164. package/dist/commands/mem.js +424 -0
  165. package/dist/commands/mem.js.map +1 -0
  166. package/dist/commands/rollout.d.ts +34 -0
  167. package/dist/commands/rollout.d.ts.map +1 -0
  168. package/dist/commands/rollout.js +102 -0
  169. package/dist/commands/rollout.js.map +1 -0
  170. package/dist/commands/uninstall.d.ts +27 -0
  171. package/dist/commands/uninstall.d.ts.map +1 -0
  172. package/dist/commands/uninstall.js +339 -0
  173. package/dist/commands/uninstall.js.map +1 -0
  174. package/dist/commands/update.d.ts +80 -0
  175. package/dist/commands/update.d.ts.map +1 -0
  176. package/dist/commands/update.js +2185 -0
  177. package/dist/commands/update.js.map +1 -0
  178. package/dist/commands/upgrade.d.ts +28 -0
  179. package/dist/commands/upgrade.d.ts.map +1 -0
  180. package/dist/commands/upgrade.js +84 -0
  181. package/dist/commands/upgrade.js.map +1 -0
  182. package/dist/commands/workflow.d.ts +35 -0
  183. package/dist/commands/workflow.d.ts.map +1 -0
  184. package/dist/commands/workflow.js +219 -0
  185. package/dist/commands/workflow.js.map +1 -0
  186. package/dist/configurators/antigravity.d.ts +7 -0
  187. package/dist/configurators/antigravity.d.ts.map +1 -0
  188. package/dist/configurators/antigravity.js +19 -0
  189. package/dist/configurators/antigravity.js.map +1 -0
  190. package/dist/configurators/claude.d.ts +9 -0
  191. package/dist/configurators/claude.d.ts.map +1 -0
  192. package/dist/configurators/claude.js +72 -0
  193. package/dist/configurators/claude.js.map +1 -0
  194. package/dist/configurators/codebuddy.d.ts +10 -0
  195. package/dist/configurators/codebuddy.d.ts.map +1 -0
  196. package/dist/configurators/codebuddy.js +30 -0
  197. package/dist/configurators/codebuddy.js.map +1 -0
  198. package/dist/configurators/codex.d.ts +8 -0
  199. package/dist/configurators/codex.d.ts.map +1 -0
  200. package/dist/configurators/codex.js +87 -0
  201. package/dist/configurators/codex.js.map +1 -0
  202. package/dist/configurators/copilot.d.ts +10 -0
  203. package/dist/configurators/copilot.d.ts.map +1 -0
  204. package/dist/configurators/copilot.js +51 -0
  205. package/dist/configurators/copilot.js.map +1 -0
  206. package/dist/configurators/cursor.d.ts +20 -0
  207. package/dist/configurators/cursor.d.ts.map +1 -0
  208. package/dist/configurators/cursor.js +49 -0
  209. package/dist/configurators/cursor.js.map +1 -0
  210. package/dist/configurators/cursor2plus-local.d.ts +6 -0
  211. package/dist/configurators/cursor2plus-local.d.ts.map +1 -0
  212. package/dist/configurators/cursor2plus-local.js +20 -0
  213. package/dist/configurators/cursor2plus-local.js.map +1 -0
  214. package/dist/configurators/droid.d.ts +10 -0
  215. package/dist/configurators/droid.d.ts.map +1 -0
  216. package/dist/configurators/droid.js +30 -0
  217. package/dist/configurators/droid.js.map +1 -0
  218. package/dist/configurators/gemini.d.ts +16 -0
  219. package/dist/configurators/gemini.d.ts.map +1 -0
  220. package/dist/configurators/gemini.js +38 -0
  221. package/dist/configurators/gemini.js.map +1 -0
  222. package/dist/configurators/index.d.ts +69 -0
  223. package/dist/configurators/index.d.ts.map +1 -0
  224. package/dist/configurators/index.js +395 -0
  225. package/dist/configurators/index.js.map +1 -0
  226. package/dist/configurators/kilo.d.ts +7 -0
  227. package/dist/configurators/kilo.d.ts.map +1 -0
  228. package/dist/configurators/kilo.js +19 -0
  229. package/dist/configurators/kilo.js.map +1 -0
  230. package/dist/configurators/kiro.d.ts +8 -0
  231. package/dist/configurators/kiro.d.ts.map +1 -0
  232. package/dist/configurators/kiro.js +24 -0
  233. package/dist/configurators/kiro.js.map +1 -0
  234. package/dist/configurators/opencode.d.ts +14 -0
  235. package/dist/configurators/opencode.d.ts.map +1 -0
  236. package/dist/configurators/opencode.js +96 -0
  237. package/dist/configurators/opencode.js.map +1 -0
  238. package/dist/configurators/pi.d.ts +3 -0
  239. package/dist/configurators/pi.d.ts.map +1 -0
  240. package/dist/configurators/pi.js +45 -0
  241. package/dist/configurators/pi.js.map +1 -0
  242. package/dist/configurators/qoder.d.ts +11 -0
  243. package/dist/configurators/qoder.d.ts.map +1 -0
  244. package/dist/configurators/qoder.js +31 -0
  245. package/dist/configurators/qoder.js.map +1 -0
  246. package/dist/configurators/shared.d.ts +183 -0
  247. package/dist/configurators/shared.d.ts.map +1 -0
  248. package/dist/configurators/shared.js +557 -0
  249. package/dist/configurators/shared.js.map +1 -0
  250. package/dist/configurators/windsurf.d.ts +7 -0
  251. package/dist/configurators/windsurf.d.ts.map +1 -0
  252. package/dist/configurators/windsurf.js +19 -0
  253. package/dist/configurators/windsurf.js.map +1 -0
  254. package/dist/configurators/workflow.d.ts +46 -0
  255. package/dist/configurators/workflow.d.ts.map +1 -0
  256. package/dist/configurators/workflow.js +180 -0
  257. package/dist/configurators/workflow.js.map +1 -0
  258. package/dist/constants/paths.d.ts +70 -0
  259. package/dist/constants/paths.d.ts.map +1 -0
  260. package/dist/constants/paths.js +79 -0
  261. package/dist/constants/paths.js.map +1 -0
  262. package/dist/constants/version.d.ts +9 -0
  263. package/dist/constants/version.d.ts.map +1 -0
  264. package/dist/constants/version.js +15 -0
  265. package/dist/constants/version.js.map +1 -0
  266. package/dist/index.d.ts +9 -0
  267. package/dist/index.d.ts.map +1 -0
  268. package/dist/index.js +9 -0
  269. package/dist/index.js.map +1 -0
  270. package/dist/migrations/index.d.ts +62 -0
  271. package/dist/migrations/index.d.ts.map +1 -0
  272. package/dist/migrations/index.js +187 -0
  273. package/dist/migrations/index.js.map +1 -0
  274. package/dist/migrations/manifests/0.1.0.json +9 -0
  275. package/dist/templates/claude/agents/trellis-check.md +124 -0
  276. package/dist/templates/claude/agents/trellis-implement.md +110 -0
  277. package/dist/templates/claude/agents/trellis-research.md +137 -0
  278. package/dist/templates/claude/index.d.ts +22 -0
  279. package/dist/templates/claude/index.d.ts.map +1 -0
  280. package/dist/templates/claude/index.js +46 -0
  281. package/dist/templates/claude/index.js.map +1 -0
  282. package/dist/templates/claude/settings.json +84 -0
  283. package/dist/templates/codebuddy/agents/trellis-check.md +115 -0
  284. package/dist/templates/codebuddy/agents/trellis-implement.md +110 -0
  285. package/dist/templates/codebuddy/agents/trellis-research.md +137 -0
  286. package/dist/templates/codebuddy/index.d.ts +15 -0
  287. package/dist/templates/codebuddy/index.d.ts.map +1 -0
  288. package/dist/templates/codebuddy/index.js +15 -0
  289. package/dist/templates/codebuddy/index.js.map +1 -0
  290. package/dist/templates/codebuddy/settings.json +70 -0
  291. package/dist/templates/codex/agents/trellis-check.toml +67 -0
  292. package/dist/templates/codex/agents/trellis-implement.toml +40 -0
  293. package/dist/templates/codex/agents/trellis-research.toml +73 -0
  294. package/dist/templates/codex/config.toml +28 -0
  295. package/dist/templates/codex/hooks/session-start.py +575 -0
  296. package/dist/templates/codex/hooks.json +15 -0
  297. package/dist/templates/codex/index.d.ts +39 -0
  298. package/dist/templates/codex/index.d.ts.map +1 -0
  299. package/dist/templates/codex/index.js +85 -0
  300. package/dist/templates/codex/index.js.map +1 -0
  301. package/dist/templates/codex/skills/before-dev/SKILL.md +40 -0
  302. package/dist/templates/codex/skills/brainstorm/SKILL.md +112 -0
  303. package/dist/templates/codex/skills/break-loop/SKILL.md +130 -0
  304. package/dist/templates/codex/skills/check/SKILL.md +98 -0
  305. package/dist/templates/codex/skills/check-cross-layer/SKILL.md +158 -0
  306. package/dist/templates/codex/skills/create-command/SKILL.md +101 -0
  307. package/dist/templates/codex/skills/finish-work/SKILL.md +90 -0
  308. package/dist/templates/codex/skills/improve-ut/SKILL.md +69 -0
  309. package/dist/templates/codex/skills/integrate-skill/SKILL.md +221 -0
  310. package/dist/templates/codex/skills/onboard/SKILL.md +363 -0
  311. package/dist/templates/codex/skills/record-session/SKILL.md +67 -0
  312. package/dist/templates/codex/skills/start/SKILL.md +66 -0
  313. package/dist/templates/codex/skills/update-spec/SKILL.md +335 -0
  314. package/dist/templates/common/bundled-skills/smart-search-cli/SKILL.md +388 -0
  315. package/dist/templates/common/bundled-skills/smart-search-cli/agents/openai.yaml +3 -0
  316. package/dist/templates/common/bundled-skills/smart-search-cli/examples/batch-search.md +98 -0
  317. package/dist/templates/common/bundled-skills/smart-search-cli/examples/evidence-gathering.md +89 -0
  318. package/dist/templates/common/bundled-skills/smart-search-cli/references/cli-contract.md +337 -0
  319. package/dist/templates/common/bundled-skills/trellis-cursor2plus-setup/SKILL.md +62 -0
  320. package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +73 -0
  321. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/add-project-local-conventions.md +83 -0
  322. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +54 -0
  323. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-context-loading.md +84 -0
  324. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +57 -0
  325. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +80 -0
  326. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-spec-structure.md +83 -0
  327. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +88 -0
  328. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +65 -0
  329. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +55 -0
  330. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/context-injection.md +68 -0
  331. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +80 -0
  332. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/overview.md +51 -0
  333. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/spec-system.md +102 -0
  334. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +133 -0
  335. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workflow.md +75 -0
  336. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workspace-memory.md +71 -0
  337. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +80 -0
  338. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +69 -0
  339. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +61 -0
  340. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +76 -0
  341. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +128 -0
  342. package/dist/templates/common/bundled-skills/trellis-micro-grill/SKILL.md +47 -0
  343. package/dist/templates/common/bundled-skills/trellis-skill-creator/SKILL.md +44 -0
  344. package/dist/templates/common/bundled-skills/trellis-skill-creator/references/authoring-rules.md +97 -0
  345. package/dist/templates/common/bundled-skills/trellis-skill-creator/references/review-checklist.md +47 -0
  346. package/dist/templates/common/bundled-skills/trellis-skill-creator/references/trellis-skill-locations.md +52 -0
  347. package/dist/templates/common/bundled-skills/trellis-spec-bootstrap/SKILL.md +41 -0
  348. package/dist/templates/common/bundled-skills/trellis-spec-bootstrap/references/mcp-setup.md +90 -0
  349. package/dist/templates/common/bundled-skills/trellis-spec-bootstrap/references/repository-analysis.md +59 -0
  350. package/dist/templates/common/bundled-skills/trellis-spec-bootstrap/references/spec-task-planning.md +61 -0
  351. package/dist/templates/common/bundled-skills/trellis-spec-bootstrap/references/spec-writing.md +70 -0
  352. package/dist/templates/common/commands/continue.md +63 -0
  353. package/dist/templates/common/commands/finish-work.md +68 -0
  354. package/dist/templates/common/commands/start.md +69 -0
  355. package/dist/templates/common/index.d.ts +48 -0
  356. package/dist/templates/common/index.d.ts.map +1 -0
  357. package/dist/templates/common/index.js +104 -0
  358. package/dist/templates/common/index.js.map +1 -0
  359. package/dist/templates/common/skills/before-dev.md +35 -0
  360. package/dist/templates/common/skills/brainstorm.md +164 -0
  361. package/dist/templates/common/skills/break-loop.md +125 -0
  362. package/dist/templates/common/skills/check.md +106 -0
  363. package/dist/templates/common/skills/update-spec.md +364 -0
  364. package/dist/templates/copilot/hooks/session-start.py +557 -0
  365. package/dist/templates/copilot/hooks.json +19 -0
  366. package/dist/templates/copilot/index.d.ts +23 -0
  367. package/dist/templates/copilot/index.d.ts.map +1 -0
  368. package/dist/templates/copilot/index.js +54 -0
  369. package/dist/templates/copilot/index.js.map +1 -0
  370. package/dist/templates/copilot/prompts/before-dev.prompt.md +39 -0
  371. package/dist/templates/copilot/prompts/brainstorm.prompt.md +111 -0
  372. package/dist/templates/copilot/prompts/break-loop.prompt.md +129 -0
  373. package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +157 -0
  374. package/dist/templates/copilot/prompts/check.prompt.md +97 -0
  375. package/dist/templates/copilot/prompts/create-command.prompt.md +116 -0
  376. package/dist/templates/copilot/prompts/finish-work.prompt.md +99 -0
  377. package/dist/templates/copilot/prompts/integrate-skill.prompt.md +223 -0
  378. package/dist/templates/copilot/prompts/onboard.prompt.md +362 -0
  379. package/dist/templates/copilot/prompts/parallel.prompt.md +204 -0
  380. package/dist/templates/copilot/prompts/record-session.prompt.md +66 -0
  381. package/dist/templates/copilot/prompts/start.prompt.md +63 -0
  382. package/dist/templates/copilot/prompts/update-spec.prompt.md +358 -0
  383. package/dist/templates/cursor/agents/trellis-check.md +139 -0
  384. package/dist/templates/cursor/agents/trellis-implement.md +119 -0
  385. package/dist/templates/cursor/agents/trellis-research.md +147 -0
  386. package/dist/templates/cursor/commands/cursor2plus-setup.md +82 -0
  387. package/dist/templates/cursor/hooks.json +30 -0
  388. package/dist/templates/cursor/index.d.ts +19 -0
  389. package/dist/templates/cursor/index.d.ts.map +1 -0
  390. package/dist/templates/cursor/index.js +19 -0
  391. package/dist/templates/cursor/index.js.map +1 -0
  392. package/dist/templates/cursor/rules/trellis-triage.mdc +38 -0
  393. package/dist/templates/cursor/worktrees.json +11 -0
  394. package/dist/templates/droid/droids/trellis-check.md +107 -0
  395. package/dist/templates/droid/droids/trellis-implement.md +102 -0
  396. package/dist/templates/droid/droids/trellis-research.md +137 -0
  397. package/dist/templates/droid/index.d.ts +15 -0
  398. package/dist/templates/droid/index.d.ts.map +1 -0
  399. package/dist/templates/droid/index.js +15 -0
  400. package/dist/templates/droid/index.js.map +1 -0
  401. package/dist/templates/droid/settings.json +70 -0
  402. package/dist/templates/extract.d.ts +40 -0
  403. package/dist/templates/extract.d.ts.map +1 -0
  404. package/dist/templates/extract.js +106 -0
  405. package/dist/templates/extract.js.map +1 -0
  406. package/dist/templates/gemini/agents/trellis-check.md +107 -0
  407. package/dist/templates/gemini/agents/trellis-implement.md +102 -0
  408. package/dist/templates/gemini/agents/trellis-research.md +136 -0
  409. package/dist/templates/gemini/index.d.ts +13 -0
  410. package/dist/templates/gemini/index.d.ts.map +1 -0
  411. package/dist/templates/gemini/index.js +13 -0
  412. package/dist/templates/gemini/index.js.map +1 -0
  413. package/dist/templates/gemini/settings.json +28 -0
  414. package/dist/templates/kiro/agents/trellis-check.json +26 -0
  415. package/dist/templates/kiro/agents/trellis-implement.json +26 -0
  416. package/dist/templates/kiro/agents/trellis-research.json +30 -0
  417. package/dist/templates/kiro/index.d.ts +18 -0
  418. package/dist/templates/kiro/index.d.ts.map +1 -0
  419. package/dist/templates/kiro/index.js +18 -0
  420. package/dist/templates/kiro/index.js.map +1 -0
  421. package/dist/templates/markdown/agents.md +32 -0
  422. package/dist/templates/markdown/gitignore.txt +15 -0
  423. package/dist/templates/markdown/index.d.ts +30 -0
  424. package/dist/templates/markdown/index.d.ts.map +1 -0
  425. package/dist/templates/markdown/index.js +55 -0
  426. package/dist/templates/markdown/index.js.map +1 -0
  427. package/dist/templates/markdown/spec/backend/database-guidelines.md.txt +51 -0
  428. package/dist/templates/markdown/spec/backend/directory-structure.md.txt +54 -0
  429. package/dist/templates/markdown/spec/backend/error-handling.md.txt +51 -0
  430. package/dist/templates/markdown/spec/backend/index.md.txt +38 -0
  431. package/dist/templates/markdown/spec/backend/logging-guidelines.md.txt +51 -0
  432. package/dist/templates/markdown/spec/backend/quality-guidelines.md.txt +51 -0
  433. package/dist/templates/markdown/spec/frontend/component-guidelines.md.txt +59 -0
  434. package/dist/templates/markdown/spec/frontend/directory-structure.md.txt +54 -0
  435. package/dist/templates/markdown/spec/frontend/hook-guidelines.md.txt +51 -0
  436. package/dist/templates/markdown/spec/frontend/index.md.txt +39 -0
  437. package/dist/templates/markdown/spec/frontend/quality-guidelines.md.txt +51 -0
  438. package/dist/templates/markdown/spec/frontend/state-management.md.txt +51 -0
  439. package/dist/templates/markdown/spec/frontend/type-safety.md.txt +51 -0
  440. package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +223 -0
  441. package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +259 -0
  442. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +633 -0
  443. package/dist/templates/markdown/spec/guides/cursor-context-injection-guide.md.txt +65 -0
  444. package/dist/templates/markdown/spec/guides/cursor-subagent-policy.md.txt +382 -0
  445. package/dist/templates/markdown/spec/guides/durable-learning-decision-guide.md.txt +76 -0
  446. package/dist/templates/markdown/spec/guides/index.md.txt +101 -0
  447. package/dist/templates/markdown/spec/guides/retrieval-daily-guide.md.txt +92 -0
  448. package/dist/templates/markdown/workspace-index.md +125 -0
  449. package/dist/templates/markdown/worktree.yaml.txt +58 -0
  450. package/dist/templates/opencode/agents/trellis-check.md +122 -0
  451. package/dist/templates/opencode/agents/trellis-implement.md +118 -0
  452. package/dist/templates/opencode/agents/trellis-research.md +145 -0
  453. package/dist/templates/opencode/lib/session-utils.js +667 -0
  454. package/dist/templates/opencode/lib/trellis-context.js +336 -0
  455. package/dist/templates/opencode/package.json +5 -0
  456. package/dist/templates/opencode/plugins/inject-subagent-context.js +495 -0
  457. package/dist/templates/opencode/plugins/inject-workflow-state.js +159 -0
  458. package/dist/templates/opencode/plugins/session-start.js +101 -0
  459. package/dist/templates/pi/agents/trellis-check.md +37 -0
  460. package/dist/templates/pi/agents/trellis-implement.md +42 -0
  461. package/dist/templates/pi/agents/trellis-research.md +25 -0
  462. package/dist/templates/pi/extensions/trellis/index.ts.txt +1600 -0
  463. package/dist/templates/pi/index.d.ts +5 -0
  464. package/dist/templates/pi/index.d.ts.map +1 -0
  465. package/dist/templates/pi/index.js +12 -0
  466. package/dist/templates/pi/index.js.map +1 -0
  467. package/dist/templates/pi/settings.json +12 -0
  468. package/dist/templates/qoder/agents/trellis-check.md +108 -0
  469. package/dist/templates/qoder/agents/trellis-implement.md +103 -0
  470. package/dist/templates/qoder/agents/trellis-research.md +137 -0
  471. package/dist/templates/qoder/index.d.ts +15 -0
  472. package/dist/templates/qoder/index.d.ts.map +1 -0
  473. package/dist/templates/qoder/index.js +15 -0
  474. package/dist/templates/qoder/index.js.map +1 -0
  475. package/dist/templates/qoder/settings.json +47 -0
  476. package/dist/templates/shared-hooks/index.d.ts +50 -0
  477. package/dist/templates/shared-hooks/index.d.ts.map +1 -0
  478. package/dist/templates/shared-hooks/index.js +92 -0
  479. package/dist/templates/shared-hooks/index.js.map +1 -0
  480. package/dist/templates/shared-hooks/inject-shell-session-context.py +183 -0
  481. package/dist/templates/shared-hooks/inject-subagent-context.py +774 -0
  482. package/dist/templates/shared-hooks/inject-workflow-state.py +366 -0
  483. package/dist/templates/shared-hooks/research-end-retrieval-pack.py +220 -0
  484. package/dist/templates/shared-hooks/session-start.py +873 -0
  485. package/dist/templates/template-utils.d.ts +28 -0
  486. package/dist/templates/template-utils.d.ts.map +1 -0
  487. package/dist/templates/template-utils.js +83 -0
  488. package/dist/templates/template-utils.js.map +1 -0
  489. package/dist/templates/trellis/config.yaml +132 -0
  490. package/dist/templates/trellis/gitignore.txt +39 -0
  491. package/dist/templates/trellis/index.d.ts +80 -0
  492. package/dist/templates/trellis/index.d.ts.map +1 -0
  493. package/dist/templates/trellis/index.js +171 -0
  494. package/dist/templates/trellis/index.js.map +1 -0
  495. package/dist/templates/trellis/local/README.md +59 -0
  496. package/dist/templates/trellis/local/config.local.json.example +6 -0
  497. package/dist/templates/trellis/local/index.d.ts +7 -0
  498. package/dist/templates/trellis/local/index.d.ts.map +1 -0
  499. package/dist/templates/trellis/local/index.js +14 -0
  500. package/dist/templates/trellis/local/index.js.map +1 -0
  501. package/dist/templates/trellis/local/patch_wpelc8.py +297 -0
  502. package/dist/templates/trellis/local/subagent-models.json.example +4 -0
  503. package/dist/templates/trellis/local/trellis-task-models.json.example +10 -0
  504. package/dist/templates/trellis/local/trellis-task-models.json5.example +45 -0
  505. package/dist/templates/trellis/local/trellis_task_models_config.py +200 -0
  506. package/dist/templates/trellis/scripts/__init__.py +5 -0
  507. package/dist/templates/trellis/scripts/add_session.py +547 -0
  508. package/dist/templates/trellis/scripts/build_context_pack.py +12 -0
  509. package/dist/templates/trellis/scripts/build_retrieval_pack.py +12 -0
  510. package/dist/templates/trellis/scripts/common/__init__.py +103 -0
  511. package/dist/templates/trellis/scripts/common/active_task.py +607 -0
  512. package/dist/templates/trellis/scripts/common/artifact_search.py +587 -0
  513. package/dist/templates/trellis/scripts/common/cli_adapter.py +811 -0
  514. package/dist/templates/trellis/scripts/common/cli_environment.py +155 -0
  515. package/dist/templates/trellis/scripts/common/codebase_retrieval_router.py +902 -0
  516. package/dist/templates/trellis/scripts/common/config.py +458 -0
  517. package/dist/templates/trellis/scripts/common/context_pack.py +371 -0
  518. package/dist/templates/trellis/scripts/common/developer.py +190 -0
  519. package/dist/templates/trellis/scripts/common/git.py +31 -0
  520. package/dist/templates/trellis/scripts/common/git_context.py +149 -0
  521. package/dist/templates/trellis/scripts/common/io.py +37 -0
  522. package/dist/templates/trellis/scripts/common/log.py +45 -0
  523. package/dist/templates/trellis/scripts/common/packages_context.py +239 -0
  524. package/dist/templates/trellis/scripts/common/parent_orchestration.py +691 -0
  525. package/dist/templates/trellis/scripts/common/paths.py +457 -0
  526. package/dist/templates/trellis/scripts/common/retrieval_adapter_metadata.py +889 -0
  527. package/dist/templates/trellis/scripts/common/retrieval_evidence.py +917 -0
  528. package/dist/templates/trellis/scripts/common/retrieval_pack.py +425 -0
  529. package/dist/templates/trellis/scripts/common/retrieval_pack_context.py +126 -0
  530. package/dist/templates/trellis/scripts/common/safe_commit.py +285 -0
  531. package/dist/templates/trellis/scripts/common/session_context.py +1159 -0
  532. package/dist/templates/trellis/scripts/common/session_memory.py +360 -0
  533. package/dist/templates/trellis/scripts/common/smart_search_evidence.py +479 -0
  534. package/dist/templates/trellis/scripts/common/smart_search_resolve.py +125 -0
  535. package/dist/templates/trellis/scripts/common/task_context.py +223 -0
  536. package/dist/templates/trellis/scripts/common/task_dashboard.py +140 -0
  537. package/dist/templates/trellis/scripts/common/task_gates.py +1735 -0
  538. package/dist/templates/trellis/scripts/common/task_map.py +586 -0
  539. package/dist/templates/trellis/scripts/common/task_queue.py +188 -0
  540. package/dist/templates/trellis/scripts/common/task_store.py +1533 -0
  541. package/dist/templates/trellis/scripts/common/task_utils.py +274 -0
  542. package/dist/templates/trellis/scripts/common/tasks.py +231 -0
  543. package/dist/templates/trellis/scripts/common/test_retrieval_arbitration.py +333 -0
  544. package/dist/templates/trellis/scripts/common/trellis_config.py +131 -0
  545. package/dist/templates/trellis/scripts/common/types.py +110 -0
  546. package/dist/templates/trellis/scripts/common/workflow_phase.py +212 -0
  547. package/dist/templates/trellis/scripts/get_context.py +17 -0
  548. package/dist/templates/trellis/scripts/get_developer.py +26 -0
  549. package/dist/templates/trellis/scripts/hooks/linear_sync.py +243 -0
  550. package/dist/templates/trellis/scripts/init_developer.py +51 -0
  551. package/dist/templates/trellis/scripts/route_codebase_retrieval.py +77 -0
  552. package/dist/templates/trellis/scripts/run_smart_search.py +12 -0
  553. package/dist/templates/trellis/scripts/search_artifacts.py +13 -0
  554. package/dist/templates/trellis/scripts/search_memory.py +12 -0
  555. package/dist/templates/trellis/scripts/task.py +851 -0
  556. package/dist/templates/trellis/tasks/.gitkeep +0 -0
  557. package/dist/templates/trellis/tasks/templates/release-execution/design.md +61 -0
  558. package/dist/templates/trellis/tasks/templates/release-execution/handoff-template.md +29 -0
  559. package/dist/templates/trellis/tasks/templates/release-execution/implement.md +29 -0
  560. package/dist/templates/trellis/tasks/templates/release-execution/prd.md +31 -0
  561. package/dist/templates/trellis/tasks/templates/release-readiness/design.md +50 -0
  562. package/dist/templates/trellis/tasks/templates/release-readiness/handoff-template.md +39 -0
  563. package/dist/templates/trellis/tasks/templates/release-readiness/implement.md +29 -0
  564. package/dist/templates/trellis/tasks/templates/release-readiness/prd.md +31 -0
  565. package/dist/templates/trellis/workflow.md +960 -0
  566. package/dist/types/ai-tools.d.ts +106 -0
  567. package/dist/types/ai-tools.d.ts.map +1 -0
  568. package/dist/types/ai-tools.js +294 -0
  569. package/dist/types/ai-tools.js.map +1 -0
  570. package/dist/types/migration.d.ts +125 -0
  571. package/dist/types/migration.d.ts.map +1 -0
  572. package/dist/types/migration.js +8 -0
  573. package/dist/types/migration.js.map +1 -0
  574. package/dist/utils/codebase-retrieval-router.d.ts +77 -0
  575. package/dist/utils/codebase-retrieval-router.d.ts.map +1 -0
  576. package/dist/utils/codebase-retrieval-router.js +767 -0
  577. package/dist/utils/codebase-retrieval-router.js.map +1 -0
  578. package/dist/utils/compare-versions.d.ts +12 -0
  579. package/dist/utils/compare-versions.d.ts.map +1 -0
  580. package/dist/utils/compare-versions.js +86 -0
  581. package/dist/utils/compare-versions.js.map +1 -0
  582. package/dist/utils/cwd-guard.d.ts +38 -0
  583. package/dist/utils/cwd-guard.d.ts.map +1 -0
  584. package/dist/utils/cwd-guard.js +62 -0
  585. package/dist/utils/cwd-guard.js.map +1 -0
  586. package/dist/utils/file-writer.d.ts +36 -0
  587. package/dist/utils/file-writer.d.ts.map +1 -0
  588. package/dist/utils/file-writer.js +203 -0
  589. package/dist/utils/file-writer.js.map +1 -0
  590. package/dist/utils/manifest-prune.d.ts +61 -0
  591. package/dist/utils/manifest-prune.d.ts.map +1 -0
  592. package/dist/utils/manifest-prune.js +136 -0
  593. package/dist/utils/manifest-prune.js.map +1 -0
  594. package/dist/utils/posix.d.ts +13 -0
  595. package/dist/utils/posix.d.ts.map +1 -0
  596. package/dist/utils/posix.js +15 -0
  597. package/dist/utils/posix.js.map +1 -0
  598. package/dist/utils/post-update-smoke.d.ts +7 -0
  599. package/dist/utils/post-update-smoke.d.ts.map +1 -0
  600. package/dist/utils/post-update-smoke.js +40 -0
  601. package/dist/utils/post-update-smoke.js.map +1 -0
  602. package/dist/utils/project-capabilities.d.ts +54 -0
  603. package/dist/utils/project-capabilities.d.ts.map +1 -0
  604. package/dist/utils/project-capabilities.js +515 -0
  605. package/dist/utils/project-capabilities.js.map +1 -0
  606. package/dist/utils/project-detector.d.ts +46 -0
  607. package/dist/utils/project-detector.d.ts.map +1 -0
  608. package/dist/utils/project-detector.js +666 -0
  609. package/dist/utils/project-detector.js.map +1 -0
  610. package/dist/utils/proxy.d.ts +25 -0
  611. package/dist/utils/proxy.d.ts.map +1 -0
  612. package/dist/utils/proxy.js +60 -0
  613. package/dist/utils/proxy.js.map +1 -0
  614. package/dist/utils/readiness.d.ts +20 -0
  615. package/dist/utils/readiness.d.ts.map +1 -0
  616. package/dist/utils/readiness.js +430 -0
  617. package/dist/utils/readiness.js.map +1 -0
  618. package/dist/utils/retrieval-execution-telemetry.d.ts +162 -0
  619. package/dist/utils/retrieval-execution-telemetry.d.ts.map +1 -0
  620. package/dist/utils/retrieval-execution-telemetry.js +288 -0
  621. package/dist/utils/retrieval-execution-telemetry.js.map +1 -0
  622. package/dist/utils/retrieval-result-ranking.d.ts +40 -0
  623. package/dist/utils/retrieval-result-ranking.d.ts.map +1 -0
  624. package/dist/utils/retrieval-result-ranking.js +162 -0
  625. package/dist/utils/retrieval-result-ranking.js.map +1 -0
  626. package/dist/utils/task-json.d.ts +13 -0
  627. package/dist/utils/task-json.d.ts.map +1 -0
  628. package/dist/utils/task-json.js +12 -0
  629. package/dist/utils/task-json.js.map +1 -0
  630. package/dist/utils/template-fetcher.d.ts +150 -0
  631. package/dist/utils/template-fetcher.d.ts.map +1 -0
  632. package/dist/utils/template-fetcher.js +907 -0
  633. package/dist/utils/template-fetcher.js.map +1 -0
  634. package/dist/utils/template-hash.d.ts +123 -0
  635. package/dist/utils/template-hash.d.ts.map +1 -0
  636. package/dist/utils/template-hash.js +334 -0
  637. package/dist/utils/template-hash.js.map +1 -0
  638. package/dist/utils/uninstall-scrubbers.d.ts +66 -0
  639. package/dist/utils/uninstall-scrubbers.d.ts.map +1 -0
  640. package/dist/utils/uninstall-scrubbers.js +342 -0
  641. package/dist/utils/uninstall-scrubbers.js.map +1 -0
  642. package/dist/utils/update-rollout-report.d.ts +137 -0
  643. package/dist/utils/update-rollout-report.d.ts.map +1 -0
  644. package/dist/utils/update-rollout-report.js +108 -0
  645. package/dist/utils/update-rollout-report.js.map +1 -0
  646. package/dist/utils/workflow-resolver.d.ts +86 -0
  647. package/dist/utils/workflow-resolver.d.ts.map +1 -0
  648. package/dist/utils/workflow-resolver.js +265 -0
  649. package/dist/utils/workflow-resolver.js.map +1 -0
  650. package/package.json +137 -0
  651. package/scripts/postinstall.js +45 -0
  652. package/vendor/smart-search/LICENSE +21 -0
  653. package/vendor/smart-search/README.md +412 -0
  654. package/vendor/smart-search/README.zh-CN.md +430 -0
  655. package/vendor/smart-search/npm/bin/smart-search.js +63 -0
  656. package/vendor/smart-search/npm/scripts/postinstall.js +87 -0
  657. package/vendor/smart-search/npm/scripts/resolve-prerelease-version.js +108 -0
  658. package/vendor/smart-search/npm/scripts/set-package-version.js +35 -0
  659. package/vendor/smart-search/npm/scripts/sync-python-version.js +22 -0
  660. package/vendor/smart-search/npm/scripts/test-wrapper-repair.js +99 -0
  661. package/vendor/smart-search/npm/scripts/test.js +85 -0
  662. package/vendor/smart-search/package.json +42 -0
  663. package/vendor/smart-search/pyproject.toml +37 -0
  664. package/vendor/smart-search/skills/smart-search-cli/SKILL.md +387 -0
  665. package/vendor/smart-search/skills/smart-search-cli/agents/openai.yaml +3 -0
  666. package/vendor/smart-search/skills/smart-search-cli/examples/batch-search.md +98 -0
  667. package/vendor/smart-search/skills/smart-search-cli/examples/evidence-gathering.md +89 -0
  668. package/vendor/smart-search/skills/smart-search-cli/references/cli-contract.md +337 -0
  669. package/vendor/smart-search/src/smart_search/__init__.py +1 -0
  670. package/vendor/smart-search/src/smart_search/assets/skills/smart-search-cli/SKILL.md +387 -0
  671. package/vendor/smart-search/src/smart_search/assets/skills/smart-search-cli/agents/openai.yaml +3 -0
  672. package/vendor/smart-search/src/smart_search/assets/skills/smart-search-cli/examples/batch-search.md +98 -0
  673. package/vendor/smart-search/src/smart_search/assets/skills/smart-search-cli/examples/evidence-gathering.md +89 -0
  674. package/vendor/smart-search/src/smart_search/assets/skills/smart-search-cli/references/cli-contract.md +337 -0
  675. package/vendor/smart-search/src/smart_search/cli.py +2003 -0
  676. package/vendor/smart-search/src/smart_search/config.py +580 -0
  677. package/vendor/smart-search/src/smart_search/logger.py +43 -0
  678. package/vendor/smart-search/src/smart_search/providers/__init__.py +16 -0
  679. package/vendor/smart-search/src/smart_search/providers/base.py +41 -0
  680. package/vendor/smart-search/src/smart_search/providers/context7.py +141 -0
  681. package/vendor/smart-search/src/smart_search/providers/exa.py +206 -0
  682. package/vendor/smart-search/src/smart_search/providers/jina.py +136 -0
  683. package/vendor/smart-search/src/smart_search/providers/openai_compatible.py +370 -0
  684. package/vendor/smart-search/src/smart_search/providers/zhipu.py +143 -0
  685. package/vendor/smart-search/src/smart_search/service.py +2865 -0
  686. package/vendor/smart-search/src/smart_search/sources.py +429 -0
  687. package/vendor/smart-search/src/smart_search/utils.py +220 -0
@@ -0,0 +1,1735 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Task quality gate helpers.
4
+
5
+ Gate records are stored in task.json under:
6
+ quality_gate_results.transitions[transition][gate]
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ import re
14
+ from dataclasses import dataclass, field
15
+ from datetime import datetime, timezone
16
+ from pathlib import Path
17
+
18
+ from .task_map import (
19
+ get_child_state,
20
+ load_task_map,
21
+ validate_child_archive_state,
22
+ validate_parent_children_complete,
23
+ )
24
+
25
+ SCHEMA_VERSION = 1
26
+ BASELINE_GATE = "baseline-check"
27
+
28
+ KNOWN_TRANSITIONS = {
29
+ "start-execution",
30
+ "full-task-complete",
31
+ "child-review",
32
+ "parent-changes",
33
+ "parent-accepted",
34
+ "parent-integrating",
35
+ "parent-integrated",
36
+ "parent-cancelled",
37
+ }
38
+
39
+ REVIEW_GATES = {
40
+ "requirements-review",
41
+ "code-review",
42
+ "architecture-review",
43
+ "architecture-deep-review",
44
+ "integration-review",
45
+ }
46
+
47
+ KNOWN_GATES = {BASELINE_GATE, *REVIEW_GATES}
48
+ RESULTS = {"PASS", "FAIL", "SKIPPED"}
49
+ FAIL_ROOT_CAUSES = {
50
+ "implementation-defect": "Execution",
51
+ "contract-changing-defect": "Planning",
52
+ "validation-environment-blocker": "Verification / Review",
53
+ }
54
+
55
+ PROFILE_DEFAULT_GATES = {
56
+ "standard": ["requirements-review", "code-review"],
57
+ "strict": ["requirements-review", "code-review"],
58
+ "architecture": [
59
+ "requirements-review",
60
+ "architecture-review",
61
+ "code-review",
62
+ ],
63
+ }
64
+
65
+ ALLOWED_EXECUTION_MODES = {"inline", "worker", "child-task"}
66
+ ALLOWED_ISOLATION = {"main-worktree", "git-worktree"}
67
+ ALLOWED_VERIFICATION_PROFILES = {"standard", "strict", "architecture"}
68
+ ALLOWED_RETRIEVAL_PROFILES = {
69
+ "exact-only",
70
+ "semantic",
71
+ "structure",
72
+ "architecture-memory",
73
+ }
74
+ ALLOWED_GATE_MODES = {"profile", "explicit"}
75
+
76
+ MAX_SHORT_FIELD = 240
77
+ MAX_REASON = 500
78
+ REVIEWER_RE = re.compile(r"^[A-Za-z0-9_.:@/-]+$")
79
+ VALIDATION_EVIDENCE_RE = re.compile(
80
+ r"(?im)^\s*(?:[-*]\s*)?validation"
81
+ r"(?:\s+(?:commands?|results?|evidence))?\s*:\s*\S"
82
+ )
83
+ ACCEPTANCE_EVIDENCE_RE = re.compile(
84
+ r"(?im)^\s*(?:[-*]\s*)?(?:final\s+|user\s+)?acceptance"
85
+ r"(?:\s+evidence)?\s*:\s*\S"
86
+ )
87
+ ACCEPTED_BY_USER_RE = re.compile(
88
+ r"(?im)^\s*(?:[-*]\s*)?accepted\s+by\s+user\s*:\s*\S"
89
+ )
90
+ NO_DURABLE_LEARNING_RE = re.compile(r"(?i)\bno\s+durable\s+learning\b")
91
+ DURABLE_LEARNING_EVIDENCE_RE = re.compile(
92
+ r"(?im)^\s*(?:[-*]\s*)?(?:durable\s+learning|learning\s+decision|"
93
+ r"spec\s+updates?|spec\s+update\s+(?:needed|evidence)|updated\s+spec|"
94
+ r"retrospective(?:\.md)?|learning\s+artifact)\s*:\s*\S"
95
+ )
96
+ INTEGRATION_EVIDENCE_RE = re.compile(
97
+ r"(?im)^\s*(?:[-*]\s*)?(?:final\s+)?integration"
98
+ r"(?:\s+evidence)?\s*:\s*\S"
99
+ )
100
+ REVIEWED_CHANGE_SET_RE = re.compile(
101
+ r"(?im)^\s*(?:[-*]\s*)?(?:reviewed\s+)?"
102
+ r"(?:change[- ]set|changeset|diff|git\s+diff|ref|git\s+ref)"
103
+ r"(?:\s+(?:identity|evidence|summary|ref))?\s*:\s*(\S[^\r\n]*)$"
104
+ )
105
+ PRD_ACCEPTANCE_CRITERIA_HEADING_RE = re.compile(
106
+ r"(?im)^\s*#{1,6}\s*acceptance\s+criteria\s*$"
107
+ )
108
+ PRD_ACCEPTANCE_ITEM_RE = re.compile(
109
+ r"(?im)^\s*-\s*\[[ xX]\]\s+(.+)$"
110
+ )
111
+ PRD_PLACEHOLDER_RE = re.compile(r"(?i)^\s*(TBD|TODO|待定|待补充)(?:\s*[.::])?\s*$")
112
+ AUTO_PLANNING_REVIEWER = "trellis-cli"
113
+ START_EXECUTION_AUTO_GATES = frozenset({"requirements-review", "architecture-review"})
114
+
115
+ STABLE_TASK_KEYS = (
116
+ "id",
117
+ "name",
118
+ "title",
119
+ "description",
120
+ "status",
121
+ "dev_type",
122
+ "scope",
123
+ "package",
124
+ "priority",
125
+ "creator",
126
+ "assignee",
127
+ "parent",
128
+ "children",
129
+ "subtasks",
130
+ "relatedFiles",
131
+ "notes",
132
+ "meta",
133
+ "branch",
134
+ "base_branch",
135
+ "task_kind",
136
+ "task_type",
137
+ "kind",
138
+ "mode",
139
+ "contract_epoch",
140
+ )
141
+
142
+
143
+ @dataclass
144
+ class GateGuardResult:
145
+ """Result from a protected-transition guard."""
146
+
147
+ ok: bool
148
+ errors: list[str] = field(default_factory=list)
149
+ warnings: list[str] = field(default_factory=list)
150
+ contract_fingerprint: str = ""
151
+ artifact_fingerprints: dict[str, str] = field(default_factory=dict)
152
+ required_gates: list[str] = field(default_factory=list)
153
+ baseline_record: dict | None = None
154
+ is_full_task: bool = False
155
+ auto_gate_records: dict[str, dict] = field(default_factory=dict)
156
+
157
+
158
+ def utc_now() -> str:
159
+ """Return a compact UTC timestamp for task.json records."""
160
+ return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace(
161
+ "+00:00", "Z"
162
+ )
163
+
164
+
165
+ def normalize_result(result: str) -> str:
166
+ """Normalize a CLI result value."""
167
+ return result.strip().upper()
168
+
169
+
170
+ def is_full_task(task_dir: Path, task_data: dict | None = None) -> bool:
171
+ """Return True when a task should satisfy Full Task gates."""
172
+ data = task_data or {}
173
+ meta = data.get("meta") if isinstance(data.get("meta"), dict) else {}
174
+ candidates = [
175
+ data.get("task_kind"),
176
+ data.get("task_type"),
177
+ data.get("kind"),
178
+ data.get("mode"),
179
+ meta.get("task_kind"),
180
+ meta.get("task_type"),
181
+ meta.get("classification"),
182
+ meta.get("mode"),
183
+ ]
184
+
185
+ for value in candidates:
186
+ if not isinstance(value, str):
187
+ continue
188
+ normalized = value.lower().replace("_", "-")
189
+ if "full" in normalized:
190
+ return True
191
+ if "lite" in normalized:
192
+ return False
193
+
194
+ return (task_dir / "design.md").is_file() and (task_dir / "implement.md").is_file()
195
+
196
+
197
+ def read_strategy_contract(task_dir: Path) -> tuple[dict, list[str]]:
198
+ """Parse the lightweight Development Strategy Contract from implement.md."""
199
+ implement_path = task_dir / "implement.md"
200
+ if not implement_path.is_file():
201
+ return {}, ["implement.md"]
202
+
203
+ try:
204
+ lines = implement_path.read_text(encoding="utf-8").splitlines()
205
+ except OSError:
206
+ return {}, ["implement.md could not be read"]
207
+
208
+ contract: dict = {}
209
+ section: str | None = None
210
+ list_key: str | None = None
211
+ started = False
212
+
213
+ top_level_keys = {
214
+ "execution_mode",
215
+ "isolation",
216
+ "verification_profile",
217
+ "retrieval_profile",
218
+ "optional_capabilities",
219
+ "quality_gates",
220
+ }
221
+
222
+ for raw_line in lines:
223
+ line = raw_line.rstrip()
224
+ stripped = line.strip()
225
+ if not stripped:
226
+ continue
227
+ if stripped.startswith("#") or stripped.startswith("```"):
228
+ continue
229
+
230
+ top_match = re.match(r"^([A-Za-z_][A-Za-z0-9_]*):\s*(.*)$", stripped)
231
+ if top_match:
232
+ key = top_match.group(1)
233
+ value = top_match.group(2).strip()
234
+ if key in top_level_keys:
235
+ started = True
236
+ section = key if key == "quality_gates" else None
237
+ list_key = key if key == "optional_capabilities" and not value else None
238
+ if key == "quality_gates":
239
+ contract.setdefault("quality_gates", {})
240
+ elif key == "optional_capabilities":
241
+ if value:
242
+ contract[key] = _parse_inline_list(value)
243
+ else:
244
+ contract[key] = []
245
+ else:
246
+ contract[key] = value
247
+ continue
248
+
249
+ if not started:
250
+ continue
251
+
252
+ nested_match = re.match(r"^\s+([A-Za-z_][A-Za-z0-9_]*):\s*(.*)$", line)
253
+ if nested_match and section == "quality_gates":
254
+ key = nested_match.group(1)
255
+ value = nested_match.group(2).strip()
256
+ gates = contract.setdefault("quality_gates", {})
257
+ if value:
258
+ gates[key] = _parse_inline_list(value)
259
+ list_key = None
260
+ else:
261
+ gates[key] = []
262
+ list_key = f"quality_gates.{key}"
263
+ continue
264
+
265
+ item_match = re.match(r"^\s*-\s+(.+)$", line)
266
+ if item_match:
267
+ item = item_match.group(1).strip()
268
+ if list_key == "optional_capabilities":
269
+ contract.setdefault("optional_capabilities", []).append(item)
270
+ elif list_key and list_key.startswith("quality_gates."):
271
+ gate_key = list_key.split(".", 1)[1]
272
+ gates = contract.setdefault("quality_gates", {})
273
+ gates.setdefault(gate_key, []).append(item)
274
+
275
+ errors = validate_strategy_contract(contract)
276
+ return contract, errors
277
+
278
+
279
+ def validate_strategy_contract(contract: dict) -> list[str]:
280
+ """Validate the parsed Development Strategy Contract."""
281
+ errors: list[str] = []
282
+ required = [
283
+ "execution_mode",
284
+ "isolation",
285
+ "verification_profile",
286
+ "retrieval_profile",
287
+ "optional_capabilities",
288
+ "quality_gates",
289
+ ]
290
+ for key in required:
291
+ if key not in contract:
292
+ errors.append(f"Development Strategy Contract missing {key}")
293
+
294
+ execution_mode = contract.get("execution_mode")
295
+ if execution_mode and execution_mode not in ALLOWED_EXECUTION_MODES:
296
+ errors.append(f"invalid execution_mode: {execution_mode}")
297
+
298
+ isolation = contract.get("isolation")
299
+ if isolation and isolation not in ALLOWED_ISOLATION:
300
+ errors.append(f"invalid isolation: {isolation}")
301
+
302
+ verification_profile = contract.get("verification_profile")
303
+ if verification_profile and verification_profile not in ALLOWED_VERIFICATION_PROFILES:
304
+ errors.append(f"invalid verification_profile: {verification_profile}")
305
+
306
+ retrieval_profile = contract.get("retrieval_profile")
307
+ if retrieval_profile and retrieval_profile not in ALLOWED_RETRIEVAL_PROFILES:
308
+ errors.append(f"invalid retrieval_profile: {retrieval_profile}")
309
+
310
+ optional_capabilities = contract.get("optional_capabilities")
311
+ if "optional_capabilities" in contract and not isinstance(optional_capabilities, list):
312
+ errors.append("optional_capabilities must be a list")
313
+
314
+ gates = contract.get("quality_gates")
315
+ if isinstance(gates, dict):
316
+ errors.extend(_validate_quality_gates(gates, verification_profile))
317
+ elif "quality_gates" in contract:
318
+ errors.append("quality_gates must be a mapping")
319
+
320
+ return errors
321
+
322
+
323
+ def enabled_gates_for_contract(contract: dict) -> list[str]:
324
+ """Return enabled non-baseline gates from a parsed contract."""
325
+ gates = contract.get("quality_gates")
326
+ if not isinstance(gates, dict):
327
+ return []
328
+
329
+ mode = gates.get("mode", "profile")
330
+ if mode == "explicit":
331
+ enabled = gates.get("enabled", [])
332
+ return [g for g in enabled if isinstance(g, str)]
333
+
334
+ profile = gates.get("profile") or contract.get("verification_profile") or "standard"
335
+ return list(PROFILE_DEFAULT_GATES.get(profile, PROFILE_DEFAULT_GATES["standard"]))
336
+
337
+
338
+ def required_gates_for_transition(transition: str, contract: dict) -> list[str]:
339
+ """Return non-baseline gates required before a transition."""
340
+ enabled = set(enabled_gates_for_contract(contract))
341
+
342
+ if transition == "start-execution":
343
+ required = ["requirements-review"]
344
+ if "architecture-review" in enabled:
345
+ required.append("architecture-review")
346
+ return required
347
+
348
+ if transition in ("full-task-complete", "child-review"):
349
+ required = ["code-review"]
350
+ if "architecture-review" in enabled:
351
+ required.append("architecture-review")
352
+ if "architecture-deep-review" in enabled:
353
+ required.append("architecture-deep-review")
354
+ return required
355
+
356
+ if transition == "parent-integrated":
357
+ return ["integration-review"]
358
+
359
+ return []
360
+
361
+
362
+ def compute_contract_fingerprint(
363
+ task_dir: Path,
364
+ task_data: dict,
365
+ contract: dict | None = None,
366
+ ) -> str:
367
+ """Compute a task-level fingerprint excluding generated result fields."""
368
+ payload = {
369
+ "schema_version": SCHEMA_VERSION,
370
+ "task_dir": task_dir.name,
371
+ "stable_task": _stable_task_data(task_data),
372
+ "strategy_contract": contract or {},
373
+ }
374
+ return _hash_payload(payload)
375
+
376
+
377
+ def compute_artifact_fingerprint(
378
+ task_dir: Path,
379
+ task_data: dict,
380
+ transition: str,
381
+ gate: str,
382
+ ) -> str:
383
+ """Compute a transition/gate-scoped artifact fingerprint."""
384
+ files = _artifact_files_for(transition, gate)
385
+ payload = {
386
+ "schema_version": SCHEMA_VERSION,
387
+ "transition": transition,
388
+ "gate": gate,
389
+ "task_dir": task_dir.name,
390
+ "stable_task": _stable_task_data(task_data),
391
+ "parent_contract": _parent_contract_metadata(
392
+ task_dir,
393
+ task_data,
394
+ transition,
395
+ ),
396
+ "reviewed_change_set": _reviewed_change_set_metadata(
397
+ task_dir,
398
+ transition,
399
+ ),
400
+ "files": _read_artifact_files(task_dir, files),
401
+ }
402
+ return _hash_payload(payload)
403
+
404
+
405
+ def make_baseline_record(
406
+ task_dir: Path,
407
+ task_data: dict,
408
+ transition: str,
409
+ contract: dict | None = None,
410
+ evidence: str = "task.json",
411
+ ) -> dict:
412
+ """Build a CLI-owned baseline-check PASS record."""
413
+ contract_fingerprint = compute_contract_fingerprint(task_dir, task_data, contract)
414
+ artifact_fingerprint = compute_artifact_fingerprint(
415
+ task_dir, task_data, transition, BASELINE_GATE
416
+ )
417
+ return {
418
+ "schema_version": SCHEMA_VERSION,
419
+ "transition": transition,
420
+ "gate": BASELINE_GATE,
421
+ "result": "PASS",
422
+ "reviewer": "trellis-cli",
423
+ "evidence": evidence,
424
+ "checked_at": utc_now(),
425
+ "contract_fingerprint": contract_fingerprint,
426
+ "artifact_fingerprint": artifact_fingerprint,
427
+ "issue_fingerprint": None,
428
+ "consecutive_failures": 0,
429
+ "approved_skip": None,
430
+ }
431
+
432
+
433
+ def write_gate_record(task_data: dict, transition: str, gate: str, record: dict) -> None:
434
+ """Write a gate record into task_data in-place."""
435
+ qgr = task_data.get("quality_gate_results")
436
+ if not isinstance(qgr, dict):
437
+ qgr = {}
438
+ task_data["quality_gate_results"] = qgr
439
+
440
+ qgr["schema_version"] = SCHEMA_VERSION
441
+ qgr["contract_fingerprint"] = record.get("contract_fingerprint")
442
+ qgr["artifact_fingerprint"] = record.get("artifact_fingerprint")
443
+ transitions = qgr.get("transitions")
444
+ if not isinstance(transitions, dict):
445
+ transitions = {}
446
+ qgr["transitions"] = transitions
447
+
448
+ transition_records = transitions.get(transition)
449
+ if not isinstance(transition_records, dict):
450
+ transition_records = {}
451
+ transitions[transition] = transition_records
452
+ transition_records[gate] = record
453
+
454
+
455
+ def build_reviewer_gate_record(
456
+ task_dir: Path,
457
+ task_data: dict,
458
+ transition: str,
459
+ gate: str,
460
+ result: str,
461
+ reviewer: str,
462
+ evidence: str,
463
+ issue_fingerprint: str | None = None,
464
+ issue_summary: str | None = None,
465
+ root_cause: str | None = None,
466
+ skip_approved_by: str | None = None,
467
+ skip_reason: str | None = None,
468
+ contract_fingerprint: str | None = None,
469
+ artifact_fingerprint: str | None = None,
470
+ ) -> tuple[dict | None, list[str], list[str]]:
471
+ """Validate and build a non-baseline reviewer gate record."""
472
+ normalized_result = normalize_result(result)
473
+ errors = validate_reviewer_gate_input(
474
+ transition=transition,
475
+ gate=gate,
476
+ result=normalized_result,
477
+ reviewer=reviewer,
478
+ evidence=evidence,
479
+ issue_fingerprint=issue_fingerprint,
480
+ issue_summary=issue_summary,
481
+ root_cause=root_cause,
482
+ skip_approved_by=skip_approved_by,
483
+ skip_reason=skip_reason,
484
+ )
485
+ warnings: list[str] = []
486
+ if errors:
487
+ return None, errors, warnings
488
+
489
+ contract, contract_errors = read_strategy_contract(task_dir) if is_full_task(task_dir, task_data) else ({}, [])
490
+ if contract_errors:
491
+ errors.extend(contract_errors)
492
+ return None, errors, warnings
493
+
494
+ current_contract_fingerprint = compute_contract_fingerprint(
495
+ task_dir, task_data, contract
496
+ )
497
+ current_artifact_fingerprint = compute_artifact_fingerprint(
498
+ task_dir, task_data, transition, gate
499
+ )
500
+ if contract_fingerprint and contract_fingerprint != current_contract_fingerprint:
501
+ errors.append("provided contract fingerprint does not match current task artifacts")
502
+ if artifact_fingerprint and artifact_fingerprint != current_artifact_fingerprint:
503
+ errors.append("provided artifact fingerprint does not match current task artifacts")
504
+ if errors:
505
+ return None, errors, warnings
506
+
507
+ previous = _get_gate_record(task_data, transition, gate)
508
+ consecutive_failures = 0
509
+ required_user_choice = None
510
+ if normalized_result == "FAIL":
511
+ consecutive_failures = _next_consecutive_failures(previous, issue_fingerprint)
512
+ if consecutive_failures > 3:
513
+ required_user_choice = {
514
+ "required": True,
515
+ "reason": "same gate and issue fingerprint failed more than three times",
516
+ "options": [
517
+ "re-plan",
518
+ "continue-fixing",
519
+ "user-approved-skip-if-allowed",
520
+ ],
521
+ }
522
+ warnings.append(
523
+ "same gate and issue fingerprint has failed more than three times; ask the user to choose re-plan, continue fixing, or user-approved skip if allowed"
524
+ )
525
+
526
+ approved_skip = None
527
+ if normalized_result == "SKIPPED":
528
+ approved_skip = {
529
+ "approved_by": skip_approved_by,
530
+ "reason": skip_reason,
531
+ "approved_at": utc_now(),
532
+ }
533
+
534
+ record = {
535
+ "schema_version": SCHEMA_VERSION,
536
+ "transition": transition,
537
+ "gate": gate,
538
+ "result": normalized_result,
539
+ "reviewer": reviewer,
540
+ "evidence": evidence,
541
+ "checked_at": utc_now(),
542
+ "contract_fingerprint": current_contract_fingerprint,
543
+ "artifact_fingerprint": current_artifact_fingerprint,
544
+ "issue_fingerprint": issue_fingerprint if normalized_result == "FAIL" else None,
545
+ "root_cause": root_cause if normalized_result == "FAIL" else None,
546
+ "route": _route_for_fail_root_cause(root_cause) if normalized_result == "FAIL" else None,
547
+ "consecutive_failures": consecutive_failures,
548
+ "required_user_choice": required_user_choice,
549
+ "approved_skip": approved_skip,
550
+ }
551
+ if issue_summary and normalized_result == "FAIL":
552
+ record["issue_summary"] = issue_summary
553
+ return record, [], warnings
554
+
555
+
556
+ def validate_start_execution(
557
+ task_dir: Path,
558
+ task_data: dict | None,
559
+ approved: bool,
560
+ ) -> GateGuardResult:
561
+ """Validate start-execution readiness."""
562
+ errors: list[str] = []
563
+ if task_data is None:
564
+ return GateGuardResult(ok=False, errors=["task.json"])
565
+
566
+ status = task_data.get("status")
567
+ if status not in ("planning", "in_progress"):
568
+ errors.append(f"task status must be planning or in_progress, got {status!r}")
569
+
570
+ errors.extend(_required_file_errors(task_dir, ["prd.md"]))
571
+
572
+ has_design = (task_dir / "design.md").is_file()
573
+ has_implement = (task_dir / "implement.md").is_file()
574
+ if has_design != has_implement:
575
+ errors.append("design.md and implement.md must be present together for Full Tasks")
576
+
577
+ full_task = is_full_task(task_dir, task_data)
578
+ contract: dict = {}
579
+ required_gates: list[str] = []
580
+ if full_task:
581
+ contract, contract_errors = read_strategy_contract(task_dir)
582
+ errors.extend(contract_errors)
583
+ if not contract_errors:
584
+ required_gates = required_gates_for_transition("start-execution", contract)
585
+
586
+ contract_fingerprint = compute_contract_fingerprint(task_dir, task_data, contract)
587
+ artifact_fingerprints = {
588
+ BASELINE_GATE: compute_artifact_fingerprint(
589
+ task_dir, task_data, "start-execution", BASELINE_GATE
590
+ )
591
+ }
592
+
593
+ auto_gate_records: dict[str, dict] = {}
594
+ for gate in required_gates:
595
+ artifact_fingerprints[gate] = compute_artifact_fingerprint(
596
+ task_dir, task_data, "start-execution", gate
597
+ )
598
+ gate_errors = _validate_gate_record_for_transition(
599
+ task_data=task_data,
600
+ transition="start-execution",
601
+ gate=gate,
602
+ contract_fingerprint=contract_fingerprint,
603
+ artifact_fingerprint=artifact_fingerprints[gate],
604
+ )
605
+ if gate_errors and gate in START_EXECUTION_AUTO_GATES and _only_missing_gate_record(
606
+ gate_errors
607
+ ):
608
+ readiness_errors = _start_execution_planning_gate_readiness_errors(
609
+ task_dir,
610
+ gate,
611
+ full_task=full_task,
612
+ contract=contract,
613
+ )
614
+ if readiness_errors:
615
+ errors.extend(readiness_errors)
616
+ else:
617
+ auto_gate_records[gate] = make_planning_review_gate_record(
618
+ task_dir=task_dir,
619
+ task_data=task_data,
620
+ transition="start-execution",
621
+ gate=gate,
622
+ contract=contract,
623
+ contract_fingerprint=contract_fingerprint,
624
+ artifact_fingerprint=artifact_fingerprints[gate],
625
+ )
626
+ else:
627
+ errors.extend(gate_errors)
628
+
629
+ if approved:
630
+ errors.extend(
631
+ _validate_existing_execution_approval(
632
+ task_data=task_data,
633
+ contract_fingerprint=contract_fingerprint,
634
+ artifact_fingerprint=artifact_fingerprints[BASELINE_GATE],
635
+ )
636
+ )
637
+
638
+ if not approved:
639
+ errors.append("--approved is required for mutation")
640
+
641
+ baseline_record = None
642
+ if not errors:
643
+ baseline_record = make_baseline_record(
644
+ task_dir, task_data, "start-execution", contract
645
+ )
646
+
647
+ return GateGuardResult(
648
+ ok=not errors,
649
+ errors=errors,
650
+ contract_fingerprint=contract_fingerprint,
651
+ artifact_fingerprints=artifact_fingerprints,
652
+ required_gates=required_gates,
653
+ baseline_record=baseline_record,
654
+ is_full_task=full_task,
655
+ auto_gate_records=auto_gate_records,
656
+ )
657
+
658
+
659
+ def _validate_existing_execution_approval(
660
+ task_data: dict,
661
+ contract_fingerprint: str,
662
+ artifact_fingerprint: str,
663
+ ) -> list[str]:
664
+ """Reject stale approval state before re-approving an in-progress task."""
665
+ approval = task_data.get("execution_approval")
666
+ if not isinstance(approval, dict):
667
+ return []
668
+
669
+ errors: list[str] = []
670
+ if approval.get("transition") != "start-execution":
671
+ errors.append("stale execution approval: transition mismatch")
672
+ if approval.get("contract_fingerprint") != contract_fingerprint:
673
+ errors.append("stale execution approval: contract fingerprint changed")
674
+ if approval.get("artifact_fingerprint") != artifact_fingerprint:
675
+ errors.append("stale execution approval: artifact fingerprint changed")
676
+ if errors:
677
+ errors.append(
678
+ "return to Planning, refresh gates, run start-execution --check, and ask for explicit approval again"
679
+ )
680
+ return errors
681
+
682
+
683
+ def validate_start_execution_check(
684
+ task_dir: Path,
685
+ task_data: dict | None,
686
+ ) -> GateGuardResult:
687
+ """Validate start-execution preflight without requiring approval."""
688
+ result = validate_start_execution(task_dir, task_data, approved=True)
689
+ return result
690
+
691
+
692
+ def validate_archive(
693
+ task_dir: Path,
694
+ task_data: dict | None,
695
+ ) -> GateGuardResult:
696
+ """Validate archive readiness without mutating task files."""
697
+ errors: list[str] = []
698
+ if task_data is None:
699
+ return GateGuardResult(ok=False, errors=["task.json"])
700
+
701
+ errors.extend(_required_file_errors(task_dir, ["verify.md"]))
702
+ errors.extend(_verify_evidence_errors(task_dir, task_data))
703
+
704
+ parent_name = task_data.get("parent")
705
+ if isinstance(parent_name, str) and parent_name:
706
+ parent_dir = task_dir.parent / parent_name
707
+ if not parent_dir.is_dir():
708
+ errors.append(f"parent task not found: {parent_name}")
709
+ else:
710
+ errors.extend(validate_child_archive_state(parent_dir, task_dir.name))
711
+ if get_child_state(parent_dir, task_dir.name) == "integrated":
712
+ errors.extend(_required_file_errors(task_dir, ["handoff.md"]))
713
+
714
+ child_names = task_data.get("children")
715
+ if isinstance(child_names, list) and child_names:
716
+ errors.extend(
717
+ validate_parent_children_complete(
718
+ task_dir,
719
+ [name for name in child_names if isinstance(name, str)],
720
+ )
721
+ )
722
+
723
+ full_task = is_full_task(task_dir, task_data)
724
+ contract: dict = {}
725
+ required_gates: list[str] = []
726
+ artifact_fingerprints: dict[str, str] = {}
727
+ baseline_record = None
728
+
729
+ if full_task:
730
+ contract, contract_errors = read_strategy_contract(task_dir)
731
+ errors.extend(contract_errors)
732
+ if not contract_errors:
733
+ required_gates = required_gates_for_transition(
734
+ "full-task-complete", contract
735
+ )
736
+
737
+ contract_fingerprint = compute_contract_fingerprint(task_dir, task_data, contract)
738
+
739
+ if full_task:
740
+ artifact_fingerprints[BASELINE_GATE] = compute_artifact_fingerprint(
741
+ task_dir, task_data, "full-task-complete", BASELINE_GATE
742
+ )
743
+ for gate in required_gates:
744
+ artifact_fingerprints[gate] = compute_artifact_fingerprint(
745
+ task_dir, task_data, "full-task-complete", gate
746
+ )
747
+ errors.extend(
748
+ _validate_gate_record_for_transition(
749
+ task_data=task_data,
750
+ transition="full-task-complete",
751
+ gate=gate,
752
+ contract_fingerprint=contract_fingerprint,
753
+ artifact_fingerprint=artifact_fingerprints[gate],
754
+ )
755
+ )
756
+ if not errors:
757
+ baseline_record = make_baseline_record(
758
+ task_dir, task_data, "full-task-complete", contract
759
+ )
760
+
761
+ return GateGuardResult(
762
+ ok=not errors,
763
+ errors=errors,
764
+ contract_fingerprint=contract_fingerprint,
765
+ artifact_fingerprints=artifact_fingerprints,
766
+ required_gates=required_gates,
767
+ baseline_record=baseline_record,
768
+ is_full_task=full_task,
769
+ )
770
+
771
+
772
+ def _verify_evidence_errors(task_dir: Path, task_data: dict) -> list[str]:
773
+ """Validate human-readable archive evidence in verify.md."""
774
+ verify_path = task_dir / "verify.md"
775
+ if not verify_path.is_file():
776
+ return []
777
+
778
+ try:
779
+ content = verify_path.read_text(encoding="utf-8")
780
+ except OSError:
781
+ return ["verify.md could not be read for archive evidence"]
782
+
783
+ errors: list[str] = []
784
+ if not _has_validation_evidence(content):
785
+ errors.append("verify.md missing validation evidence")
786
+ if not _has_final_acceptance_evidence(content):
787
+ errors.append("verify.md missing final acceptance evidence")
788
+ if not _has_durable_learning_evidence(content):
789
+ errors.append("verify.md missing durable-learning decision evidence")
790
+
791
+ child_names = task_data.get("children")
792
+ if isinstance(child_names, list) and child_names:
793
+ if not INTEGRATION_EVIDENCE_RE.search(content):
794
+ errors.append("verify.md missing final integration evidence")
795
+
796
+ return errors
797
+
798
+
799
+ def _has_validation_evidence(content: str) -> bool:
800
+ return bool(VALIDATION_EVIDENCE_RE.search(content))
801
+
802
+
803
+ def _has_final_acceptance_evidence(content: str) -> bool:
804
+ return bool(
805
+ ACCEPTANCE_EVIDENCE_RE.search(content)
806
+ or ACCEPTED_BY_USER_RE.search(content)
807
+ )
808
+
809
+
810
+ def _has_durable_learning_evidence(content: str) -> bool:
811
+ return bool(
812
+ NO_DURABLE_LEARNING_RE.search(content)
813
+ or DURABLE_LEARNING_EVIDENCE_RE.search(content)
814
+ )
815
+
816
+
817
+ def durable_learning_decision_status(content: str) -> dict[str, bool]:
818
+ """Return which durable-learning outcomes are signaled in verify.md text."""
819
+ return {
820
+ "no_durable_learning": bool(NO_DURABLE_LEARNING_RE.search(content)),
821
+ "spec_update": bool(
822
+ re.search(
823
+ r"(?im)^\s*(?:[-*]\s*)?(?:spec\s+updates?|spec\s+update\s+"
824
+ r"(?:needed|evidence)|updated\s+spec)\s*:\s*\S",
825
+ content,
826
+ )
827
+ ),
828
+ "learning_artifact": bool(
829
+ re.search(
830
+ r"(?im)^\s*(?:[-*]\s*)?(?:learning\s+artifact|retrospective(?:\.md)?)\s*:\s*\S",
831
+ content,
832
+ )
833
+ or re.search(
834
+ r"(?im)^\s*(?:[-*]\s*)?(?:durable\s+learning|learning\s+decision)\s*:\s*\S",
835
+ content,
836
+ )
837
+ ),
838
+ "any": _has_durable_learning_evidence(content),
839
+ }
840
+
841
+
842
+ def suggest_spec_targets(repo_root: Path, task_dir: Path, task_data: dict) -> list[str]:
843
+ """Suggest existing spec paths from task scope; does not invent new rules."""
844
+ suggestions: list[str] = []
845
+ spec_root = repo_root / ".trellis" / "spec"
846
+ if not spec_root.is_dir():
847
+ return suggestions
848
+
849
+ package = task_data.get("package")
850
+ if isinstance(package, str) and package.strip():
851
+ pkg_index = spec_root / package.strip() / "index.md"
852
+ if pkg_index.is_file():
853
+ suggestions.append(f".trellis/spec/{package.strip()}/index.md")
854
+
855
+ scope = task_data.get("scope")
856
+ if isinstance(scope, str) and scope.strip():
857
+ scope_parts = [p for p in scope.strip().replace("\\", "/").split("/") if p]
858
+ scope_path = spec_root.joinpath(*scope_parts) if scope_parts else spec_root
859
+ if scope_path.is_dir():
860
+ index = scope_path / "index.md"
861
+ if index.is_file():
862
+ suggestions.append(
863
+ f".trellis/spec/{scope_path.relative_to(spec_root).as_posix()}/index.md"
864
+ )
865
+
866
+ guide_paths = (
867
+ "guides/durable-learning-decision-guide.md",
868
+ "guides/index.md",
869
+ )
870
+ for rel in guide_paths:
871
+ path = spec_root / rel
872
+ if path.is_file():
873
+ suggestions.append(f".trellis/spec/{rel}")
874
+
875
+ return _dedupe_preserve_order(suggestions)[:6]
876
+
877
+
878
+ def build_spec_update_scaffold(
879
+ repo_root: Path,
880
+ task_dir: Path,
881
+ task_data: dict,
882
+ *,
883
+ trigger: str | None = None,
884
+ ) -> str:
885
+ """Markdown checklist for spec capture; user/reviewer must confirm before editing specs."""
886
+ targets = suggest_spec_targets(repo_root, task_dir, task_data)
887
+ rel_task = f".trellis/tasks/{task_dir.name}"
888
+ lines = [
889
+ "## Spec update scaffold (reviewer-confirmed)",
890
+ "",
891
+ "_Suggestions only — do not treat this block as project policy until a human confirms._",
892
+ "",
893
+ ]
894
+ if trigger:
895
+ lines.extend([f"Trigger: {trigger.strip()}", ""])
896
+ lines.extend(
897
+ [
898
+ "1. Decide outcome in verify.md:",
899
+ " - Routine: `Durable learning decision: no durable learning`",
900
+ " - Reusable insight: `Spec update evidence: .trellis/spec/<path>` after edits",
901
+ f" - Already documented: `Learning artifact: {rel_task}/handoff.md`",
902
+ "",
903
+ "2. Use `/trellis:update-spec` or `/trellis:break-loop` for depth; never auto-write specs.",
904
+ "",
905
+ ]
906
+ )
907
+ if targets:
908
+ lines.append("3. Existing spec indexes to consider (from task scope):")
909
+ for target in targets:
910
+ lines.append(f" - `{target}`")
911
+ lines.append("")
912
+ else:
913
+ lines.append(
914
+ "3. Browse `.trellis/spec/<package-or-layer>/index.md` for the right code-spec file."
915
+ )
916
+ lines.append("")
917
+ lines.append(
918
+ "4. Re-run `python ./.trellis/scripts/task.py archive <task> --check` after verify.md is final."
919
+ )
920
+ lines.append("")
921
+ return "\n".join(lines)
922
+
923
+
924
+ def _learning_decision_draft_lines(task_dir: Path, task_data: dict) -> list[str]:
925
+ """Default durable-learning block for prepare-archive-evidence."""
926
+ repo_root = task_dir.parent.parent.parent
927
+ targets = suggest_spec_targets(repo_root, task_dir, task_data)
928
+ target_hint = targets[0] if targets else ".trellis/spec/<layer>/index.md"
929
+ return [
930
+ "Durable learning decision: no durable learning for this task scope.",
931
+ "",
932
+ "# Replace the line above with ONE of these before archive:",
933
+ f"# Spec update evidence: {target_hint}",
934
+ f"# Learning artifact: .trellis/tasks/{task_dir.name}/handoff.md",
935
+ "# Spec update needed: (brief reason) — then run /trellis:update-spec and point Spec update evidence at the edited file",
936
+ "",
937
+ ]
938
+
939
+
940
+ ARCHIVE_EVIDENCE_DRAFT_MARKER = "<!-- trellis:archive-evidence-draft -->"
941
+
942
+
943
+ def _verify_evidence_status(task_dir: Path, task_data: dict) -> dict[str, bool]:
944
+ """Return which archive evidence sections are present in verify.md."""
945
+ verify_path = task_dir / "verify.md"
946
+ if not verify_path.is_file():
947
+ return {
948
+ "validation": False,
949
+ "acceptance": False,
950
+ "durable_learning": False,
951
+ "integration": False,
952
+ }
953
+ try:
954
+ content = verify_path.read_text(encoding="utf-8")
955
+ except OSError:
956
+ return {
957
+ "validation": False,
958
+ "acceptance": False,
959
+ "durable_learning": False,
960
+ "integration": False,
961
+ }
962
+
963
+ child_names = task_data.get("children")
964
+ has_children = isinstance(child_names, list) and bool(child_names)
965
+ return {
966
+ "validation": _has_validation_evidence(content),
967
+ "acceptance": _has_final_acceptance_evidence(content),
968
+ "durable_learning": _has_durable_learning_evidence(content),
969
+ "integration": (
970
+ bool(INTEGRATION_EVIDENCE_RE.search(content)) if has_children else True
971
+ ),
972
+ }
973
+
974
+
975
+ def archive_repair_hints(
976
+ errors: list[str],
977
+ task_dir: Path,
978
+ task_data: dict,
979
+ guard: GateGuardResult,
980
+ ) -> list[str]:
981
+ """Map archive validation errors to actionable next-step hints."""
982
+ hints: list[str] = []
983
+ task_ref = task_dir.name
984
+ rel_task = f".trellis/tasks/{task_ref}"
985
+
986
+ for error in errors:
987
+ if error == "verify.md":
988
+ hints.append(
989
+ f"Create verify.md, then run: python ./.trellis/scripts/task.py "
990
+ f"prepare-archive-evidence {rel_task}"
991
+ )
992
+ continue
993
+ if error == "verify.md missing validation evidence":
994
+ hints.append(
995
+ "Add a grep-friendly line such as "
996
+ "'Validation commands: <command> — <outcome>' to verify.md, "
997
+ f"or run: python ./.trellis/scripts/task.py prepare-archive-evidence {rel_task}"
998
+ )
999
+ continue
1000
+ if error == "verify.md missing final acceptance evidence":
1001
+ hints.append(
1002
+ "Add 'Final acceptance evidence: <criteria met>' or "
1003
+ "'Accepted by user: <who/when>' to verify.md, "
1004
+ f"or run prepare-archive-evidence {rel_task}"
1005
+ )
1006
+ continue
1007
+ if error == "verify.md missing durable-learning decision evidence":
1008
+ hints.append(
1009
+ "Durable learning decision (pick one grep-friendly line in verify.md): "
1010
+ "'Durable learning decision: no durable learning' for routine work; "
1011
+ "'Spec update evidence: .trellis/spec/<path>' after /trellis:update-spec; "
1012
+ "'Learning artifact: <path>' when handoff/retrospective already captures the insight. "
1013
+ f"Or run: python ./.trellis/scripts/task.py prepare-archive-evidence {rel_task}"
1014
+ )
1015
+ continue
1016
+ if error == "verify.md missing final integration evidence":
1017
+ hints.append(
1018
+ "Parent tasks need 'Final integration evidence: <child handoffs / task-map>'. "
1019
+ f"Run prepare-archive-evidence {rel_task} to draft a section from task-map.md"
1020
+ )
1021
+ continue
1022
+ if error.startswith("missing gate record:"):
1023
+ _rest = error.split(":", 1)[1].strip()
1024
+ _transition, gate = _rest.split("/", 1)
1025
+ hints.append(
1026
+ f"Record reviewer gate after explicit review (never auto-PASS): "
1027
+ f"python ./.trellis/scripts/task.py record-gate {rel_task} "
1028
+ f"--transition full-task-complete --gate {gate} --result PASS "
1029
+ f"--reviewer <reviewer-id> --evidence verify.md"
1030
+ )
1031
+ continue
1032
+ if error.startswith("gate failed:"):
1033
+ hints.append(
1034
+ "Resolve the FAIL gate (fix, re-review, or user-approved SKIPPED) "
1035
+ "before archive."
1036
+ )
1037
+ continue
1038
+ if error.startswith("stale "):
1039
+ hints.append(
1040
+ "Artifacts changed since the gate was recorded. Re-run review and "
1041
+ "record-gate for full-task-complete, then archive --check again."
1042
+ )
1043
+ continue
1044
+ if "child" in error and "integrated or cancelled" in error:
1045
+ hints.append(
1046
+ "Advance each child in parent task-map to integrated or cancelled "
1047
+ "via integrate-child before parent archive."
1048
+ )
1049
+ continue
1050
+ if error == "handoff.md" or error.startswith("handoff.md"):
1051
+ hints.append(
1052
+ "Integrated children require handoff.md on the child task before archive."
1053
+ )
1054
+ continue
1055
+
1056
+ if not hints and not guard.ok:
1057
+ hints.append(
1058
+ f"Run: python ./.trellis/scripts/task.py prepare-archive-evidence {rel_task} "
1059
+ "then archive --check again."
1060
+ )
1061
+
1062
+ if guard.is_full_task and guard.required_gates:
1063
+ missing_reviewer = any(
1064
+ e.startswith("missing gate record: full-task-complete/")
1065
+ for e in errors
1066
+ )
1067
+ if missing_reviewer and not any("record-gate" in h for h in hints):
1068
+ for gate in guard.required_gates:
1069
+ hints.append(
1070
+ f"Full Task archive needs record-gate for full-task-complete/{gate} "
1071
+ "(reviewer action required)."
1072
+ )
1073
+
1074
+ return _dedupe_preserve_order(hints)
1075
+
1076
+
1077
+ def _dedupe_preserve_order(items: list[str]) -> list[str]:
1078
+ seen: set[str] = set()
1079
+ out: list[str] = []
1080
+ for item in items:
1081
+ if item in seen:
1082
+ continue
1083
+ seen.add(item)
1084
+ out.append(item)
1085
+ return out
1086
+
1087
+
1088
+ def build_archive_evidence_draft(
1089
+ task_dir: Path,
1090
+ task_data: dict,
1091
+ guard: GateGuardResult | None = None,
1092
+ ) -> str:
1093
+ """Build markdown sections that satisfy archive evidence regex checks."""
1094
+ if guard is None:
1095
+ guard = validate_archive(task_dir, task_data)
1096
+
1097
+ status = _verify_evidence_status(task_dir, task_data)
1098
+ child_names = task_data.get("children")
1099
+ has_children = isinstance(child_names, list) and bool(child_names)
1100
+ lines = [
1101
+ "",
1102
+ "## Archive evidence (draft)",
1103
+ "",
1104
+ ARCHIVE_EVIDENCE_DRAFT_MARKER,
1105
+ "",
1106
+ "_Auto-drafted by prepare-archive-evidence. Edit placeholders before archive._",
1107
+ "",
1108
+ ]
1109
+
1110
+ if not status["validation"]:
1111
+ lines.extend(
1112
+ [
1113
+ "Validation commands: (fill in commands and outcomes, e.g. pnpm test — pass)",
1114
+ "",
1115
+ ]
1116
+ )
1117
+ if not status["acceptance"]:
1118
+ lines.extend(
1119
+ [
1120
+ "Final acceptance evidence: (describe acceptance criteria met for this task)",
1121
+ "",
1122
+ ]
1123
+ )
1124
+ if not status["durable_learning"]:
1125
+ lines.extend(_learning_decision_draft_lines(task_dir, task_data))
1126
+ if has_children and not status["integration"]:
1127
+ summary = _integration_draft_summary(task_dir, child_names)
1128
+ lines.extend(
1129
+ [
1130
+ f"Final integration evidence: {summary}",
1131
+ "",
1132
+ ]
1133
+ )
1134
+
1135
+ if guard.is_full_task and guard.required_gates:
1136
+ rel_task = f".trellis/tasks/{task_dir.name}"
1137
+ lines.extend(
1138
+ [
1139
+ "## Completion gate preparation (not recorded)",
1140
+ "",
1141
+ "Reviewer gates are **not** recorded by this helper. After review, run:",
1142
+ "",
1143
+ ]
1144
+ )
1145
+ for gate in guard.required_gates:
1146
+ lines.append(
1147
+ f"- `python ./.trellis/scripts/task.py record-gate {rel_task} "
1148
+ f"--transition full-task-complete --gate {gate} --result PASS "
1149
+ f"--reviewer <reviewer-id> --evidence verify.md`"
1150
+ )
1151
+ lines.append("")
1152
+
1153
+ return "\n".join(lines).rstrip() + "\n"
1154
+
1155
+
1156
+ def _integration_draft_summary(task_dir: Path, child_names: list) -> str:
1157
+ """Summarize child integration states for a parent integration evidence line."""
1158
+ names = [n for n in child_names if isinstance(n, str)]
1159
+ if not names:
1160
+ return "parent children integrated per task-map.md"
1161
+
1162
+ data, _body = load_task_map(task_dir)
1163
+ states: list[str] = []
1164
+ if isinstance(data, dict):
1165
+ children = data.get("children")
1166
+ if isinstance(children, list):
1167
+ by_id = {
1168
+ entry.get("id"): entry.get("state")
1169
+ for entry in children
1170
+ if isinstance(entry, dict) and entry.get("id")
1171
+ }
1172
+ for name in names:
1173
+ state = by_id.get(name, "unknown")
1174
+ states.append(f"{name}={state}")
1175
+
1176
+ if states:
1177
+ return "children " + ", ".join(states) + " per task-map.md"
1178
+ return "all structural children terminal in task-map.md before parent archive"
1179
+
1180
+
1181
+ def prepare_archive_evidence(
1182
+ task_dir: Path,
1183
+ task_data: dict | None,
1184
+ *,
1185
+ dry_run: bool = False,
1186
+ ) -> tuple[bool, list[str]]:
1187
+ """
1188
+ Append missing archive evidence sections to verify.md without rewriting user text.
1189
+
1190
+ Returns (changed, messages).
1191
+ """
1192
+ messages: list[str] = []
1193
+ if task_data is None:
1194
+ return False, ["task.json missing or invalid"]
1195
+
1196
+ verify_path = task_dir / "verify.md"
1197
+ guard = validate_archive(task_dir, task_data)
1198
+
1199
+ if verify_path.is_file():
1200
+ try:
1201
+ existing = verify_path.read_text(encoding="utf-8")
1202
+ except OSError:
1203
+ return False, ["verify.md could not be read"]
1204
+ if ARCHIVE_EVIDENCE_DRAFT_MARKER in existing:
1205
+ messages.append(
1206
+ "verify.md already contains an archive evidence draft block; "
1207
+ "edit it in place instead of re-running prepare."
1208
+ )
1209
+ still_missing = _verify_evidence_errors(task_dir, task_data)
1210
+ if not still_missing:
1211
+ return False, messages
1212
+ messages.append(
1213
+ "Some evidence is still missing after the draft block; "
1214
+ "fill placeholders or add lines outside the draft section."
1215
+ )
1216
+ return False, messages
1217
+ content = existing
1218
+ else:
1219
+ content = "# Verification Evidence\n"
1220
+
1221
+ draft = build_archive_evidence_draft(task_dir, task_data, guard)
1222
+ status = _verify_evidence_status(task_dir, task_data)
1223
+ child_names = task_data.get("children")
1224
+ has_children = isinstance(child_names, list) and bool(child_names)
1225
+ needs_draft = (
1226
+ not status["validation"]
1227
+ or not status["acceptance"]
1228
+ or not status["durable_learning"]
1229
+ or (has_children and not status["integration"])
1230
+ or (guard.is_full_task and guard.required_gates)
1231
+ )
1232
+ if not needs_draft:
1233
+ messages.append("No missing archive evidence sections to draft.")
1234
+ return False, messages
1235
+
1236
+ new_content = content.rstrip() + "\n" + draft
1237
+ if dry_run:
1238
+ messages.append("Dry run: would append archive evidence draft to verify.md")
1239
+ return True, messages
1240
+
1241
+ try:
1242
+ verify_path.write_text(new_content, encoding="utf-8")
1243
+ except OSError as exc:
1244
+ return False, [f"failed to write verify.md: {exc}"]
1245
+
1246
+ messages.append("Appended archive evidence draft to verify.md")
1247
+ remaining = _verify_evidence_errors(task_dir, task_data)
1248
+ gate_errors = [
1249
+ e
1250
+ for e in validate_archive(task_dir, task_data).errors
1251
+ if e.startswith("missing gate record:")
1252
+ or e.startswith("gate failed:")
1253
+ or e.startswith("stale ")
1254
+ ]
1255
+ if remaining:
1256
+ messages.append(
1257
+ "Still missing after draft (edit placeholders): " + "; ".join(remaining)
1258
+ )
1259
+ if gate_errors:
1260
+ messages.append(
1261
+ "Completion gates still require explicit record-gate: "
1262
+ + "; ".join(gate_errors)
1263
+ )
1264
+ return True, messages
1265
+
1266
+
1267
+ def validate_reviewer_gate_input(
1268
+ transition: str,
1269
+ gate: str,
1270
+ result: str,
1271
+ reviewer: str,
1272
+ evidence: str,
1273
+ issue_fingerprint: str | None = None,
1274
+ issue_summary: str | None = None,
1275
+ root_cause: str | None = None,
1276
+ skip_approved_by: str | None = None,
1277
+ skip_reason: str | None = None,
1278
+ ) -> list[str]:
1279
+ """Validate CLI inputs for record-gate."""
1280
+ errors: list[str] = []
1281
+
1282
+ if transition not in KNOWN_TRANSITIONS:
1283
+ errors.append(f"unknown transition: {transition}")
1284
+ if gate == BASELINE_GATE:
1285
+ errors.append("baseline-check is CLI-owned and cannot be recorded manually")
1286
+ elif gate not in REVIEW_GATES:
1287
+ errors.append(f"unknown gate: {gate}")
1288
+ if result not in RESULTS:
1289
+ errors.append(f"invalid result: {result}")
1290
+ if not _is_short_token(reviewer) or not REVIEWER_RE.match(reviewer):
1291
+ errors.append("reviewer must be a short identifier")
1292
+ if not _is_short_text(evidence):
1293
+ errors.append("evidence must be a short reference, not a review body")
1294
+
1295
+ if result == "PASS":
1296
+ if issue_fingerprint:
1297
+ errors.append("--issue-fingerprint is only valid for FAIL")
1298
+ if root_cause:
1299
+ errors.append("--root-cause is only valid for FAIL")
1300
+ if skip_approved_by or skip_reason:
1301
+ errors.append("skip metadata is only valid for SKIPPED")
1302
+ elif result == "FAIL":
1303
+ if not _is_short_token(issue_fingerprint):
1304
+ errors.append("FAIL requires --issue-fingerprint")
1305
+ if root_cause not in FAIL_ROOT_CAUSES:
1306
+ errors.append(
1307
+ "FAIL requires --root-cause implementation-defect|contract-changing-defect|validation-environment-blocker"
1308
+ )
1309
+ if issue_summary and not _is_short_text(issue_summary, MAX_REASON):
1310
+ errors.append("--issue-summary must be short and single-line")
1311
+ if skip_approved_by or skip_reason:
1312
+ errors.append("skip metadata is only valid for SKIPPED")
1313
+ elif result == "SKIPPED":
1314
+ if issue_fingerprint or issue_summary or root_cause:
1315
+ errors.append("issue metadata is only valid for FAIL")
1316
+ if skip_approved_by != "user":
1317
+ errors.append("SKIPPED requires --skip-approved-by user")
1318
+ if not _is_short_text(skip_reason, MAX_REASON):
1319
+ errors.append("SKIPPED requires --skip-reason")
1320
+
1321
+ return errors
1322
+
1323
+
1324
+ def _route_for_fail_root_cause(root_cause: str | None) -> str | None:
1325
+ if root_cause is None:
1326
+ return None
1327
+ return FAIL_ROOT_CAUSES.get(root_cause)
1328
+
1329
+
1330
+ def _validate_quality_gates(
1331
+ gates: dict,
1332
+ verification_profile: str | None,
1333
+ ) -> list[str]:
1334
+ errors: list[str] = []
1335
+ mode = gates.get("mode")
1336
+ if mode not in ALLOWED_GATE_MODES:
1337
+ errors.append("quality_gates.mode must be profile or explicit")
1338
+
1339
+ profile = gates.get("profile")
1340
+ if profile and profile not in ALLOWED_VERIFICATION_PROFILES:
1341
+ errors.append(f"invalid quality_gates.profile: {profile}")
1342
+
1343
+ enabled = gates.get("enabled", [])
1344
+ disabled = gates.get("disabled", [])
1345
+ if enabled is None:
1346
+ enabled = []
1347
+ if disabled is None:
1348
+ disabled = []
1349
+
1350
+ if mode == "explicit":
1351
+ if not isinstance(enabled, list):
1352
+ errors.append("quality_gates.enabled must be a list in explicit mode")
1353
+ if not isinstance(disabled, list):
1354
+ errors.append("quality_gates.disabled must be a list in explicit mode")
1355
+
1356
+ all_configured = []
1357
+ if isinstance(enabled, list):
1358
+ all_configured.extend(enabled)
1359
+ if isinstance(disabled, list):
1360
+ all_configured.extend(disabled)
1361
+
1362
+ for gate in all_configured:
1363
+ if gate not in KNOWN_GATES:
1364
+ errors.append(f"unknown quality gate: {gate}")
1365
+
1366
+ if BASELINE_GATE in disabled:
1367
+ errors.append("baseline-check cannot be disabled")
1368
+
1369
+ active = set(enabled) if mode == "explicit" else set(
1370
+ PROFILE_DEFAULT_GATES.get(
1371
+ profile or verification_profile or "standard",
1372
+ PROFILE_DEFAULT_GATES["standard"],
1373
+ )
1374
+ )
1375
+ if "architecture-deep-review" in active and "architecture-review" not in active:
1376
+ errors.append("architecture-deep-review requires architecture-review")
1377
+
1378
+ return errors
1379
+
1380
+
1381
+ def _parse_inline_list(value: str) -> list[str] | str:
1382
+ value = value.strip()
1383
+ if value.startswith("[") and value.endswith("]"):
1384
+ inner = value[1:-1].strip()
1385
+ if not inner:
1386
+ return []
1387
+ return [
1388
+ item.strip().strip("'\"")
1389
+ for item in inner.split(",")
1390
+ if item.strip()
1391
+ ]
1392
+ return value
1393
+
1394
+
1395
+ def _hash_payload(payload: dict) -> str:
1396
+ body = json.dumps(payload, sort_keys=True, ensure_ascii=False, separators=(",", ":"))
1397
+ digest = hashlib.sha256(body.encode("utf-8")).hexdigest()
1398
+ return f"sha256:{digest}"
1399
+
1400
+
1401
+ def _stable_task_data(task_data: dict) -> dict:
1402
+ stable: dict = {}
1403
+ for key in STABLE_TASK_KEYS:
1404
+ if key in task_data:
1405
+ stable[key] = task_data.get(key)
1406
+ return stable
1407
+
1408
+
1409
+ def _parent_contract_metadata(
1410
+ task_dir: Path,
1411
+ task_data: dict,
1412
+ transition: str,
1413
+ ) -> dict | None:
1414
+ if transition == "child-review":
1415
+ parent_name = task_data.get("parent")
1416
+ if not isinstance(parent_name, str) or not parent_name:
1417
+ return None
1418
+ parent_dir = task_dir.parent / parent_name
1419
+ data, _ = load_task_map(parent_dir)
1420
+ return _task_map_contract_metadata("child", parent_dir, data)
1421
+
1422
+ if transition.startswith("parent-"):
1423
+ data, _ = load_task_map(task_dir)
1424
+ return _task_map_contract_metadata("parent", task_dir, data)
1425
+
1426
+ return None
1427
+
1428
+
1429
+ def _task_map_contract_metadata(
1430
+ role: str,
1431
+ parent_dir: Path,
1432
+ task_map_data: dict | None,
1433
+ ) -> dict:
1434
+ return {
1435
+ "role": role,
1436
+ "parent": parent_dir.name,
1437
+ "task_map_found": task_map_data is not None,
1438
+ "contract_epoch": (
1439
+ task_map_data.get("contract_epoch")
1440
+ if isinstance(task_map_data, dict)
1441
+ else None
1442
+ ),
1443
+ }
1444
+
1445
+
1446
+ def _reviewed_change_set_metadata(
1447
+ task_dir: Path,
1448
+ transition: str,
1449
+ ) -> dict | None:
1450
+ evidence_files = _change_set_evidence_files_for(transition)
1451
+ if not evidence_files:
1452
+ return None
1453
+
1454
+ files = []
1455
+ for name in evidence_files:
1456
+ path = task_dir / name
1457
+ try:
1458
+ content = path.read_text(encoding="utf-8") if path.is_file() else ""
1459
+ except OSError:
1460
+ content = ""
1461
+ files.append(
1462
+ {
1463
+ "path": name,
1464
+ "present": path.is_file(),
1465
+ "entries": _extract_reviewed_change_set_entries(content),
1466
+ }
1467
+ )
1468
+ return {"files": files}
1469
+
1470
+
1471
+ def _change_set_evidence_files_for(transition: str) -> list[str]:
1472
+ if transition == "full-task-complete":
1473
+ return ["verify.md"]
1474
+ if transition == "child-review":
1475
+ return ["verify.md", "handoff.md"]
1476
+ if transition.startswith("parent-"):
1477
+ return ["task-map.md", "verify.md"]
1478
+ return []
1479
+
1480
+
1481
+ def _extract_reviewed_change_set_entries(content: str) -> list[str]:
1482
+ return [
1483
+ match.group(1).strip()
1484
+ for match in REVIEWED_CHANGE_SET_RE.finditer(content)
1485
+ ]
1486
+
1487
+
1488
+ def _only_missing_gate_record(errors: list[str]) -> bool:
1489
+ return bool(errors) and all(
1490
+ error.startswith("missing gate record:") for error in errors
1491
+ )
1492
+
1493
+
1494
+ def make_planning_review_gate_record(
1495
+ task_dir: Path,
1496
+ task_data: dict,
1497
+ transition: str,
1498
+ gate: str,
1499
+ contract: dict,
1500
+ contract_fingerprint: str,
1501
+ artifact_fingerprint: str,
1502
+ ) -> dict:
1503
+ """Build a CLI-owned PASS record when planning artifacts satisfy the gate."""
1504
+ _ = task_dir, task_data, contract
1505
+ evidence_by_gate = {
1506
+ "requirements-review": "prd.md",
1507
+ "architecture-review": "design.md+implement.md",
1508
+ }
1509
+ return {
1510
+ "schema_version": SCHEMA_VERSION,
1511
+ "transition": transition,
1512
+ "gate": gate,
1513
+ "result": "PASS",
1514
+ "reviewer": AUTO_PLANNING_REVIEWER,
1515
+ "evidence": evidence_by_gate.get(gate, "planning-artifacts"),
1516
+ "checked_at": utc_now(),
1517
+ "contract_fingerprint": contract_fingerprint,
1518
+ "artifact_fingerprint": artifact_fingerprint,
1519
+ "issue_fingerprint": None,
1520
+ "root_cause": None,
1521
+ "route": None,
1522
+ "consecutive_failures": 0,
1523
+ "required_user_choice": None,
1524
+ "approved_skip": None,
1525
+ "auto_recorded": True,
1526
+ "auto_record_reason": "planning-artifacts",
1527
+ }
1528
+
1529
+
1530
+ def _start_execution_planning_gate_readiness_errors(
1531
+ task_dir: Path,
1532
+ gate: str,
1533
+ *,
1534
+ full_task: bool,
1535
+ contract: dict,
1536
+ ) -> list[str]:
1537
+ if gate == "requirements-review":
1538
+ return _prd_requirements_review_errors(task_dir)
1539
+ if gate == "architecture-review":
1540
+ errors = _required_file_errors(task_dir, ["design.md", "implement.md"])
1541
+ if not full_task:
1542
+ errors.append(
1543
+ "architecture-review requires design.md and implement.md (Full Task)"
1544
+ )
1545
+ errors.extend(validate_strategy_contract(contract))
1546
+ return errors
1547
+ return [f"planning gate readiness check unsupported: {gate}"]
1548
+
1549
+
1550
+ def _prd_requirements_review_errors(task_dir: Path) -> list[str]:
1551
+ path = task_dir / "prd.md"
1552
+ if not path.is_file():
1553
+ return ["prd.md missing for requirements-review"]
1554
+ try:
1555
+ content = path.read_text(encoding="utf-8")
1556
+ except OSError:
1557
+ return ["prd.md could not be read for requirements-review"]
1558
+ if not content.strip():
1559
+ return ["prd.md is empty (requirements-review)"]
1560
+ if not PRD_ACCEPTANCE_CRITERIA_HEADING_RE.search(content):
1561
+ return [
1562
+ "prd.md missing Acceptance Criteria heading (complete PRD Grill before start-execution)"
1563
+ ]
1564
+ criteria: list[str] = []
1565
+ for match in PRD_ACCEPTANCE_ITEM_RE.finditer(content):
1566
+ text = match.group(1).strip()
1567
+ if text and not PRD_PLACEHOLDER_RE.match(text):
1568
+ criteria.append(text)
1569
+ if not criteria:
1570
+ return [
1571
+ "prd.md needs at least one non-placeholder Acceptance Criteria item (replace TBD checkboxes)"
1572
+ ]
1573
+ return []
1574
+
1575
+
1576
+ def start_execution_repair_hints(
1577
+ errors: list[str],
1578
+ task_dir: Path,
1579
+ ) -> list[str]:
1580
+ """Map start-execution validation errors to actionable planning hints."""
1581
+ hints: list[str] = []
1582
+ rel_task = f".trellis/tasks/{task_dir.name}"
1583
+ for error in errors:
1584
+ if error.startswith("missing gate record:"):
1585
+ _rest = error.split(":", 1)[1].strip()
1586
+ _transition, gate = _rest.split("/", 1)
1587
+ if gate in START_EXECUTION_AUTO_GATES:
1588
+ hints.append(
1589
+ "Complete planning artifacts so Trellis can auto-record this gate on "
1590
+ f"`start-execution --approved` (requirements: prd.md Acceptance Criteria; "
1591
+ "architecture: design.md + valid implement.md contract)."
1592
+ )
1593
+ else:
1594
+ hints.append(
1595
+ f"python ./.trellis/scripts/task.py record-gate {rel_task} "
1596
+ f"--transition start-execution --gate {gate} --result PASS "
1597
+ f"--reviewer <reviewer-id> --evidence verify.md"
1598
+ )
1599
+ continue
1600
+ if "Acceptance Criteria" in error or "TBD" in error:
1601
+ hints.append(
1602
+ "Finish PRD Grill in trellis-brainstorm: fill Goal, Acceptance Criteria "
1603
+ "(non-TBD checkboxes), then re-run start-execution --check."
1604
+ )
1605
+ continue
1606
+ if "Development Strategy Contract" in error or error.startswith("invalid "):
1607
+ hints.append(
1608
+ "Fix the Development Strategy Contract block in implement.md "
1609
+ "(execution_mode, isolation, verification_profile, quality_gates)."
1610
+ )
1611
+ continue
1612
+ if error == "design.md and implement.md must be present together for Full Tasks":
1613
+ hints.append(
1614
+ "Add both design.md and implement.md for Full Tasks, or stay PRD-only for Lite."
1615
+ )
1616
+ return _dedupe_preserve_order(hints)
1617
+
1618
+
1619
+ def _artifact_files_for(transition: str, gate: str) -> list[str]:
1620
+ if transition == "start-execution":
1621
+ return ["prd.md", "design.md", "implement.md"]
1622
+ if transition == "full-task-complete":
1623
+ return ["prd.md", "design.md", "implement.md", "verify.md"]
1624
+ if transition == "child-review":
1625
+ return ["prd.md", "design.md", "implement.md", "verify.md", "handoff.md"]
1626
+ if transition in ("parent-accepted", "parent-integrated"):
1627
+ return ["task-map.md", "verify.md"]
1628
+ _ = gate
1629
+ return ["prd.md", "verify.md"]
1630
+
1631
+
1632
+ def _read_artifact_files(task_dir: Path, names: list[str]) -> list[dict]:
1633
+ files = []
1634
+ for name in names:
1635
+ path = task_dir / name
1636
+ try:
1637
+ content = path.read_text(encoding="utf-8") if path.is_file() else None
1638
+ except OSError:
1639
+ content = None
1640
+ files.append({"path": name, "content": content})
1641
+ return files
1642
+
1643
+
1644
+ def _required_file_errors(task_dir: Path, names: list[str]) -> list[str]:
1645
+ errors = []
1646
+ for name in names:
1647
+ path = task_dir / name
1648
+ if not path.is_file():
1649
+ errors.append(name)
1650
+ continue
1651
+ try:
1652
+ if not path.read_text(encoding="utf-8").strip():
1653
+ errors.append(f"{name} is empty")
1654
+ except OSError:
1655
+ errors.append(f"{name} could not be read")
1656
+ return errors
1657
+
1658
+
1659
+ def _get_gate_record(task_data: dict, transition: str, gate: str) -> dict | None:
1660
+ qgr = task_data.get("quality_gate_results")
1661
+ if not isinstance(qgr, dict):
1662
+ return None
1663
+ transitions = qgr.get("transitions")
1664
+ if not isinstance(transitions, dict):
1665
+ return None
1666
+ transition_records = transitions.get(transition)
1667
+ if not isinstance(transition_records, dict):
1668
+ return None
1669
+ record = transition_records.get(gate)
1670
+ return record if isinstance(record, dict) else None
1671
+
1672
+
1673
+ def _validate_gate_record_for_transition(
1674
+ task_data: dict,
1675
+ transition: str,
1676
+ gate: str,
1677
+ contract_fingerprint: str,
1678
+ artifact_fingerprint: str,
1679
+ ) -> list[str]:
1680
+ errors: list[str] = []
1681
+ record = _get_gate_record(task_data, transition, gate)
1682
+ if record is None:
1683
+ errors.append(f"missing gate record: {transition}/{gate}")
1684
+ return errors
1685
+
1686
+ result = record.get("result")
1687
+ if result == "FAIL":
1688
+ errors.append(f"gate failed: {transition}/{gate}")
1689
+ return errors
1690
+ if result not in ("PASS", "SKIPPED"):
1691
+ errors.append(f"gate record has invalid result: {transition}/{gate}")
1692
+ return errors
1693
+ if record.get("contract_fingerprint") != contract_fingerprint:
1694
+ errors.append(f"stale contract fingerprint: {transition}/{gate}")
1695
+ if record.get("artifact_fingerprint") != artifact_fingerprint:
1696
+ errors.append(f"stale artifact fingerprint: {transition}/{gate}")
1697
+ if result == "SKIPPED":
1698
+ approved_skip = record.get("approved_skip")
1699
+ if not isinstance(approved_skip, dict):
1700
+ errors.append(f"SKIPPED gate lacks approval metadata: {transition}/{gate}")
1701
+ elif approved_skip.get("approved_by") != "user" or not approved_skip.get("reason"):
1702
+ errors.append(f"invalid SKIPPED approval metadata: {transition}/{gate}")
1703
+ return errors
1704
+
1705
+
1706
+ def _next_consecutive_failures(
1707
+ previous: dict | None,
1708
+ issue_fingerprint: str | None,
1709
+ ) -> int:
1710
+ if not previous or previous.get("result") != "FAIL":
1711
+ return 1
1712
+ if previous.get("issue_fingerprint") != issue_fingerprint:
1713
+ return 1
1714
+ previous_count = previous.get("consecutive_failures")
1715
+ return int(previous_count) + 1 if isinstance(previous_count, int) else 2
1716
+
1717
+
1718
+ def _is_short_token(value: str | None, max_len: int = MAX_SHORT_FIELD) -> bool:
1719
+ if not value or not isinstance(value, str):
1720
+ return False
1721
+ if len(value) > max_len:
1722
+ return False
1723
+ if any(ch.isspace() for ch in value):
1724
+ return False
1725
+ return True
1726
+
1727
+
1728
+ def _is_short_text(value: str | None, max_len: int = MAX_SHORT_FIELD) -> bool:
1729
+ if not value or not isinstance(value, str):
1730
+ return False
1731
+ if len(value) > max_len:
1732
+ return False
1733
+ if "\n" in value or "\r" in value:
1734
+ return False
1735
+ return True